# HG changeset patch # User Raphaël Gomès # Date 2022-02-18 13:27:43 # Node ID c00d3ce4e94bb0ee8d809e25e1dcb2a5fab84e2c # Parent b84ff512b645a076fabaa41c0f1bd7a7e94a80a0 # Parent 61c27ac5a1c5239d8539b71ef0a60490463d1703 branching: merge default into stable for 6.1 freeze diff --git a/contrib/automation/hgautomation/cli.py b/contrib/automation/hgautomation/cli.py --- a/contrib/automation/hgautomation/cli.py +++ b/contrib/automation/hgautomation/cli.py @@ -158,7 +158,7 @@ def build_all_windows_packages( windows.synchronize_hg(SOURCE_ROOT, revision, instance) - for py_version in ("2.7", "3.7", "3.8", "3.9"): + for py_version in ("2.7", "3.7", "3.8", "3.9", "3.10"): for arch in ("x86", "x64"): windows.purge_hg(winrm_client) windows.build_wheel( @@ -377,7 +377,7 @@ def get_parser(): sp.add_argument( '--python-version', help='Python version to build for', - choices={'2.7', '3.7', '3.8', '3.9'}, + choices={'2.7', '3.7', '3.8', '3.9', '3.10'}, nargs='*', default=['3.8'], ) @@ -501,7 +501,7 @@ def get_parser(): sp.add_argument( '--python-version', help='Python version to use', - choices={'2.7', '3.5', '3.6', '3.7', '3.8', '3.9'}, + choices={'2.7', '3.5', '3.6', '3.7', '3.8', '3.9', '3.10'}, default='2.7', ) sp.add_argument( diff --git a/contrib/automation/hgautomation/windows.py b/contrib/automation/hgautomation/windows.py --- a/contrib/automation/hgautomation/windows.py +++ b/contrib/automation/hgautomation/windows.py @@ -129,6 +129,8 @@ WHEEL_FILENAME_PYTHON38_X86 = 'mercurial WHEEL_FILENAME_PYTHON38_X64 = 'mercurial-{version}-cp38-cp38-win_amd64.whl' WHEEL_FILENAME_PYTHON39_X86 = 'mercurial-{version}-cp39-cp39-win32.whl' WHEEL_FILENAME_PYTHON39_X64 = 'mercurial-{version}-cp39-cp39-win_amd64.whl' +WHEEL_FILENAME_PYTHON310_X86 = 'mercurial-{version}-cp310-cp310-win32.whl' +WHEEL_FILENAME_PYTHON310_X64 = 'mercurial-{version}-cp310-cp310-win_amd64.whl' EXE_FILENAME_PYTHON2_X86 = 'Mercurial-{version}-x86-python2.exe' EXE_FILENAME_PYTHON2_X64 = 'Mercurial-{version}-x64-python2.exe' @@ -480,6 +482,8 @@ def resolve_wheel_artifacts(dist_path: p dist_path / WHEEL_FILENAME_PYTHON38_X64.format(version=version), dist_path / WHEEL_FILENAME_PYTHON39_X86.format(version=version), dist_path / WHEEL_FILENAME_PYTHON39_X64.format(version=version), + dist_path / WHEEL_FILENAME_PYTHON310_X86.format(version=version), + dist_path / WHEEL_FILENAME_PYTHON310_X64.format(version=version), ) @@ -493,6 +497,8 @@ def resolve_all_artifacts(dist_path: pat dist_path / WHEEL_FILENAME_PYTHON38_X64.format(version=version), dist_path / WHEEL_FILENAME_PYTHON39_X86.format(version=version), dist_path / WHEEL_FILENAME_PYTHON39_X64.format(version=version), + dist_path / WHEEL_FILENAME_PYTHON310_X86.format(version=version), + dist_path / WHEEL_FILENAME_PYTHON310_X64.format(version=version), dist_path / EXE_FILENAME_PYTHON2_X86.format(version=version), dist_path / EXE_FILENAME_PYTHON2_X64.format(version=version), dist_path / EXE_FILENAME_PYTHON3_X86.format(version=version), diff --git a/contrib/heptapod-ci.yml b/contrib/heptapod-ci.yml --- a/contrib/heptapod-ci.yml +++ b/contrib/heptapod-ci.yml @@ -56,6 +56,11 @@ rust-cargo-test-py3: phabricator-refresh: stage: phabricator + rules: + - if: '"$PHABRICATOR_TOKEN" != "NO-PHAB"' + when: on_success + - if: '"$PHABRICATOR_TOKEN" == "NO-PHAB"' + when: never 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}" diff --git a/contrib/install-windows-dependencies.ps1 b/contrib/install-windows-dependencies.ps1 --- a/contrib/install-windows-dependencies.ps1 +++ b/contrib/install-windows-dependencies.ps1 @@ -29,10 +29,15 @@ $PYTHON38_x64_URL = "https://www.python.org/ftp/python/3.8.10/python-3.8.10-amd64.exe" $PYTHON38_x64_SHA256 = "7628244cb53408b50639d2c1287c659f4e29d3dfdb9084b11aed5870c0c6a48a" -$PYTHON39_x86_URL = "https://www.python.org/ftp/python/3.9.5/python-3.9.5.exe" -$PYTHON39_x86_SHA256 = "505129081a839b699a6ab9064b441ad922ef03767b5dd4241fd0c2166baf64de" -$PYTHON39_x64_URL = "https://www.python.org/ftp/python/3.9.5/python-3.9.5-amd64.exe" -$PYTHON39_x64_SHA256 = "84d5243088ba00c11e51905c704dbe041040dfff044f4e1ce5476844ee2e6eac" +$PYTHON39_x86_URL = "https://www.python.org/ftp/python/3.9.9/python-3.9.9.exe" +$PYTHON39_x86_SHA256 = "6646a5683adf14d35e8c53aab946895bc0f0b825f7acac3a62cc85ee7d0dc71a" +$PYTHON39_X64_URL = "https://www.python.org/ftp/python/3.9.9/python-3.9.9-amd64.exe" +$PYTHON39_x64_SHA256 = "137d59e5c0b01a8f1bdcba08344402ae658c81c6bf03b6602bd8b4e951ad0714" + +$PYTHON310_x86_URL = "https://www.python.org/ftp/python/3.10.0/python-3.10.0.exe" +$PYTHON310_x86_SHA256 = "ea896eeefb1db9e12fb89ec77a6e28c9fe52b4a162a34c85d9688be2ec2392e8" +$PYTHON310_X64_URL = "https://www.python.org/ftp/python/3.10.0/python-3.10.0-amd64.exe" +$PYTHON310_x64_SHA256 = "cb580eb7dc55f9198e650f016645023e8b2224cf7d033857d12880b46c5c94ef" # PIP 19.2.3. $PIP_URL = "https://github.com/pypa/get-pip/raw/309a56c5fd94bd1134053a541cb4657a4e47e09d/get-pip.py" @@ -132,6 +137,8 @@ function Install-Dependencies($prefix) { Secure-Download $PYTHON38_x64_URL ${prefix}\assets\python38-x64.exe $PYTHON38_x64_SHA256 Secure-Download $PYTHON39_x86_URL ${prefix}\assets\python39-x86.exe $PYTHON39_x86_SHA256 Secure-Download $PYTHON39_x64_URL ${prefix}\assets\python39-x64.exe $PYTHON39_x64_SHA256 + Secure-Download $PYTHON310_x86_URL ${prefix}\assets\python310-x86.exe $PYTHON310_x86_SHA256 + Secure-Download $PYTHON310_x64_URL ${prefix}\assets\python310-x64.exe $PYTHON310_x64_SHA256 Secure-Download $PIP_URL ${pip} $PIP_SHA256 Secure-Download $VS_BUILD_TOOLS_URL ${prefix}\assets\vs_buildtools.exe $VS_BUILD_TOOLS_SHA256 Secure-Download $INNO_SETUP_URL ${prefix}\assets\InnoSetup.exe $INNO_SETUP_SHA256 @@ -146,6 +153,8 @@ function Install-Dependencies($prefix) { # Install-Python3 "Python 3.8 64-bit" ${prefix}\assets\python38-x64.exe ${prefix}\python38-x64 ${pip} Install-Python3 "Python 3.9 32-bit" ${prefix}\assets\python39-x86.exe ${prefix}\python39-x86 ${pip} Install-Python3 "Python 3.9 64-bit" ${prefix}\assets\python39-x64.exe ${prefix}\python39-x64 ${pip} + Install-Python3 "Python 3.10 32-bit" ${prefix}\assets\python310-x86.exe ${prefix}\python310-x86 ${pip} + Install-Python3 "Python 3.10 64-bit" ${prefix}\assets\python310-x64.exe ${prefix}\python310-x64 ${pip} Write-Output "installing Visual Studio 2017 Build Tools and SDKs" Invoke-Process ${prefix}\assets\vs_buildtools.exe "--quiet --wait --norestart --nocache --channelUri https://aka.ms/vs/15/release/channel --add Microsoft.VisualStudio.Workload.MSBuildTools --add Microsoft.VisualStudio.Component.Windows10SDK.17763 --add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Component.Windows10SDK --add Microsoft.VisualStudio.Component.VC.140" diff --git a/contrib/packaging/requirements-windows-py3.txt b/contrib/packaging/requirements-windows-py3.txt --- a/contrib/packaging/requirements-windows-py3.txt +++ b/contrib/packaging/requirements-windows-py3.txt @@ -1,68 +1,84 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.7 # To update, run: # # pip-compile --generate-hashes --output-file=contrib/packaging/requirements-windows-py3.txt contrib/packaging/requirements-windows.txt.in # atomicwrites==1.4.0 \ --hash=sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197 \ - --hash=sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a \ + --hash=sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a # via pytest attrs==21.2.0 \ --hash=sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1 \ - --hash=sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb \ + --hash=sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb # via pytest cached-property==1.5.2 \ --hash=sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130 \ - --hash=sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0 \ + --hash=sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0 # via pygit2 certifi==2021.5.30 \ --hash=sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee \ - --hash=sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8 \ + --hash=sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8 # via dulwich -cffi==1.14.4 \ - --hash=sha256:00a1ba5e2e95684448de9b89888ccd02c98d512064b4cb987d48f4b40aa0421e \ - --hash=sha256:00e28066507bfc3fe865a31f325c8391a1ac2916219340f87dfad602c3e48e5d \ - --hash=sha256:045d792900a75e8b1e1b0ab6787dd733a8190ffcf80e8c8ceb2fb10a29ff238a \ - --hash=sha256:0638c3ae1a0edfb77c6765d487fee624d2b1ee1bdfeffc1f0b58c64d149e7eec \ - --hash=sha256:105abaf8a6075dc96c1fe5ae7aae073f4696f2905fde6aeada4c9d2926752362 \ - --hash=sha256:155136b51fd733fa94e1c2ea5211dcd4c8879869008fc811648f16541bf99668 \ - --hash=sha256:1a465cbe98a7fd391d47dce4b8f7e5b921e6cd805ef421d04f5f66ba8f06086c \ - --hash=sha256:1d2c4994f515e5b485fd6d3a73d05526aa0fcf248eb135996b088d25dfa1865b \ - --hash=sha256:2c24d61263f511551f740d1a065eb0212db1dbbbbd241db758f5244281590c06 \ - --hash=sha256:51a8b381b16ddd370178a65360ebe15fbc1c71cf6f584613a7ea08bfad946698 \ - --hash=sha256:594234691ac0e9b770aee9fcdb8fa02c22e43e5c619456efd0d6c2bf276f3eb2 \ - --hash=sha256:5cf4be6c304ad0b6602f5c4e90e2f59b47653ac1ed9c662ed379fe48a8f26b0c \ - --hash=sha256:64081b3f8f6f3c3de6191ec89d7dc6c86a8a43911f7ecb422c60e90c70be41c7 \ - --hash=sha256:6bc25fc545a6b3d57b5f8618e59fc13d3a3a68431e8ca5fd4c13241cd70d0009 \ - --hash=sha256:798caa2a2384b1cbe8a2a139d80734c9db54f9cc155c99d7cc92441a23871c03 \ - --hash=sha256:7c6b1dece89874d9541fc974917b631406233ea0440d0bdfbb8e03bf39a49b3b \ - --hash=sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909 \ - --hash=sha256:8d6603078baf4e11edc4168a514c5ce5b3ba6e3e9c374298cb88437957960a53 \ - --hash=sha256:9cc46bc107224ff5b6d04369e7c595acb700c3613ad7bcf2e2012f62ece80c35 \ - --hash=sha256:9f7a31251289b2ab6d4012f6e83e58bc3b96bd151f5b5262467f4bb6b34a7c26 \ - --hash=sha256:9ffb888f19d54a4d4dfd4b3f29bc2c16aa4972f1c2ab9c4ab09b8ab8685b9c2b \ - --hash=sha256:a7711edca4dcef1a75257b50a2fbfe92a65187c47dab5a0f1b9b332c5919a3fb \ - --hash=sha256:af5c59122a011049aad5dd87424b8e65a80e4a6477419c0c1015f73fb5ea0293 \ - --hash=sha256:b18e0a9ef57d2b41f5c68beefa32317d286c3d6ac0484efd10d6e07491bb95dd \ - --hash=sha256:b4e248d1087abf9f4c10f3c398896c87ce82a9856494a7155823eb45a892395d \ - --hash=sha256:ba4e9e0ae13fc41c6b23299545e5ef73055213e466bd107953e4a013a5ddd7e3 \ - --hash=sha256:c6332685306b6417a91b1ff9fae889b3ba65c2292d64bd9245c093b1b284809d \ - --hash=sha256:d9efd8b7a3ef378dd61a1e77367f1924375befc2eba06168b6ebfa903a5e59ca \ - --hash=sha256:df5169c4396adc04f9b0a05f13c074df878b6052430e03f50e68adf3a57aa28d \ - --hash=sha256:ebb253464a5d0482b191274f1c8bf00e33f7e0b9c66405fbffc61ed2c839c775 \ - --hash=sha256:ec80dc47f54e6e9a78181ce05feb71a0353854cc26999db963695f950b5fb375 \ - --hash=sha256:f032b34669220030f905152045dfa27741ce1a6db3324a5bc0b96b6c7420c87b \ - --hash=sha256:f60567825f791c6f8a592f3c6e3bd93dd2934e3f9dac189308426bd76b00ef3b \ - --hash=sha256:f803eaa94c2fcda012c047e62bc7a51b0bdabda1cad7a92a522694ea2d76e49f \ +cffi==1.15.0 \ + --hash=sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3 \ + --hash=sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2 \ + --hash=sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636 \ + --hash=sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20 \ + --hash=sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728 \ + --hash=sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27 \ + --hash=sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66 \ + --hash=sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443 \ + --hash=sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0 \ + --hash=sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7 \ + --hash=sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39 \ + --hash=sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605 \ + --hash=sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a \ + --hash=sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37 \ + --hash=sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029 \ + --hash=sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139 \ + --hash=sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc \ + --hash=sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df \ + --hash=sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14 \ + --hash=sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880 \ + --hash=sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2 \ + --hash=sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a \ + --hash=sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e \ + --hash=sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474 \ + --hash=sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024 \ + --hash=sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8 \ + --hash=sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0 \ + --hash=sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e \ + --hash=sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a \ + --hash=sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e \ + --hash=sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032 \ + --hash=sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6 \ + --hash=sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e \ + --hash=sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b \ + --hash=sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e \ + --hash=sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954 \ + --hash=sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962 \ + --hash=sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c \ + --hash=sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4 \ + --hash=sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55 \ + --hash=sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962 \ + --hash=sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023 \ + --hash=sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c \ + --hash=sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6 \ + --hash=sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8 \ + --hash=sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382 \ + --hash=sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7 \ + --hash=sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc \ + --hash=sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997 \ + --hash=sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796 # via pygit2 colorama==0.4.4 \ --hash=sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b \ - --hash=sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2 \ + --hash=sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2 # via pytest docutils==0.16 \ --hash=sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af \ - --hash=sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc \ + --hash=sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc # via -r contrib/packaging/requirements-windows.txt.in dulwich==0.20.6 ; python_version >= "3" \ --hash=sha256:1ccd55e38fa9f169290f93e027ab4508202f5bdd6ef534facac4edd3f6903f0d \ @@ -77,26 +93,29 @@ dulwich==0.20.6 ; python_version >= "3" --hash=sha256:8f7a7f973be2beedfb10dd8d3eb6bdf9ec466c72ad555704897cbd6357fe5021 \ --hash=sha256:bea6e6caffc6c73bfd1647714c5715ab96ac49deb8beb8b67511529afa25685a \ --hash=sha256:e5871b86a079e9e290f52ab14559cea1b694a0b8ed2b9ebb898f6ced7f14a406 \ - --hash=sha256:e593f514b8ac740b4ceeb047745b4719bfc9f334904245c6edcb3a9d002f577b \ + --hash=sha256:e593f514b8ac740b4ceeb047745b4719bfc9f334904245c6edcb3a9d002f577b # via -r contrib/packaging/requirements-windows.txt.in fuzzywuzzy==0.18.0 \ - --hash=sha256:45016e92264780e58972dca1b3d939ac864b78437422beecebb3095f8efd00e8 \ + --hash=sha256:45016e92264780e58972dca1b3d939ac864b78437422beecebb3095f8efd00e8 # via -r contrib/packaging/requirements-windows.txt.in idna==3.2 \ --hash=sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a \ - --hash=sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3 \ + --hash=sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3 # via yarl importlib-metadata==3.1.0 \ --hash=sha256:590690d61efdd716ff82c39ca9a9d4209252adfe288a4b5721181050acbd4175 \ - --hash=sha256:d9b8a46a0885337627a6430db287176970fff18ad421becec1d64cfc763c2099 \ - # via keyring, pluggy, pytest + --hash=sha256:d9b8a46a0885337627a6430db287176970fff18ad421becec1d64cfc763c2099 + # via + # keyring + # pluggy + # pytest iniconfig==1.1.1 \ --hash=sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3 \ - --hash=sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32 \ + --hash=sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32 # via pytest keyring==21.4.0 \ --hash=sha256:4e34ea2fdec90c1c43d6610b5a5fafa1b9097db1802948e90caf5763974b8f8d \ - --hash=sha256:9aeadd006a852b78f4b4ef7c7556c2774d2432bbef8ee538a3e9089ac8b11466 \ + --hash=sha256:9aeadd006a852b78f4b4ef7c7556c2774d2432bbef8ee538a3e9089ac8b11466 # via -r contrib/packaging/requirements-windows.txt.in multidict==5.1.0 \ --hash=sha256:018132dbd8688c7a69ad89c4a3f39ea2f9f33302ebe567a879da8f4ca73f0d0a \ @@ -135,62 +154,68 @@ multidict==5.1.0 \ --hash=sha256:ecc771ab628ea281517e24fd2c52e8f31c41e66652d07599ad8818abaad38cda \ --hash=sha256:f200755768dc19c6f4e2b672421e0ebb3dd54c38d5a4f262b872d8cfcc9e93b5 \ --hash=sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281 \ - --hash=sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80 \ + --hash=sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80 # via yarl packaging==21.0 \ --hash=sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7 \ - --hash=sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14 \ + --hash=sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14 # via pytest pluggy==0.13.1 \ --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ - --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d # via pytest py==1.10.0 \ --hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 \ - --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a \ + --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a # via pytest -pycparser==2.20 \ - --hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0 \ - --hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705 \ +pycparser==2.21 \ + --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ + --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 # via cffi -pygit2==1.4.0 ; python_version >= "3" \ - --hash=sha256:0d298098e286eeda000e49ca7e1b41f87300e10dd8b9d06b32b008bd61f50b83 \ - --hash=sha256:0ee135eb2cd8b07ce1374f3596cc5c3213472d6389bad6a4c5d87d8e267e93e9 \ - --hash=sha256:32eb863d6651d4890ced318505ea8dc229bd9637deaf29c898de1ab574d727a0 \ - --hash=sha256:37d6d7d6d7804c42a0fe23425c72e38093488525092fc5e51a05684e63503ce7 \ - --hash=sha256:41204b6f3406d9f53147710f3cc485d77181ba67f57c34d36b7c86de1c14a18c \ - --hash=sha256:818c91b582109d90580c5da74af783738838353f15eb12eeb734d80a974b05a3 \ - --hash=sha256:8306a302487dac67df7af6a064bb37e8a8eb4138958f9560ff49ff162e185dab \ - --hash=sha256:9c2f2d9ef59513007b66f6534b000792b614de3faf60313a0a68f6b8571aea85 \ - --hash=sha256:9c8d5881eb709e2e2e13000b507a131bd5fb91a879581030088d0ddffbcd19af \ - --hash=sha256:b422e417739def0a136a6355723dfe8a5ffc83db5098076f28a14f1d139779c1 \ - --hash=sha256:cbeb38ab1df9b5d8896548a11e63aae8a064763ab5f1eabe4475e6b8a78ee1c8 \ - --hash=sha256:cf00481ddf053e549a6edd0216bdc267b292d261eae02a67bb3737de920cbf88 \ - --hash=sha256:d0d889144e9487d926fecea947c3f39ce5f477e521d7d467d2e66907e4cd657d \ - --hash=sha256:ddb7a1f6d38063e8724abfa1cfdfb0f9b25014b8bca0546274b7a84b873a3888 \ - --hash=sha256:e9037a7d810750fe23c9f5641ef14a0af2525ff03e14752cd4f73e1870ecfcb0 \ - --hash=sha256:ec5c0365a9bdfcac1609d20868507b28685ec5ea7cc3a2c903c9b62ef2e0bbc0 \ - --hash=sha256:fdd8ba30cda277290e000322f505132f590cf89bd7d31829b45a3cb57447ec32 \ +pygit2==1.7.1 ; python_version >= "3" \ + --hash=sha256:2c9e95efb86c0b32cc07c26be3d179e851ca4a7899c47fef63c4203963144f5e \ + --hash=sha256:3ddacbf461652d3d4900382f821d9fbd5ae2dedecd7862b5245842419ad0ccba \ + --hash=sha256:4cb0414df6089d0072ebe93ff2f34730737172dd5f0e72289567d06a6caf09c0 \ + --hash=sha256:56e960dc74f4582bfa3ca17a1a9d542732fc93b5cf8f82574c235d06b2d61eae \ + --hash=sha256:6b17ab922c2a2d99b30ab9222472b07732bf7261d9f9655a4ea23b4c700049d8 \ + --hash=sha256:73a7b471f22cb59e8729016de1f447c472b3b2c1cc2b622194e5e3b48a7f5776 \ + --hash=sha256:761a8850e33822796c1c24d411d5cc2460c04e1a74b04ae8560efd3596bbd6bd \ + --hash=sha256:7c467e81158f5827b3bca6362e5cc9b92857eff9de65034d338c1f18524b09be \ + --hash=sha256:7c56e10592e62610a19bd3e2a633aafe3488c57b906c7c2fde0299937f0f0b2f \ + --hash=sha256:7cc2a8e29cc9598310a78cf58b70d9331277cf374802be8f97d97c4a9e5d8387 \ + --hash=sha256:812670f7994f31778e873a9eced29d2bbfa91674e8be0ab1e974c8a4bda9cbab \ + --hash=sha256:8cdb0b1d6c3d24b44f340fed143b16e64ba23fe2a449f1a5db87aaf9339a9dbe \ + --hash=sha256:91b77a305d8d18b649396e66e832d654cd593a3d29b5728f753f254a04533812 \ + --hash=sha256:a75bcde32238c77eb0cf7d9698a5aa899408d7ad999a5920a29a7c4b80fdeaa7 \ + --hash=sha256:b060240cf3038e7a0706bbfc5436dd03b8d5ac797ac1d512b613f4d04b974c80 \ + --hash=sha256:cdfa61c0428a8182e5a6a1161c017b824cd511574f080a40b10d6413774eb0ca \ + --hash=sha256:d7faa29558436decc2e78110f38d6677eb366b683ba5cdc2803d47195711165d \ + --hash=sha256:d831825ad9c3b3c28e6b3ef8a2401ad2d3fd4db5455427ff27175a7e254e2592 \ + --hash=sha256:df4c477bdfac85d32a1e3180282cd829a0980aa69be9bd0f7cbd4db1778ca72b \ + --hash=sha256:eced3529bafcaaac015d08dfaa743b3cbad37fcd5b13ae9d280b8b7f716ec5ce \ + --hash=sha256:fec17e2da668e6bb192d777417aad9c7ca924a166d0a0b9a81a11e00362b1bc7 # via -r contrib/packaging/requirements-windows.txt.in pygments==2.7.1 \ --hash=sha256:307543fe65c0947b126e83dd5a61bd8acbd84abec11f43caebaf5534cbc17998 \ - --hash=sha256:926c3f319eda178d1bd90851e4317e6d8cdb5e292a3386aac9bd75eca29cf9c7 \ + --hash=sha256:926c3f319eda178d1bd90851e4317e6d8cdb5e292a3386aac9bd75eca29cf9c7 # via -r contrib/packaging/requirements-windows.txt.in pyparsing==2.4.7 \ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ - --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b # via packaging -pytest-vcr==1.0.2 \ - --hash=sha256:23ee51b75abbcc43d926272773aae4f39f93aceb75ed56852d0bf618f92e1896 \ - # via -r contrib/packaging/requirements-windows.txt.in pytest==6.2.4 \ --hash=sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b \ - --hash=sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890 \ + --hash=sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890 # via pytest-vcr +pytest-vcr==1.0.2 \ + --hash=sha256:23ee51b75abbcc43d926272773aae4f39f93aceb75ed56852d0bf618f92e1896 + # via -r contrib/packaging/requirements-windows.txt.in pywin32-ctypes==0.2.0 \ --hash=sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942 \ - --hash=sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98 \ - # via -r contrib/packaging/requirements-windows.txt.in, keyring + --hash=sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98 + # via + # -r contrib/packaging/requirements-windows.txt.in + # keyring pyyaml==5.4.1 \ --hash=sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf \ --hash=sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696 \ @@ -220,41 +245,43 @@ pyyaml==5.4.1 \ --hash=sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc \ --hash=sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247 \ --hash=sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6 \ - --hash=sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0 \ + --hash=sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0 # via vcrpy six==1.16.0 \ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ - --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 \ + --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 # via vcrpy toml==0.10.2 \ --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \ - --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f \ + --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f # via pytest typing-extensions==3.10.0.0 \ --hash=sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497 \ --hash=sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342 \ - --hash=sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84 \ + --hash=sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84 # via yarl urllib3==1.25.11 \ --hash=sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2 \ - --hash=sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e \ + --hash=sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e # via dulwich vcrpy==4.1.1 \ --hash=sha256:12c3fcdae7b88ecf11fc0d3e6d77586549d4575a2ceee18e82eee75c1f626162 \ - --hash=sha256:57095bf22fc0a2d99ee9674cdafebed0f3ba763018582450706f7d3a74fff599 \ + --hash=sha256:57095bf22fc0a2d99ee9674cdafebed0f3ba763018582450706f7d3a74fff599 # via pytest-vcr -windows-curses==2.2.0 \ - --hash=sha256:1452d771ec6f9b3fef037da2b169196a9a12be4e86a6c27dd579adac70c42028 \ - --hash=sha256:267544e4f60c09af6505e50a69d7f01d7f8a281cf4bd4fc7efc3b32b9a4ef64e \ - --hash=sha256:389228a3df556102e72450f599283094168aa82eee189f501ad9f131a0fc92e1 \ - --hash=sha256:84336fe470fa07288daec5c684dec74c0766fec6b3511ccedb4c494804acfbb7 \ - --hash=sha256:9aa6ff60be76f5de696dc6dbf7897e3b1e6abcf4c0f741e9a0ee22cd6ef382f8 \ - --hash=sha256:c4a8ce00e82635f06648cc40d99f470be4e3ffeb84f9f7ae9d6a4f68ec6361e7 \ - --hash=sha256:c5cd032bc7d0f03224ab55c925059d98e81795098d59bbd10f7d05c7ea9677ce \ - --hash=sha256:fc0be372fe6da3c39d7093154ce029115a927bf287f34b4c615e2b3f8c23dfaa \ +windows-curses==2.3.0 \ + --hash=sha256:170c0d941c2e0cdf864e7f0441c1bdf0709232bf4aa7ce7f54d90fc76a4c0504 \ + --hash=sha256:4d5fb991d1b90a41c2332f02241a1f84c8a1e6bc8f6e0d26f532d0da7a9f7b51 \ + --hash=sha256:7a35eda4cb120b9e1a5ae795f3bc06c55b92c9d391baba6be1903285a05f3551 \ + --hash=sha256:935be95cfdb9213f6f5d3d5bcd489960e3a8fbc9b574e7b2e8a3a3cc46efff49 \ + --hash=sha256:a3a63a0597729e10f923724c2cf972a23ea677b400d2387dee1d668cf7116177 \ + --hash=sha256:c860f596d28377e47f322b7382be4d3573fd76d1292234996bb7f72e0bc0ed0d \ + --hash=sha256:cc5fa913780d60f4a40824d374a4f8ca45b4e205546e83a2d85147315a57457e \ + --hash=sha256:d5cde8ec6d582aa77af791eca54f60858339fb3f391945f9cad11b1ab71062e3 \ + --hash=sha256:e913dc121446d92b33fe4f5bcca26d3a34e4ad19f2af160370d57c3d1e93b4e1 \ + --hash=sha256:fbc2131cec57e422c6660e6cdb3420aff5be5169b8e45bb7c471f884b0590a2b # via -r contrib/packaging/requirements-windows.txt.in wrapt==1.12.1 \ - --hash=sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7 \ + --hash=sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7 # via vcrpy yarl==1.6.3 \ --hash=sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e \ @@ -293,9 +320,9 @@ yarl==1.6.3 \ --hash=sha256:e6b5460dc5ad42ad2b36cca524491dfcaffbfd9c8df50508bddc354e787b8dc2 \ --hash=sha256:f040bcc6725c821a4c0665f3aa96a4d0805a7aaf2caf266d256b8ed71b9f041c \ --hash=sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a \ - --hash=sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71 \ + --hash=sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71 # via vcrpy zipp==3.4.0 \ --hash=sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108 \ - --hash=sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb \ + --hash=sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb # via importlib-metadata diff --git a/contrib/packaging/requirements.txt b/contrib/packaging/requirements.txt --- a/contrib/packaging/requirements.txt +++ b/contrib/packaging/requirements.txt @@ -1,16 +1,16 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.7 # To update, run: # # pip-compile --generate-hashes --output-file=contrib/packaging/requirements.txt contrib/packaging/requirements.txt.in # docutils==0.16 \ --hash=sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af \ - --hash=sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc \ + --hash=sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc # via -r contrib/packaging/requirements.txt.in jinja2==2.11.2 \ --hash=sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0 \ - --hash=sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035 \ + --hash=sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035 # via -r contrib/packaging/requirements.txt.in markupsafe==1.1.1 \ --hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \ @@ -45,5 +45,5 @@ markupsafe==1.1.1 \ --hash=sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f \ --hash=sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2 \ --hash=sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7 \ - --hash=sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be \ + --hash=sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be # via jinja2 diff --git a/contrib/phab-refresh-stack.sh b/contrib/phab-refresh-stack.sh --- a/contrib/phab-refresh-stack.sh +++ b/contrib/phab-refresh-stack.sh @@ -1,6 +1,11 @@ #!/bin/bash set -eu +if [[ "$PHABRICATOR_TOKEN" == "NO-PHAB" ]]; then + echo 'Skipping Phabricator Step' >&2 + exit 0 +fi + revision_in_stack=`hg log \ --rev '.#stack and ::. and topic()' \ -T '\nONE-REV\n' \ @@ -27,6 +32,7 @@ fi if [[ "$PHABRICATOR_TOKEN" == "" ]]; then echo 'missing $PHABRICATOR_TOKEN variable' >&2 + echo '(use PHABRICATOR_TOKEN="NO-PHAB" to disable this step)' >&2 exit 2 fi diff --git a/contrib/simplemerge b/contrib/simplemerge --- a/contrib/simplemerge +++ b/contrib/simplemerge @@ -13,9 +13,9 @@ from mercurial import ( context, error, fancyopts, - pycompat, simplemerge, ui as uimod, + util, ) from mercurial.utils import procutil, stringutil @@ -65,6 +65,17 @@ def showhelp(): procutil.stdout.write(b' %-*s %s\n' % (opts_len, first, second)) +def _verifytext(input, ui, quiet=False, allow_binary=False): + """verifies that text is non-binary (unless opts[text] is passed, + then we just warn)""" + if stringutil.binary(input.text()): + msg = _(b"%s looks like a binary file.") % input.fctx.path() + if not quiet: + ui.warn(_(b'warning: %s\n') % msg) + if not allow_binary: + sys.exit(1) + + try: for fp in (sys.stdin, procutil.stdout, sys.stderr): procutil.setbinary(fp) @@ -80,16 +91,44 @@ try: sys.exit(0) if len(args) != 3: raise ParseError(_(b'wrong number of arguments').decode('utf8')) + mode = b'merge' + if len(opts[b'label']) > 2: + mode = b'merge3' local, base, other = args - sys.exit( - simplemerge.simplemerge( - uimod.ui.load(), - context.arbitraryfilectx(local), - context.arbitraryfilectx(base), - context.arbitraryfilectx(other), - **pycompat.strkwargs(opts) - ) + overrides = opts[b'label'] + if len(overrides) > 3: + raise error.InputError(b'can only specify three labels.') + labels = [local, other, base] + labels[: len(overrides)] = overrides + local_input = simplemerge.MergeInput( + context.arbitraryfilectx(local), labels[0] + ) + other_input = simplemerge.MergeInput( + context.arbitraryfilectx(other), labels[1] + ) + base_input = simplemerge.MergeInput( + context.arbitraryfilectx(base), labels[2] ) + + quiet = opts.get(b'quiet') + allow_binary = opts.get(b'text') + ui = uimod.ui.load() + _verifytext(local_input, ui, quiet=quiet, allow_binary=allow_binary) + _verifytext(base_input, ui, quiet=quiet, allow_binary=allow_binary) + _verifytext(other_input, ui, quiet=quiet, allow_binary=allow_binary) + + merged_text, conflicts = simplemerge.simplemerge( + local_input, + base_input, + other_input, + mode, + allow_binary=allow_binary, + ) + if opts.get(b'print'): + ui.fout.write(merged_text) + else: + util.writefile(local, merged_text) + sys.exit(1 if conflicts else 0) except ParseError as e: e = stringutil.forcebytestr(e) procutil.stdout.write(b"%s: %s\n" % (sys.argv[0].encode('utf8'), e)) diff --git a/hgext/blackbox.py b/hgext/blackbox.py --- a/hgext/blackbox.py +++ b/hgext/blackbox.py @@ -36,7 +36,7 @@ Examples:: maxfiles = 3 [blackbox] - # Include nanoseconds in log entries with %f (see Python function + # Include microseconds in log entries with %f (see Python function # datetime.datetime.strftime) date-format = %Y-%m-%d @ %H:%M:%S.%f @@ -101,11 +101,7 @@ configitem( b'ignore', default=lambda: [b'chgserver', b'cmdserver', b'extension'], ) -configitem( - b'blackbox', - b'date-format', - default=b'%Y/%m/%d %H:%M:%S', -) +configitem(b'blackbox', b'date-format', default=b'') _lastlogger = loggingutil.proxylogger() @@ -138,7 +134,14 @@ class blackboxlogger(object): def _log(self, ui, event, msg, opts): default = ui.configdate(b'devel', b'default-date') - date = dateutil.datestr(default, ui.config(b'blackbox', b'date-format')) + dateformat = ui.config(b'blackbox', b'date-format') + if dateformat: + date = dateutil.datestr(default, dateformat) + else: + # We want to display milliseconds (more precision seems + # unnecessary). Since %.3f is not supported, use %f and truncate + # microseconds. + date = dateutil.datestr(default, b'%Y-%m-%d %H:%M:%S.%f')[:-3] user = procutil.getuser() pid = b'%d' % procutil.getpid() changed = b'' @@ -224,8 +227,14 @@ def blackbox(ui, repo, *revs, **opts): if count >= limit: break - # count the commands by matching lines like: 2013/01/23 19:13:36 root> - if re.match(br'^\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} .*> .*', line): + # count the commands by matching lines like: + # 2013/01/23 19:13:36 root> + # 2013/01/23 19:13:36 root (1234)> + # 2013/01/23 19:13:36 root @0000000000000000000000000000000000000000 (1234)> + # 2013-01-23 19:13:36.000 root @0000000000000000000000000000000000000000 (1234)> + if re.match( + br'^\d{4}[-/]\d{2}[-/]\d{2} \d{2}:\d{2}:\d{2}(.\d*)? .*> .*', line + ): count += 1 output.append(line) diff --git a/hgext/commitextras.py b/hgext/commitextras.py --- a/hgext/commitextras.py +++ b/hgext/commitextras.py @@ -65,23 +65,23 @@ def _commit(orig, ui, repo, *pats, **opt b"unable to parse '%s', should follow " b"KEY=VALUE format" ) - raise error.Abort(msg % raw) + raise error.InputError(msg % raw) k, v = raw.split(b'=', 1) if not k: msg = _(b"unable to parse '%s', keys can't be empty") - raise error.Abort(msg % raw) + raise error.InputError(msg % raw) if re.search(br'[^\w-]', k): msg = _( b"keys can only contain ascii letters, digits," b" '_' and '-'" ) - raise error.Abort(msg) + raise error.InputError(msg) if k in usedinternally: msg = _( b"key '%s' is used internally, can't be set " b"manually" ) - raise error.Abort(msg % k) + raise error.InputError(msg % k) inneropts['extra'][k] = v return super(repoextra, self).commit(*innerpats, **inneropts) diff --git a/hgext/convert/hg.py b/hgext/convert/hg.py --- a/hgext/convert/hg.py +++ b/hgext/convert/hg.py @@ -38,6 +38,7 @@ from mercurial import ( lock as lockmod, logcmdutil, merge as mergemod, + mergestate, phases, pycompat, util, @@ -241,7 +242,7 @@ class mercurial_sink(common.converter_si # If the file requires actual merging, abort. We don't have enough # context to resolve merges correctly. - if action in [b'm', b'dm', b'cd', b'dc']: + if action in mergestate.CONVERT_MERGE_ACTIONS: raise error.Abort( _( b"unable to convert merge commit " @@ -250,7 +251,7 @@ class mercurial_sink(common.converter_si ) % (file, p1ctx, p2ctx) ) - elif action == b'k': + elif action == mergestate.ACTION_KEEP: # 'keep' means nothing changed from p1 continue else: diff --git a/hgext/fix.py b/hgext/fix.py --- a/hgext/fix.py +++ b/hgext/fix.py @@ -149,7 +149,6 @@ from mercurial import ( mdiff, merge, mergestate as mergestatemod, - obsolete, pycompat, registrar, rewriteutil, @@ -463,8 +462,6 @@ def getrevstofix(ui, repo, opts): revs = set(logcmdutil.revrange(repo, opts[b'rev'])) if opts.get(b'working_dir'): revs.add(wdirrev) - for rev in revs: - checkfixablectx(ui, repo, repo[rev]) # Allow fixing only wdir() even if there's an unfinished operation if not (len(revs) == 1 and wdirrev in revs): cmdutil.checkunfinished(repo) @@ -481,16 +478,6 @@ def getrevstofix(ui, repo, opts): return revs -def checkfixablectx(ui, repo, ctx): - """Aborts if the revision shouldn't be replaced with a fixed one.""" - if ctx.obsolete(): - # It would be better to actually check if the revision has a successor. - if not obsolete.isenabled(repo, obsolete.allowdivergenceopt): - raise error.Abort( - b'fixing obsolete revision could cause divergence' - ) - - def pathstofix(ui, repo, pats, opts, match, basectxs, fixctx): """Returns the set of files that should be fixed in a context diff --git a/hgext/git/__init__.py b/hgext/git/__init__.py --- a/hgext/git/__init__.py +++ b/hgext/git/__init__.py @@ -51,6 +51,7 @@ getversion = gitutil.pygit2_version class gitstore(object): # store.basicstore): def __init__(self, path, vfstype): self.vfs = vfstype(path) + self.opener = self.vfs self.path = self.vfs.base self.createmode = store._calcmode(self.vfs) # above lines should go away in favor of: diff --git a/hgext/git/dirstate.py b/hgext/git/dirstate.py --- a/hgext/git/dirstate.py +++ b/hgext/git/dirstate.py @@ -257,7 +257,7 @@ class gitdirstate(object): if match(p): yield p - def set_clean(self, f, parentfiledata=None): + def set_clean(self, f, parentfiledata): """Mark a file normal and clean.""" # TODO: for now we just let libgit2 re-stat the file. We can # clearly do better. diff --git a/hgext/histedit.py b/hgext/histedit.py --- a/hgext/histedit.py +++ b/hgext/histedit.py @@ -667,7 +667,15 @@ def applychanges(ui, repo, ctx, opts): repo.ui.setconfig( b'ui', b'forcemerge', opts.get(b'tool', b''), b'histedit' ) - stats = mergemod.graft(repo, ctx, labels=[b'local', b'histedit']) + stats = mergemod.graft( + repo, + ctx, + labels=[ + b'already edited', + b'current change', + b'parent of current change', + ], + ) finally: repo.ui.setconfig(b'ui', b'forcemerge', b'', b'histedit') return stats @@ -1324,6 +1332,10 @@ pgup: prev page, space/pgdn: next page, d: drop, e: edit, f: fold, m: mess, p: pick, r: roll pgup/K: move patch up, pgdn/J: move patch down, c: commit, q: abort """ + if self.later_on_top: + help += b"Newer commits are shown above older commits.\n" + else: + help += b"Older commits are shown above newer commits.\n" return help.splitlines() def render_help(self, win): diff --git a/hgext/keyword.py b/hgext/keyword.py --- a/hgext/keyword.py +++ b/hgext/keyword.py @@ -116,6 +116,7 @@ from mercurial.utils import ( dateutil, stringutil, ) +from mercurial.dirstateutils import timestamp cmdtable = {} command = registrar.command(cmdtable) @@ -326,6 +327,7 @@ class kwtemplater(object): msg = _(b'overwriting %s expanding keywords\n') else: msg = _(b'overwriting %s shrinking keywords\n') + wctx = self.repo[None] for f in candidates: if self.restrict: data = self.repo.file(f).read(mf[f]) @@ -356,7 +358,12 @@ class kwtemplater(object): fp.write(data) fp.close() if kwcmd: - self.repo.dirstate.set_clean(f) + s = wctx[f].lstat() + mode = s.st_mode + size = s.st_size + mtime = timestamp.mtime_of(s) + cache_data = (mode, size, mtime) + self.repo.dirstate.set_clean(f, cache_data) elif self.postcommit: self.repo.dirstate.update_file_p1(f, p1_tracked=True) diff --git a/hgext/largefiles/lfutil.py b/hgext/largefiles/lfutil.py --- a/hgext/largefiles/lfutil.py +++ b/hgext/largefiles/lfutil.py @@ -32,6 +32,7 @@ from mercurial import ( vfs as vfsmod, ) from mercurial.utils import hashutil +from mercurial.dirstateutils import timestamp shortname = b'.hglf' shortnameslash = shortname + b'/' @@ -243,10 +244,11 @@ def openlfdirstate(ui, repo, create=True def lfdirstatestatus(lfdirstate, repo): pctx = repo[b'.'] match = matchmod.always() - unsure, s = lfdirstate.status( + unsure, s, mtime_boundary = lfdirstate.status( match, subrepos=[], ignored=False, clean=False, unknown=False ) modified, clean = s.modified, s.clean + wctx = repo[None] for lfile in unsure: try: fctx = pctx[standin(lfile)] @@ -256,7 +258,13 @@ def lfdirstatestatus(lfdirstate, repo): modified.append(lfile) else: clean.append(lfile) - lfdirstate.set_clean(lfile) + st = wctx[lfile].lstat() + mode = st.st_mode + size = st.st_size + mtime = timestamp.reliable_mtime_of(st, mtime_boundary) + if mtime is not None: + cache_data = (mode, size, mtime) + lfdirstate.set_clean(lfile, cache_data) return s @@ -663,7 +671,7 @@ def updatestandinsbymatch(repo, match): # large. lfdirstate = openlfdirstate(ui, repo) dirtymatch = matchmod.always() - unsure, s = lfdirstate.status( + unsure, s, mtime_boundary = lfdirstate.status( dirtymatch, subrepos=[], ignored=False, clean=False, unknown=False ) modifiedfiles = unsure + s.modified + s.added + s.removed diff --git a/hgext/largefiles/overrides.py b/hgext/largefiles/overrides.py --- a/hgext/largefiles/overrides.py +++ b/hgext/largefiles/overrides.py @@ -51,11 +51,17 @@ from . import ( storefactory, ) +ACTION_ADD = mergestatemod.ACTION_ADD +ACTION_DELETED_CHANGED = mergestatemod.ACTION_DELETED_CHANGED +ACTION_GET = mergestatemod.ACTION_GET +ACTION_KEEP = mergestatemod.ACTION_KEEP +ACTION_REMOVE = mergestatemod.ACTION_REMOVE + eh = exthelper.exthelper() lfstatus = lfutil.lfstatus -MERGE_ACTION_LARGEFILE_MARK_REMOVED = b'lfmr' +MERGE_ACTION_LARGEFILE_MARK_REMOVED = mergestatemod.MergeAction('lfmr') # -- Utility functions: commonly/repeatedly needed functionality --------------- @@ -563,8 +569,9 @@ def overridecalculateupdates( standin = lfutil.standin(lfile) (lm, largs, lmsg) = mresult.getfile(lfile, (None, None, None)) (sm, sargs, smsg) = mresult.getfile(standin, (None, None, None)) - if sm in (b'g', b'dc') and lm != b'r': - if sm == b'dc': + + if sm in (ACTION_GET, ACTION_DELETED_CHANGED) and lm != ACTION_REMOVE: + if sm == ACTION_DELETED_CHANGED: f1, f2, fa, move, anc = sargs sargs = (p2[f2].flags(), False) # Case 1: normal file in the working copy, largefile in @@ -578,26 +585,28 @@ def overridecalculateupdates( % lfile ) if repo.ui.promptchoice(usermsg, 0) == 0: # pick remote largefile - mresult.addfile(lfile, b'r', None, b'replaced by standin') - mresult.addfile(standin, b'g', sargs, b'replaces standin') + mresult.addfile( + lfile, ACTION_REMOVE, None, b'replaced by standin' + ) + mresult.addfile(standin, ACTION_GET, sargs, b'replaces standin') else: # keep local normal file - mresult.addfile(lfile, b'k', None, b'replaces standin') + mresult.addfile(lfile, ACTION_KEEP, None, b'replaces standin') if branchmerge: mresult.addfile( standin, - b'k', + ACTION_KEEP, None, b'replaced by non-standin', ) else: mresult.addfile( standin, - b'r', + ACTION_REMOVE, None, b'replaced by non-standin', ) - elif lm in (b'g', b'dc') and sm != b'r': - if lm == b'dc': + if lm in (ACTION_GET, ACTION_DELETED_CHANGED) and sm != ACTION_REMOVE: + if lm == ACTION_DELETED_CHANGED: f1, f2, fa, move, anc = largs largs = (p2[f2].flags(), False) # Case 2: largefile in the working copy, normal file in @@ -615,11 +624,13 @@ def overridecalculateupdates( # largefile can be restored from standin safely mresult.addfile( lfile, - b'k', + ACTION_KEEP, None, b'replaced by standin', ) - mresult.addfile(standin, b'k', None, b'replaces standin') + mresult.addfile( + standin, ACTION_KEEP, None, b'replaces standin' + ) else: # "lfile" should be marked as "removed" without # removal of itself @@ -631,12 +642,12 @@ def overridecalculateupdates( ) # linear-merge should treat this largefile as 're-added' - mresult.addfile(standin, b'a', None, b'keep standin') + mresult.addfile(standin, ACTION_ADD, None, b'keep standin') else: # pick remote normal file - mresult.addfile(lfile, b'g', largs, b'replaces standin') + mresult.addfile(lfile, ACTION_GET, largs, b'replaces standin') mresult.addfile( standin, - b'r', + ACTION_REMOVE, None, b'replaced by non-standin', ) @@ -666,14 +677,12 @@ def mergerecordupdates(orig, repo, actio # Override filemerge to prompt the user about how they wish to merge # largefiles. This will handle identical edits without prompting the user. -@eh.wrapfunction(filemerge, b'_filemerge') +@eh.wrapfunction(filemerge, b'filemerge') def overridefilemerge( - origfn, premerge, repo, wctx, mynode, orig, fcd, fco, fca, labels=None + origfn, repo, wctx, mynode, orig, fcd, fco, fca, labels=None ): if not lfutil.isstandin(orig) or fcd.isabsent() or fco.isabsent(): - return origfn( - premerge, repo, wctx, mynode, orig, fcd, fco, fca, labels=labels - ) + return origfn(repo, wctx, mynode, orig, fcd, fco, fca, labels=labels) ahash = lfutil.readasstandin(fca).lower() dhash = lfutil.readasstandin(fcd).lower() @@ -697,7 +706,7 @@ def overridefilemerge( ) ): repo.wwrite(fcd.path(), fco.data(), fco.flags()) - return True, 0, False + return 0, False @eh.wrapfunction(copiesmod, b'pathcopies') @@ -1519,7 +1528,7 @@ def scmutiladdremove(orig, repo, matcher return orig(repo, matcher, prefix, uipathfn, opts) # Get the list of missing largefiles so we can remove them lfdirstate = lfutil.openlfdirstate(repo.ui, repo) - unsure, s = lfdirstate.status( + unsure, s, mtime_boundary = lfdirstate.status( matchmod.always(), subrepos=[], ignored=False, @@ -1746,7 +1755,7 @@ def mergeupdate(orig, repo, node, branch # (*1) deprecated, but used internally (e.g: "rebase --collapse") lfdirstate = lfutil.openlfdirstate(repo.ui, repo) - unsure, s = lfdirstate.status( + unsure, s, mtime_boundary = lfdirstate.status( matchmod.always(), subrepos=[], ignored=False, diff --git a/hgext/largefiles/reposetup.py b/hgext/largefiles/reposetup.py --- a/hgext/largefiles/reposetup.py +++ b/hgext/largefiles/reposetup.py @@ -22,6 +22,8 @@ from mercurial import ( util, ) +from mercurial.dirstateutils import timestamp + from . import ( lfcommands, lfutil, @@ -195,7 +197,7 @@ def reposetup(ui, repo): match._files = [f for f in match._files if sfindirstate(f)] # Don't waste time getting the ignored and unknown # files from lfdirstate - unsure, s = lfdirstate.status( + unsure, s, mtime_boundary = lfdirstate.status( match, subrepos=[], ignored=False, @@ -210,6 +212,7 @@ def reposetup(ui, repo): s.clean, ) if parentworking: + wctx = repo[None] for lfile in unsure: standin = lfutil.standin(lfile) if standin not in ctx1: @@ -222,7 +225,15 @@ def reposetup(ui, repo): else: if listclean: clean.append(lfile) - lfdirstate.set_clean(lfile) + s = wctx[lfile].lstat() + mode = s.st_mode + size = s.st_size + mtime = timestamp.reliable_mtime_of( + s, mtime_boundary + ) + if mtime is not None: + cache_data = (mode, size, mtime) + lfdirstate.set_clean(lfile, cache_data) else: tocheck = unsure + modified + added + clean modified, added, clean = [], [], [] @@ -444,11 +455,12 @@ def reposetup(ui, repo): repo.prepushoutgoinghooks.add(b"largefiles", prepushoutgoinghook) def checkrequireslfiles(ui, repo, **kwargs): - if b'largefiles' not in repo.requirements and any( - lfutil.shortname + b'/' in f[1] for f in repo.store.datafiles() - ): - repo.requirements.add(b'largefiles') - scmutil.writereporequirements(repo) + with repo.lock(): + if b'largefiles' not in repo.requirements and any( + lfutil.shortname + b'/' in f[1] for f in repo.store.datafiles() + ): + repo.requirements.add(b'largefiles') + scmutil.writereporequirements(repo) ui.setconfig( b'hooks', b'changegroup.lfiles', checkrequireslfiles, b'largefiles' diff --git a/hgext/lfs/__init__.py b/hgext/lfs/__init__.py --- a/hgext/lfs/__init__.py +++ b/hgext/lfs/__init__.py @@ -257,25 +257,28 @@ def _reposetup(ui, repo): if b'lfs' not in repo.requirements: def checkrequireslfs(ui, repo, **kwargs): - if b'lfs' in repo.requirements: - return 0 + with repo.lock(): + if b'lfs' in repo.requirements: + return 0 - last = kwargs.get('node_last') - if last: - s = repo.set(b'%n:%n', bin(kwargs['node']), bin(last)) - else: - s = repo.set(b'%n', bin(kwargs['node'])) - match = repo._storenarrowmatch - for ctx in s: - # TODO: is there a way to just walk the files in the commit? - if any( - ctx[f].islfs() for f in ctx.files() if f in ctx and match(f) - ): - repo.requirements.add(b'lfs') - repo.features.add(repository.REPO_FEATURE_LFS) - scmutil.writereporequirements(repo) - repo.prepushoutgoinghooks.add(b'lfs', wrapper.prepush) - break + last = kwargs.get('node_last') + if last: + s = repo.set(b'%n:%n', bin(kwargs['node']), bin(last)) + else: + s = repo.set(b'%n', bin(kwargs['node'])) + match = repo._storenarrowmatch + for ctx in s: + # TODO: is there a way to just walk the files in the commit? + if any( + ctx[f].islfs() + for f in ctx.files() + if f in ctx and match(f) + ): + repo.requirements.add(b'lfs') + repo.features.add(repository.REPO_FEATURE_LFS) + scmutil.writereporequirements(repo) + repo.prepushoutgoinghooks.add(b'lfs', wrapper.prepush) + break ui.setconfig(b'hooks', b'commit.lfs', checkrequireslfs, b'lfs') ui.setconfig( diff --git a/hgext/narrow/narrowdirstate.py b/hgext/narrow/narrowdirstate.py --- a/hgext/narrow/narrowdirstate.py +++ b/hgext/narrow/narrowdirstate.py @@ -38,8 +38,8 @@ def wrapdirstate(repo, dirstate): return super(narrowdirstate, self).normal(*args, **kwargs) @_editfunc - def set_tracked(self, *args): - return super(narrowdirstate, self).set_tracked(*args) + def set_tracked(self, *args, **kwargs): + return super(narrowdirstate, self).set_tracked(*args, **kwargs) @_editfunc def set_untracked(self, *args): diff --git a/hgext/notify.py b/hgext/notify.py --- a/hgext/notify.py +++ b/hgext/notify.py @@ -435,7 +435,10 @@ class notifier(object): if spec is None: subs.add(sub) continue - revs = self.repo.revs(b'%r and %d:', spec, ctx.rev()) + try: + revs = self.repo.revs(b'%r and %d:', spec, ctx.rev()) + except error.RepoLookupError: + continue if len(revs): subs.add(sub) continue diff --git a/hgext/rebase.py b/hgext/rebase.py --- a/hgext/rebase.py +++ b/hgext/rebase.py @@ -1544,7 +1544,7 @@ def rebasenode(repo, rev, p1, p2, base, force=True, ancestor=base, mergeancestor=mergeancestor, - labels=[b'dest', b'source'], + labels=[b'dest', b'source', b'parent of source'], wc=wctx, ) wctx.setparents(p1ctx.node(), repo[p2].node()) diff --git a/hgext/remotefilelog/README.md b/hgext/remotefilelog/README.md --- a/hgext/remotefilelog/README.md +++ b/hgext/remotefilelog/README.md @@ -88,7 +88,9 @@ 3. remotefilelog only works with ssh bas 4. Tags are not supported in completely shallow repos. If you use tags in your repo you will have to specify `excludepattern=.hgtags` in your client configuration to ensure that file is downloaded. The include/excludepattern settings are experimental at the moment and have yet to be deployed in a production environment. -5. A few commands will be slower. `hg log ` will be much slower since it has to walk the entire commit history instead of just the filelog. Use `hg log -f ` instead, which remains very fast. +5. Similarly, subrepositories should not be used with completely shallow repos. Use `excludepattern=.hgsub*` in your client configuration to ensure that the files are downloaded. + +6. A few commands will be slower. `hg log ` will be much slower since it has to walk the entire commit history instead of just the filelog. Use `hg log -f ` instead, which remains very fast. Contributing ============ diff --git a/hgext/remotefilelog/__init__.py b/hgext/remotefilelog/__init__.py --- a/hgext/remotefilelog/__init__.py +++ b/hgext/remotefilelog/__init__.py @@ -520,7 +520,7 @@ def checkunknownfiles(orig, repo, wctx, # Prefetch files before status attempts to look at their size and contents -def checklookup(orig, self, files): +def checklookup(orig, self, files, mtime_boundary): repo = self._repo if isenabled(repo): prefetchfiles = [] @@ -530,7 +530,7 @@ def checklookup(orig, self, files): prefetchfiles.append((f, hex(parent.filenode(f)))) # batch fetch the needed files from the server repo.fileservice.prefetch(prefetchfiles) - return orig(self, files) + return orig(self, files, mtime_boundary) # Prefetch the logic that compares added and removed files for renames diff --git a/hgext/remotefilelog/remotefilelog.py b/hgext/remotefilelog/remotefilelog.py --- a/hgext/remotefilelog/remotefilelog.py +++ b/hgext/remotefilelog/remotefilelog.py @@ -18,7 +18,6 @@ from mercurial import ( mdiff, pycompat, revlog, - util, ) from mercurial.utils import storageutil from mercurial.revlogutils import flagutil @@ -245,11 +244,11 @@ class remotefilelog(object): __bool__ = __nonzero__ def __len__(self): - if self.filename == b'.hgtags': - # The length of .hgtags is used to fast path tag checking. - # remotefilelog doesn't support .hgtags since the entire .hgtags - # history is needed. Use the excludepattern setting to make - # .hgtags a normal filelog. + if self.filename in (b'.hgtags', b'.hgsub', b'.hgsubstate'): + # Global tag and subrepository support require access to the + # file history for various performance sensitive operations. + # excludepattern should be used for repositories depending on + # those features to fallback to regular filelog. return 0 raise RuntimeError(b"len not supported") @@ -360,17 +359,6 @@ class remotefilelog(object): ) return rev - def _processflags(self, text, flags, operation, raw=False): - """deprecated entry point to access flag processors""" - msg = b'_processflag(...) use the specialized variant' - util.nouideprecwarn(msg, b'5.2', stacklevel=2) - if raw: - return text, flagutil.processflagsraw(self, text, flags) - elif operation == b'read': - return flagutil.processflagsread(self, text, flags) - else: # write operation - return flagutil.processflagswrite(self, text, flags) - def revision(self, node, raw=False): """returns the revlog contents at this node. this includes the meta data traditionally included in file revlogs. diff --git a/hgext/sparse.py b/hgext/sparse.py --- a/hgext/sparse.py +++ b/hgext/sparse.py @@ -76,6 +76,7 @@ from __future__ import absolute_import from mercurial.i18n import _ from mercurial.pycompat import setattr from mercurial import ( + cmdutil, commands, dirstate, error, @@ -153,22 +154,11 @@ def _setuplog(ui): def _clonesparsecmd(orig, ui, repo, *args, **opts): - include_pat = opts.get('include') - exclude_pat = opts.get('exclude') - enableprofile_pat = opts.get('enable_profile') + include = opts.get('include') + exclude = opts.get('exclude') + enableprofile = opts.get('enable_profile') narrow_pat = opts.get('narrow') - include = exclude = enableprofile = False - if include_pat: - pat = include_pat - include = True - if exclude_pat: - pat = exclude_pat - exclude = True - if enableprofile_pat: - pat = enableprofile_pat - enableprofile = True - if sum([include, exclude, enableprofile]) > 1: - raise error.Abort(_(b"too many flags specified.")) + # if --narrow is passed, it means they are includes and excludes for narrow # clone if not narrow_pat and (include or exclude or enableprofile): @@ -176,7 +166,6 @@ def _clonesparsecmd(orig, ui, repo, *arg def clonesparse(orig, ctx, *args, **kwargs): sparse.updateconfig( ctx.repo().unfiltered(), - pat, {}, include=include, exclude=exclude, @@ -214,7 +203,7 @@ def _setupadd(ui): for pat in pats: dirname, basename = util.split(pat) dirs.add(dirname) - sparse.updateconfig(repo, list(dirs), opts, include=True) + sparse.updateconfig(repo, opts, include=list(dirs)) return orig(ui, repo, *pats, **opts) extensions.wrapcommand(commands.table, b'add', _add) @@ -286,18 +275,54 @@ def _setupdirstate(ui): @command( b'debugsparse', [ - (b'I', b'include', False, _(b'include files in the sparse checkout')), - (b'X', b'exclude', False, _(b'exclude files in the sparse checkout')), - (b'd', b'delete', False, _(b'delete an include/exclude rule')), + ( + b'I', + b'include', + [], + _(b'include files in the sparse checkout'), + _(b'PATTERN'), + ), + ( + b'X', + b'exclude', + [], + _(b'exclude files in the sparse checkout'), + _(b'PATTERN'), + ), + ( + b'd', + b'delete', + [], + _(b'delete an include/exclude rule'), + _(b'PATTERN'), + ), ( b'f', b'force', False, _(b'allow changing rules even with pending changes'), ), - (b'', b'enable-profile', False, _(b'enables the specified profile')), - (b'', b'disable-profile', False, _(b'disables the specified profile')), - (b'', b'import-rules', False, _(b'imports rules from a file')), + ( + b'', + b'enable-profile', + [], + _(b'enables the specified profile'), + _(b'PATTERN'), + ), + ( + b'', + b'disable-profile', + [], + _(b'disables the specified profile'), + _(b'PATTERN'), + ), + ( + b'', + b'import-rules', + [], + _(b'imports rules from a file'), + _(b'PATTERN'), + ), (b'', b'clear-rules', False, _(b'clears local include/exclude rules')), ( b'', @@ -308,10 +333,10 @@ def _setupdirstate(ui): (b'', b'reset', False, _(b'makes the repo full again')), ] + commands.templateopts, - _(b'[--OPTION] PATTERN...'), + _(b'[--OPTION]'), helpbasic=True, ) -def debugsparse(ui, repo, *pats, **opts): +def debugsparse(ui, repo, **opts): """make the current checkout sparse, or edit the existing checkout The sparse command is used to make the current checkout sparse. @@ -363,19 +388,13 @@ def debugsparse(ui, repo, *pats, **opts) delete = opts.get(b'delete') refresh = opts.get(b'refresh') reset = opts.get(b'reset') - count = sum( - [ - include, - exclude, - enableprofile, - disableprofile, - delete, - importrules, - refresh, - clearrules, - reset, - ] + action = cmdutil.check_at_most_one_arg( + opts, b'import_rules', b'clear_rules', b'refresh' ) + updateconfig = bool( + include or exclude or delete or reset or enableprofile or disableprofile + ) + count = sum([updateconfig, bool(action)]) if count > 1: raise error.Abort(_(b"too many flags specified")) @@ -397,10 +416,9 @@ def debugsparse(ui, repo, *pats, **opts) ) ) - if include or exclude or delete or reset or enableprofile or disableprofile: + if updateconfig: sparse.updateconfig( repo, - pats, opts, include=include, exclude=exclude, @@ -412,7 +430,7 @@ def debugsparse(ui, repo, *pats, **opts) ) if importrules: - sparse.importfromfiles(repo, opts, pats, force=force) + sparse.importfromfiles(repo, opts, importrules, force=force) if clearrules: sparse.clearrules(repo, force=force) diff --git a/hgext/win32text.py b/hgext/win32text.py --- a/hgext/win32text.py +++ b/hgext/win32text.py @@ -47,6 +47,8 @@ import re from mercurial.i18n import _ from mercurial.node import short from mercurial import ( + cmdutil, + extensions, pycompat, registrar, ) @@ -215,6 +217,23 @@ def reposetup(ui, repo): repo.adddatafilter(name, fn) +def wrap_revert(orig, repo, ctx, names, uipathfn, actions, *args, **kwargs): + # reset dirstate cache for file we touch + ds = repo.dirstate + with ds.parentchange(): + for filename in actions[b'revert'][0]: + entry = ds.get_entry(filename) + if entry is not None: + if entry.p1_tracked: + ds.update_file( + filename, + entry.tracked, + p1_tracked=True, + p2_info=entry.p2_info, + ) + return orig(repo, ctx, names, uipathfn, actions, *args, **kwargs) + + def extsetup(ui): # deprecated config: win32text.warn if ui.configbool(b'win32text', b'warn'): @@ -224,3 +243,4 @@ def extsetup(ui): b"https://mercurial-scm.org/wiki/Win32TextExtension\n" ) ) + extensions.wrapfunction(cmdutil, '_performrevert', wrap_revert) diff --git a/mercurial/bookmarks.py b/mercurial/bookmarks.py --- a/mercurial/bookmarks.py +++ b/mercurial/bookmarks.py @@ -22,6 +22,7 @@ from . import ( error, obsutil, pycompat, + requirements, scmutil, txnutil, util, @@ -36,11 +37,9 @@ from .utils import ( # custom styles activebookmarklabel = b'bookmarks.active bookmarks.current' -BOOKMARKS_IN_STORE_REQUIREMENT = b'bookmarksinstore' - def bookmarksinstore(repo): - return BOOKMARKS_IN_STORE_REQUIREMENT in repo.requirements + return requirements.BOOKMARKS_IN_STORE_REQUIREMENT in repo.requirements def bookmarksvfs(repo): @@ -213,7 +212,11 @@ class bmstore(object): The transaction is then responsible for updating the file content.""" location = b'' if bookmarksinstore(self._repo) else b'plain' tr.addfilegenerator( - b'bookmarks', (b'bookmarks',), self._write, location=location + b'bookmarks', + (b'bookmarks',), + self._write, + location=location, + post_finalize=True, ) tr.hookargs[b'bookmark_moved'] = b'1' diff --git a/mercurial/branchmap.py b/mercurial/branchmap.py --- a/mercurial/branchmap.py +++ b/mercurial/branchmap.py @@ -17,6 +17,7 @@ from .node import ( from . import ( encoding, error, + obsolete, pycompat, scmutil, util, @@ -184,7 +185,7 @@ class branchcache(object): The first line is used to check if the cache is still valid. If the branch cache is for a filtered repo view, an optional third hash is - included that hashes the hashes of all filtered revisions. + included that hashes the hashes of all filtered and obsolete revisions. The open/closed state is represented by a single letter 'o' or 'c'. This field can be used to avoid changelog reads when determining if a @@ -351,16 +352,25 @@ class branchcache(object): return filename def validfor(self, repo): - """Is the cache content valid regarding a repo + """check that cache contents are valid for (a subset of) this repo - - False when cached tipnode is unknown or if we detect a strip. - - True when cache is up to date or a subset of current repo.""" + - False when the order of changesets changed or if we detect a strip. + - True when cache is up-to-date for the current repo or its subset.""" try: - return (self.tipnode == repo.changelog.node(self.tiprev)) and ( - self.filteredhash == scmutil.filteredhash(repo, self.tiprev) - ) + node = repo.changelog.node(self.tiprev) except IndexError: + # changesets were stripped and now we don't even have enough to + # find tiprev return False + if self.tipnode != node: + # tiprev doesn't correspond to tipnode: repo was stripped, or this + # repo has a different order of changesets + return False + tiphash = scmutil.filteredhash(repo, self.tiprev, needobsolete=True) + # hashes don't match if this repo view has a different set of filtered + # revisions (e.g. due to phase changes) or obsolete revisions (e.g. + # history was rewritten) + return self.filteredhash == tiphash def _branchtip(self, heads): """Return tuple with last open head in heads and false, @@ -478,6 +488,9 @@ class branchcache(object): # use the faster unfiltered parent accessor. parentrevs = repo.unfiltered().changelog.parentrevs + # Faster than using ctx.obsolete() + obsrevs = obsolete.getrevs(repo, b'obsolete') + for branch, newheadrevs in pycompat.iteritems(newbranches): # For every branch, compute the new branchheads. # A branchhead is a revision such that no descendant is on @@ -514,10 +527,15 @@ class branchcache(object): # checks can be skipped. Otherwise, the ancestors of the # "uncertain" set are removed from branchheads. # This computation is heavy and avoided if at all possible. - bheads = self._entries.setdefault(branch, []) + bheads = self._entries.get(branch, []) bheadset = {cl.rev(node) for node in bheads} uncertain = set() for newrev in sorted(newheadrevs): + if newrev in obsrevs: + # We ignore obsolete changesets as they shouldn't be + # considered heads. + continue + if not bheadset: bheadset.add(newrev) continue @@ -525,13 +543,22 @@ class branchcache(object): parents = [p for p in parentrevs(newrev) if p != nullrev] samebranch = set() otherbranch = set() + obsparents = set() for p in parents: - if p in bheadset or getbranchinfo(p)[0] == branch: + if p in obsrevs: + # We ignored this obsolete changeset earlier, but now + # that it has non-ignored children, we need to make + # sure their ancestors are not considered heads. To + # achieve that, we will simply treat this obsolete + # changeset as a parent from other branch. + obsparents.add(p) + elif p in bheadset or getbranchinfo(p)[0] == branch: samebranch.add(p) else: otherbranch.add(p) - if otherbranch and not (len(bheadset) == len(samebranch) == 1): + if not (len(bheadset) == len(samebranch) == 1): uncertain.update(otherbranch) + uncertain.update(obsparents) bheadset.difference_update(samebranch) bheadset.add(newrev) @@ -540,11 +567,12 @@ class branchcache(object): topoheads = set(cl.headrevs()) if bheadset - topoheads: floorrev = min(bheadset) - ancestors = set(cl.ancestors(newheadrevs, floorrev)) - bheadset -= ancestors - bheadrevs = sorted(bheadset) - self[branch] = [cl.node(rev) for rev in bheadrevs] - tiprev = bheadrevs[-1] + if floorrev <= max(uncertain): + ancestors = set(cl.ancestors(uncertain, floorrev)) + bheadset -= ancestors + if bheadset: + self[branch] = [cl.node(rev) for rev in sorted(bheadset)] + tiprev = max(newheadrevs) if tiprev > ntiprev: ntiprev = tiprev @@ -553,15 +581,24 @@ class branchcache(object): self.tipnode = cl.node(ntiprev) if not self.validfor(repo): - # cache key are not valid anymore + # old cache key is now invalid for the repo, but we've just updated + # the cache and we assume it's valid, so let's make the cache key + # valid as well by recomputing it from the cached data self.tipnode = repo.nullid self.tiprev = nullrev for heads in self.iterheads(): + if not heads: + # all revisions on a branch are obsolete + continue + # note: tiprev is not necessarily the tip revision of repo, + # because the tip could be obsolete (i.e. not a head) tiprev = max(cl.rev(node) for node in heads) if tiprev > self.tiprev: self.tipnode = cl.node(tiprev) self.tiprev = tiprev - self.filteredhash = scmutil.filteredhash(repo, self.tiprev) + self.filteredhash = scmutil.filteredhash( + repo, self.tiprev, needobsolete=True + ) duration = util.timer() - starttime repo.ui.log( diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py --- a/mercurial/bundle2.py +++ b/mercurial/bundle2.py @@ -1886,7 +1886,8 @@ def addpartbundlestream2(bundler, repo, filecount, bytecount, it = streamclone.generatev2( repo, includepats, excludepats, includeobsmarkers ) - requirements = _formatrequirementsspec(repo.requirements) + requirements = streamclone.streamed_requirements(repo) + requirements = _formatrequirementsspec(requirements) part = bundler.newpart(b'stream2', data=it) part.addparam(b'bytecount', b'%d' % bytecount, mandatory=True) part.addparam(b'filecount', b'%d' % filecount, mandatory=True) @@ -2419,7 +2420,7 @@ def handlebookmark(op, inpart): op.records.add(b'bookmarks', record) else: raise error.ProgrammingError( - b'unkown bookmark mode: %s' % bookmarksmode + b'unknown bookmark mode: %s' % bookmarksmode ) diff --git a/mercurial/bundlecaches.py b/mercurial/bundlecaches.py --- a/mercurial/bundlecaches.py +++ b/mercurial/bundlecaches.py @@ -195,7 +195,7 @@ def parsebundlespec(repo, spec, strict=T # repo supports and error if the bundle isn't compatible. if version == b'packed1' and b'requirements' in params: requirements = set(params[b'requirements'].split(b',')) - missingreqs = requirements - repo.supportedformats + missingreqs = requirements - requirementsmod.STREAM_FIXED_REQUIREMENTS if missingreqs: raise error.UnsupportedBundleSpecification( _(b'missing support for repository features: %s') diff --git a/mercurial/cext/parsers.c b/mercurial/cext/parsers.c --- a/mercurial/cext/parsers.c +++ b/mercurial/cext/parsers.c @@ -61,11 +61,13 @@ static PyObject *dirstate_item_new(PyTyp int p2_info; int has_meaningful_data; int has_meaningful_mtime; + int mtime_second_ambiguous; int mode; int size; int mtime_s; int mtime_ns; PyObject *parentfiledata; + PyObject *mtime; PyObject *fallback_exec; PyObject *fallback_symlink; static char *keywords_name[] = { @@ -78,6 +80,7 @@ static PyObject *dirstate_item_new(PyTyp p2_info = 0; has_meaningful_mtime = 1; has_meaningful_data = 1; + mtime_second_ambiguous = 0; parentfiledata = Py_None; fallback_exec = Py_None; fallback_symlink = Py_None; @@ -118,10 +121,18 @@ static PyObject *dirstate_item_new(PyTyp } if (parentfiledata != Py_None) { - if (!PyArg_ParseTuple(parentfiledata, "ii(ii)", &mode, &size, - &mtime_s, &mtime_ns)) { + if (!PyArg_ParseTuple(parentfiledata, "iiO", &mode, &size, + &mtime)) { return NULL; } + if (mtime != Py_None) { + if (!PyArg_ParseTuple(mtime, "iii", &mtime_s, &mtime_ns, + &mtime_second_ambiguous)) { + return NULL; + } + } else { + has_meaningful_mtime = 0; + } } else { has_meaningful_data = 0; has_meaningful_mtime = 0; @@ -130,6 +141,9 @@ static PyObject *dirstate_item_new(PyTyp t->flags |= dirstate_flag_has_meaningful_data; t->mode = mode; t->size = size; + if (mtime_second_ambiguous) { + t->flags |= dirstate_flag_mtime_second_ambiguous; + } } else { t->mode = 0; t->size = 0; @@ -255,7 +269,8 @@ static inline int dirstate_item_c_v1_mti } else if (!(self->flags & dirstate_flag_has_mtime) || !(self->flags & dirstate_flag_p1_tracked) || !(self->flags & dirstate_flag_wc_tracked) || - (self->flags & dirstate_flag_p2_info)) { + (self->flags & dirstate_flag_p2_info) || + (self->flags & dirstate_flag_mtime_second_ambiguous)) { return ambiguous_time; } else { return self->mtime_s; @@ -311,33 +326,30 @@ static PyObject *dirstate_item_v1_mtime( return PyInt_FromLong(dirstate_item_c_v1_mtime(self)); }; -static PyObject *dirstate_item_need_delay(dirstateItemObject *self, - PyObject *now) -{ - int now_s; - int now_ns; - if (!PyArg_ParseTuple(now, "ii", &now_s, &now_ns)) { - return NULL; - } - if (dirstate_item_c_v1_state(self) == 'n' && self->mtime_s == now_s) { - Py_RETURN_TRUE; - } else { - Py_RETURN_FALSE; - } -}; - static PyObject *dirstate_item_mtime_likely_equal_to(dirstateItemObject *self, PyObject *other) { int other_s; int other_ns; - if (!PyArg_ParseTuple(other, "ii", &other_s, &other_ns)) { + int other_second_ambiguous; + if (!PyArg_ParseTuple(other, "iii", &other_s, &other_ns, + &other_second_ambiguous)) { return NULL; } - if ((self->flags & dirstate_flag_has_mtime) && - self->mtime_s == other_s && - (self->mtime_ns == other_ns || self->mtime_ns == 0 || - other_ns == 0)) { + if (!(self->flags & dirstate_flag_has_mtime)) { + Py_RETURN_FALSE; + } + if (self->mtime_s != other_s) { + Py_RETURN_FALSE; + } + if (self->mtime_ns == 0 || other_ns == 0) { + if (self->flags & dirstate_flag_mtime_second_ambiguous) { + Py_RETURN_FALSE; + } else { + Py_RETURN_TRUE; + } + } + if (self->mtime_ns == other_ns) { Py_RETURN_TRUE; } else { Py_RETURN_FALSE; @@ -438,14 +450,6 @@ static PyObject *dirstate_item_from_v2_m dirstate_flag_has_meaningful_data | dirstate_flag_has_mtime); } - if (t->flags & dirstate_flag_mtime_second_ambiguous) { - /* The current code is not able to do the more subtle comparison - * that the MTIME_SECOND_AMBIGUOUS requires. So we ignore the - * mtime */ - t->flags &= ~(dirstate_flag_mtime_second_ambiguous | - dirstate_flag_has_meaningful_data | - dirstate_flag_has_mtime); - } t->mode = 0; if (t->flags & dirstate_flag_has_meaningful_data) { if (t->flags & dirstate_flag_mode_exec_perm) { @@ -474,14 +478,28 @@ static PyObject *dirstate_item_set_possi static PyObject *dirstate_item_set_clean(dirstateItemObject *self, PyObject *args) { - int size, mode, mtime_s, mtime_ns; - if (!PyArg_ParseTuple(args, "ii(ii)", &mode, &size, &mtime_s, - &mtime_ns)) { + int size, mode, mtime_s, mtime_ns, mtime_second_ambiguous; + PyObject *mtime; + mtime_s = 0; + mtime_ns = 0; + mtime_second_ambiguous = 0; + if (!PyArg_ParseTuple(args, "iiO", &mode, &size, &mtime)) { return NULL; } + if (mtime != Py_None) { + if (!PyArg_ParseTuple(mtime, "iii", &mtime_s, &mtime_ns, + &mtime_second_ambiguous)) { + return NULL; + } + } else { + self->flags &= ~dirstate_flag_has_mtime; + } self->flags = dirstate_flag_wc_tracked | dirstate_flag_p1_tracked | dirstate_flag_has_meaningful_data | dirstate_flag_has_mtime; + if (mtime_second_ambiguous) { + self->flags |= dirstate_flag_mtime_second_ambiguous; + } self->mode = mode; self->size = size; self->mtime_s = mtime_s; @@ -530,8 +548,6 @@ static PyMethodDef dirstate_item_methods "return a \"size\" suitable for v1 serialization"}, {"v1_mtime", (PyCFunction)dirstate_item_v1_mtime, METH_NOARGS, "return a \"mtime\" suitable for v1 serialization"}, - {"need_delay", (PyCFunction)dirstate_item_need_delay, METH_O, - "True if the stored mtime would be ambiguous with the current time"}, {"mtime_likely_equal_to", (PyCFunction)dirstate_item_mtime_likely_equal_to, METH_O, "True if the stored mtime is likely equal to the given mtime"}, {"from_v1_data", (PyCFunction)dirstate_item_from_v1_meth, @@ -904,12 +920,9 @@ static PyObject *pack_dirstate(PyObject Py_ssize_t nbytes, pos, l; PyObject *k, *v = NULL, *pn; char *p, *s; - int now_s; - int now_ns; - if (!PyArg_ParseTuple(args, "O!O!O!(ii):pack_dirstate", &PyDict_Type, - &map, &PyDict_Type, ©map, &PyTuple_Type, &pl, - &now_s, &now_ns)) { + if (!PyArg_ParseTuple(args, "O!O!O!:pack_dirstate", &PyDict_Type, &map, + &PyDict_Type, ©map, &PyTuple_Type, &pl)) { return NULL; } @@ -978,21 +991,6 @@ static PyObject *pack_dirstate(PyObject mode = dirstate_item_c_v1_mode(tuple); size = dirstate_item_c_v1_size(tuple); mtime = dirstate_item_c_v1_mtime(tuple); - if (state == 'n' && tuple->mtime_s == now_s) { - /* See pure/parsers.py:pack_dirstate for why we do - * this. */ - mtime = -1; - mtime_unset = (PyObject *)dirstate_item_from_v1_data( - state, mode, size, mtime); - if (!mtime_unset) { - goto bail; - } - if (PyDict_SetItem(map, k, mtime_unset) == -1) { - goto bail; - } - Py_DECREF(mtime_unset); - mtime_unset = NULL; - } *p++ = state; putbe32((uint32_t)mode, p); putbe32((uint32_t)size, p + 4); diff --git a/mercurial/cext/revlog.c b/mercurial/cext/revlog.c --- a/mercurial/cext/revlog.c +++ b/mercurial/cext/revlog.c @@ -103,8 +103,7 @@ struct indexObjectStruct { */ long rust_ext_compat; /* compatibility with being used in rust extensions */ - char format_version; /* size of index headers. Differs in v1 v.s. v2 - format */ + long format_version; /* format version selector (format_*) */ }; static Py_ssize_t index_length(const indexObject *self) @@ -120,9 +119,11 @@ static Py_ssize_t inline_scan(indexObjec static int index_find_node(indexObject *self, const char *node); #if LONG_MAX == 0x7fffffffL -static const char *const tuple_format = PY23("Kiiiiiis#KiBB", "Kiiiiiiy#KiBB"); +static const char *const tuple_format = + PY23("Kiiiiiis#KiBBi", "Kiiiiiiy#KiBBi"); #else -static const char *const tuple_format = PY23("kiiiiiis#kiBB", "kiiiiiiy#kiBB"); +static const char *const tuple_format = + PY23("kiiiiiis#kiBBi", "kiiiiiiy#kiBBi"); #endif /* A RevlogNG v1 index entry is 64 bytes long. */ @@ -131,10 +132,54 @@ static const long v1_entry_size = 64; /* A Revlogv2 index entry is 96 bytes long. */ static const long v2_entry_size = 96; -static const long format_v1 = 1; /* Internal only, could be any number */ -static const long format_v2 = 2; /* Internal only, could be any number */ +/* A Changelogv2 index entry is 96 bytes long. */ +static const long cl2_entry_size = 96; + +/* Internal format version. + * Must match their counterparts in revlogutils/constants.py */ +static const long format_v1 = 1; /* constants.py: REVLOGV1 */ +static const long format_v2 = 0xDEAD; /* constants.py: REVLOGV2 */ +static const long format_cl2 = 0xD34D; /* constants.py: CHANGELOGV2 */ + +static const long entry_v1_offset_high = 0; +static const long entry_v1_offset_offset_flags = 4; +static const long entry_v1_offset_comp_len = 8; +static const long entry_v1_offset_uncomp_len = 12; +static const long entry_v1_offset_base_rev = 16; +static const long entry_v1_offset_link_rev = 20; +static const long entry_v1_offset_parent_1 = 24; +static const long entry_v1_offset_parent_2 = 28; +static const long entry_v1_offset_node_id = 32; + +static const long entry_v2_offset_high = 0; +static const long entry_v2_offset_offset_flags = 4; +static const long entry_v2_offset_comp_len = 8; +static const long entry_v2_offset_uncomp_len = 12; +static const long entry_v2_offset_base_rev = 16; +static const long entry_v2_offset_link_rev = 20; +static const long entry_v2_offset_parent_1 = 24; +static const long entry_v2_offset_parent_2 = 28; +static const long entry_v2_offset_node_id = 32; +static const long entry_v2_offset_sidedata_offset = 64; +static const long entry_v2_offset_sidedata_comp_len = 72; +static const long entry_v2_offset_all_comp_mode = 76; +/* next free offset: 77 */ + +static const long entry_cl2_offset_high = 0; +static const long entry_cl2_offset_offset_flags = 4; +static const long entry_cl2_offset_comp_len = 8; +static const long entry_cl2_offset_uncomp_len = 12; +static const long entry_cl2_offset_parent_1 = 16; +static const long entry_cl2_offset_parent_2 = 20; +static const long entry_cl2_offset_node_id = 24; +static const long entry_cl2_offset_sidedata_offset = 56; +static const long entry_cl2_offset_sidedata_comp_len = 64; +static const long entry_cl2_offset_all_comp_mode = 68; +static const long entry_cl2_offset_rank = 69; +/* next free offset: 73 */ static const char comp_mode_inline = 2; +static const char rank_unknown = -1; static void raise_revlog_error(void) { @@ -203,8 +248,19 @@ static inline int index_get_parents(inde { const char *data = index_deref(self, rev); - ps[0] = getbe32(data + 24); - ps[1] = getbe32(data + 28); + if (self->format_version == format_v1) { + ps[0] = getbe32(data + entry_v1_offset_parent_1); + ps[1] = getbe32(data + entry_v1_offset_parent_2); + } else if (self->format_version == format_v2) { + ps[0] = getbe32(data + entry_v2_offset_parent_1); + ps[1] = getbe32(data + entry_v2_offset_parent_2); + } else if (self->format_version == format_cl2) { + ps[0] = getbe32(data + entry_cl2_offset_parent_1); + ps[1] = getbe32(data + entry_cl2_offset_parent_2); + } else { + raise_revlog_error(); + return -1; + } /* If index file is corrupted, ps[] may point to invalid revisions. So * there is a risk of buffer overflow to trust them unconditionally. */ @@ -251,14 +307,36 @@ static inline int64_t index_get_start(in return 0; data = index_deref(self, rev); - offset = getbe32(data + 4); - if (rev == 0) { - /* mask out version number for the first entry */ - offset &= 0xFFFF; + + if (self->format_version == format_v1) { + offset = getbe32(data + entry_v1_offset_offset_flags); + if (rev == 0) { + /* mask out version number for the first entry */ + offset &= 0xFFFF; + } else { + uint32_t offset_high = + getbe32(data + entry_v1_offset_high); + offset |= ((uint64_t)offset_high) << 32; + } + } else if (self->format_version == format_v2) { + offset = getbe32(data + entry_v2_offset_offset_flags); + if (rev == 0) { + /* mask out version number for the first entry */ + offset &= 0xFFFF; + } else { + uint32_t offset_high = + getbe32(data + entry_v2_offset_high); + offset |= ((uint64_t)offset_high) << 32; + } + } else if (self->format_version == format_cl2) { + uint32_t offset_high = getbe32(data + entry_cl2_offset_high); + offset = getbe32(data + entry_cl2_offset_offset_flags); + offset |= ((uint64_t)offset_high) << 32; } else { - uint32_t offset_high = getbe32(data); - offset |= ((uint64_t)offset_high) << 32; + raise_revlog_error(); + return -1; } + return (int64_t)(offset >> 16); } @@ -272,7 +350,16 @@ static inline int index_get_length(index data = index_deref(self, rev); - tmp = (int)getbe32(data + 8); + if (self->format_version == format_v1) { + tmp = (int)getbe32(data + entry_v1_offset_comp_len); + } else if (self->format_version == format_v2) { + tmp = (int)getbe32(data + entry_v2_offset_comp_len); + } else if (self->format_version == format_cl2) { + tmp = (int)getbe32(data + entry_cl2_offset_comp_len); + } else { + raise_revlog_error(); + return -1; + } if (tmp < 0) { PyErr_Format(PyExc_OverflowError, "revlog entry size out of bound (%d)", tmp); @@ -297,7 +384,7 @@ static PyObject *index_get(indexObject * { uint64_t offset_flags, sidedata_offset; int comp_len, uncomp_len, base_rev, link_rev, parent_1, parent_2, - sidedata_comp_len; + sidedata_comp_len, rank = rank_unknown; char data_comp_mode, sidedata_comp_mode; const char *c_node_id; const char *data; @@ -317,42 +404,96 @@ static PyObject *index_get(indexObject * if (data == NULL) return NULL; - offset_flags = getbe32(data + 4); - /* - * The first entry on-disk needs the version number masked out, - * but this doesn't apply if entries are added to an empty index. - */ - if (self->length && pos == 0) - offset_flags &= 0xFFFF; - else { - uint32_t offset_high = getbe32(data); - offset_flags |= ((uint64_t)offset_high) << 32; - } - - comp_len = getbe32(data + 8); - uncomp_len = getbe32(data + 12); - base_rev = getbe32(data + 16); - link_rev = getbe32(data + 20); - parent_1 = getbe32(data + 24); - parent_2 = getbe32(data + 28); - c_node_id = data + 32; - if (self->format_version == format_v1) { + offset_flags = getbe32(data + entry_v1_offset_offset_flags); + /* + * The first entry on-disk needs the version number masked out, + * but this doesn't apply if entries are added to an empty + * index. + */ + if (self->length && pos == 0) + offset_flags &= 0xFFFF; + else { + uint32_t offset_high = + getbe32(data + entry_v1_offset_high); + offset_flags |= ((uint64_t)offset_high) << 32; + } + + comp_len = getbe32(data + entry_v1_offset_comp_len); + uncomp_len = getbe32(data + entry_v1_offset_uncomp_len); + base_rev = getbe32(data + entry_v1_offset_base_rev); + link_rev = getbe32(data + entry_v1_offset_link_rev); + parent_1 = getbe32(data + entry_v1_offset_parent_1); + parent_2 = getbe32(data + entry_v1_offset_parent_2); + c_node_id = data + entry_v1_offset_node_id; + sidedata_offset = 0; sidedata_comp_len = 0; data_comp_mode = comp_mode_inline; sidedata_comp_mode = comp_mode_inline; + } else if (self->format_version == format_v2) { + offset_flags = getbe32(data + entry_v2_offset_offset_flags); + /* + * The first entry on-disk needs the version number masked out, + * but this doesn't apply if entries are added to an empty + * index. + */ + if (self->length && pos == 0) + offset_flags &= 0xFFFF; + else { + uint32_t offset_high = + getbe32(data + entry_v2_offset_high); + offset_flags |= ((uint64_t)offset_high) << 32; + } + + comp_len = getbe32(data + entry_v2_offset_comp_len); + uncomp_len = getbe32(data + entry_v2_offset_uncomp_len); + base_rev = getbe32(data + entry_v2_offset_base_rev); + link_rev = getbe32(data + entry_v2_offset_link_rev); + parent_1 = getbe32(data + entry_v2_offset_parent_1); + parent_2 = getbe32(data + entry_v2_offset_parent_2); + c_node_id = data + entry_v2_offset_node_id; + + sidedata_offset = + getbe64(data + entry_v2_offset_sidedata_offset); + sidedata_comp_len = + getbe32(data + entry_v2_offset_sidedata_comp_len); + data_comp_mode = data[entry_v2_offset_all_comp_mode] & 3; + sidedata_comp_mode = + ((data[entry_v2_offset_all_comp_mode] >> 2) & 3); + } else if (self->format_version == format_cl2) { + uint32_t offset_high = getbe32(data + entry_cl2_offset_high); + offset_flags = getbe32(data + entry_cl2_offset_offset_flags); + offset_flags |= ((uint64_t)offset_high) << 32; + comp_len = getbe32(data + entry_cl2_offset_comp_len); + uncomp_len = getbe32(data + entry_cl2_offset_uncomp_len); + /* base_rev and link_rev are not stored in changelogv2, but are + still used by some functions shared with the other revlogs. + They are supposed to contain links to other revisions, + but they always point to themselves in the case of a changelog. + */ + base_rev = pos; + link_rev = pos; + parent_1 = getbe32(data + entry_cl2_offset_parent_1); + parent_2 = getbe32(data + entry_cl2_offset_parent_2); + c_node_id = data + entry_cl2_offset_node_id; + sidedata_offset = + getbe64(data + entry_cl2_offset_sidedata_offset); + sidedata_comp_len = + getbe32(data + entry_cl2_offset_sidedata_comp_len); + data_comp_mode = data[entry_cl2_offset_all_comp_mode] & 3; + sidedata_comp_mode = + ((data[entry_cl2_offset_all_comp_mode] >> 2) & 3); + rank = getbe32(data + entry_cl2_offset_rank); } else { - sidedata_offset = getbe64(data + 64); - sidedata_comp_len = getbe32(data + 72); - data_comp_mode = data[76] & 3; - sidedata_comp_mode = ((data[76] >> 2) & 3); + raise_revlog_error(); + return NULL; } return Py_BuildValue(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, - data_comp_mode, sidedata_comp_mode); + data_comp_mode, sidedata_comp_mode, rank); } /* * Pack header information in binary @@ -410,6 +551,7 @@ static const char *index_node(indexObjec { Py_ssize_t length = index_length(self); const char *data; + const char *node_id; if (pos == nullrev) return nullid; @@ -418,7 +560,19 @@ static const char *index_node(indexObjec return NULL; data = index_deref(self, pos); - return data ? data + 32 : NULL; + + if (self->format_version == format_v1) { + node_id = data + entry_v1_offset_node_id; + } else if (self->format_version == format_v2) { + node_id = data + entry_v2_offset_node_id; + } else if (self->format_version == format_cl2) { + node_id = data + entry_cl2_offset_node_id; + } else { + raise_revlog_error(); + return NULL; + } + + return data ? node_id : NULL; } /* @@ -453,7 +607,7 @@ static PyObject *index_append(indexObjec { uint64_t offset_flags, sidedata_offset; int rev, comp_len, uncomp_len, base_rev, link_rev, parent_1, parent_2, - sidedata_comp_len; + sidedata_comp_len, rank; char data_comp_mode, sidedata_comp_mode; Py_ssize_t c_node_id_len; const char *c_node_id; @@ -464,8 +618,8 @@ static PyObject *index_append(indexObjec &uncomp_len, &base_rev, &link_rev, &parent_1, &parent_2, &c_node_id, &c_node_id_len, &sidedata_offset, &sidedata_comp_len, - &data_comp_mode, &sidedata_comp_mode)) { - PyErr_SetString(PyExc_TypeError, "11-tuple required"); + &data_comp_mode, &sidedata_comp_mode, &rank)) { + PyErr_SetString(PyExc_TypeError, "12-tuple required"); return NULL; } @@ -501,25 +655,61 @@ static PyObject *index_append(indexObjec } rev = self->length + self->new_length; data = self->added + self->entry_size * self->new_length++; - putbe32(offset_flags >> 32, data); - putbe32(offset_flags & 0xffffffffU, data + 4); - putbe32(comp_len, data + 8); - putbe32(uncomp_len, data + 12); - putbe32(base_rev, data + 16); - putbe32(link_rev, data + 20); - 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->format_version == format_v2) { - putbe64(sidedata_offset, data + 64); - putbe32(sidedata_comp_len, data + 72); + + memset(data, 0, self->entry_size); + + if (self->format_version == format_v1) { + putbe32(offset_flags >> 32, data + entry_v1_offset_high); + putbe32(offset_flags & 0xffffffffU, + data + entry_v1_offset_offset_flags); + putbe32(comp_len, data + entry_v1_offset_comp_len); + putbe32(uncomp_len, data + entry_v1_offset_uncomp_len); + putbe32(base_rev, data + entry_v1_offset_base_rev); + putbe32(link_rev, data + entry_v1_offset_link_rev); + putbe32(parent_1, data + entry_v1_offset_parent_1); + putbe32(parent_2, data + entry_v1_offset_parent_2); + memcpy(data + entry_v1_offset_node_id, c_node_id, + c_node_id_len); + } else if (self->format_version == format_v2) { + putbe32(offset_flags >> 32, data + entry_v2_offset_high); + putbe32(offset_flags & 0xffffffffU, + data + entry_v2_offset_offset_flags); + putbe32(comp_len, data + entry_v2_offset_comp_len); + putbe32(uncomp_len, data + entry_v2_offset_uncomp_len); + putbe32(base_rev, data + entry_v2_offset_base_rev); + putbe32(link_rev, data + entry_v2_offset_link_rev); + putbe32(parent_1, data + entry_v2_offset_parent_1); + putbe32(parent_2, data + entry_v2_offset_parent_2); + memcpy(data + entry_v2_offset_node_id, c_node_id, + c_node_id_len); + putbe64(sidedata_offset, + data + entry_v2_offset_sidedata_offset); + putbe32(sidedata_comp_len, + data + entry_v2_offset_sidedata_comp_len); comp_field = data_comp_mode & 3; comp_field = comp_field | (sidedata_comp_mode & 3) << 2; - data[76] = comp_field; - /* Padding for 96 bytes alignment */ - memset(data + 77, 0, self->entry_size - 77); + data[entry_v2_offset_all_comp_mode] = comp_field; + } else if (self->format_version == format_cl2) { + putbe32(offset_flags >> 32, data + entry_cl2_offset_high); + putbe32(offset_flags & 0xffffffffU, + data + entry_cl2_offset_offset_flags); + putbe32(comp_len, data + entry_cl2_offset_comp_len); + putbe32(uncomp_len, data + entry_cl2_offset_uncomp_len); + putbe32(parent_1, data + entry_cl2_offset_parent_1); + putbe32(parent_2, data + entry_cl2_offset_parent_2); + memcpy(data + entry_cl2_offset_node_id, c_node_id, + c_node_id_len); + putbe64(sidedata_offset, + data + entry_cl2_offset_sidedata_offset); + putbe32(sidedata_comp_len, + data + entry_cl2_offset_sidedata_comp_len); + comp_field = data_comp_mode & 3; + comp_field = comp_field | (sidedata_comp_mode & 3) << 2; + data[entry_cl2_offset_all_comp_mode] = comp_field; + putbe32(rank, data + entry_cl2_offset_rank); + } else { + raise_revlog_error(); + return NULL; } if (self->ntinitialized) @@ -574,10 +764,28 @@ static PyObject *index_replace_sidedata_ /* Find the newly added node, offset from the "already on-disk" length */ data = self->added + self->entry_size * (rev - self->length); - putbe64(offset_flags, data); - putbe64(sidedata_offset, data + 64); - putbe32(sidedata_comp_len, data + 72); - data[76] = (data[76] & ~(3 << 2)) | ((comp_mode & 3) << 2); + if (self->format_version == format_v2) { + putbe64(offset_flags, data + entry_v2_offset_high); + putbe64(sidedata_offset, + data + entry_v2_offset_sidedata_offset); + putbe32(sidedata_comp_len, + data + entry_v2_offset_sidedata_comp_len); + data[entry_v2_offset_all_comp_mode] = + (data[entry_v2_offset_all_comp_mode] & ~(3 << 2)) | + ((comp_mode & 3) << 2); + } else if (self->format_version == format_cl2) { + putbe64(offset_flags, data + entry_cl2_offset_high); + putbe64(sidedata_offset, + data + entry_cl2_offset_sidedata_offset); + putbe32(sidedata_comp_len, + data + entry_cl2_offset_sidedata_comp_len); + data[entry_cl2_offset_all_comp_mode] = + (data[entry_cl2_offset_all_comp_mode] & ~(3 << 2)) | + ((comp_mode & 3) << 2); + } else { + raise_revlog_error(); + return NULL; + } Py_RETURN_NONE; } @@ -1120,7 +1328,17 @@ static inline int index_baserev(indexObj data = index_deref(self, rev); if (data == NULL) return -2; - result = getbe32(data + 16); + + if (self->format_version == format_v1) { + result = getbe32(data + entry_v1_offset_base_rev); + } else if (self->format_version == format_v2) { + result = getbe32(data + entry_v2_offset_base_rev); + } else if (self->format_version == format_cl2) { + return rev; + } else { + raise_revlog_error(); + return -1; + } if (result > rev) { PyErr_Format( @@ -2598,8 +2816,10 @@ static void index_invalidate_added(index if (i < 0) return; - for (i = start; i < len; i++) - nt_delete_node(&self->nt, index_deref(self, i) + 32); + for (i = start; i < len; i++) { + const char *node = index_node(self, i); + nt_delete_node(&self->nt, node); + } self->new_length = start - self->length; } @@ -2732,9 +2952,18 @@ static Py_ssize_t inline_scan(indexObjec while (pos + self->entry_size <= 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); - if (self->entry_size == v2_entry_size) { - sidedata_comp_len = getbe32(data + pos + 72); + if (self->format_version == format_v1) { + comp_len = + getbe32(data + pos + entry_v1_offset_comp_len); + sidedata_comp_len = 0; + } else if (self->format_version == format_v2) { + comp_len = + getbe32(data + pos + entry_v2_offset_comp_len); + sidedata_comp_len = getbe32( + data + pos + entry_v2_offset_sidedata_comp_len); + } else { + raise_revlog_error(); + return -1; } incr = self->entry_size + comp_len + sidedata_comp_len; if (offsets) @@ -2754,10 +2983,10 @@ static Py_ssize_t inline_scan(indexObjec static int index_init(indexObject *self, PyObject *args, PyObject *kwargs) { - PyObject *data_obj, *inlined_obj, *revlogv2; + PyObject *data_obj, *inlined_obj; Py_ssize_t size; - static char *kwlist[] = {"data", "inlined", "revlogv2", NULL}; + static char *kwlist[] = {"data", "inlined", "format", NULL}; /* Initialize before argument-checking to avoid index_dealloc() crash. */ @@ -2774,10 +3003,11 @@ static int index_init(indexObject *self, self->nodelen = 20; self->nullentry = NULL; self->rust_ext_compat = 1; - - revlogv2 = NULL; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|O", kwlist, - &data_obj, &inlined_obj, &revlogv2)) + self->format_version = format_v1; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|l", kwlist, + &data_obj, &inlined_obj, + &(self->format_version))) return -1; if (!PyObject_CheckBuffer(data_obj)) { PyErr_SetString(PyExc_TypeError, @@ -2789,17 +3019,18 @@ static int index_init(indexObject *self, return -1; } - if (revlogv2 && PyObject_IsTrue(revlogv2)) { - self->format_version = format_v2; - self->entry_size = v2_entry_size; - } else { - self->format_version = format_v1; + if (self->format_version == format_v1) { self->entry_size = v1_entry_size; + } else if (self->format_version == format_v2) { + self->entry_size = v2_entry_size; + } else if (self->format_version == format_cl2) { + self->entry_size = cl2_entry_size; } - self->nullentry = Py_BuildValue( - PY23("iiiiiiis#iiBB", "iiiiiiiy#iiBB"), 0, 0, 0, -1, -1, -1, -1, - nullid, self->nodelen, 0, 0, comp_mode_inline, comp_mode_inline); + self->nullentry = + Py_BuildValue(PY23("iiiiiiis#iiBBi", "iiiiiiiy#iiBBi"), 0, 0, 0, -1, + -1, -1, -1, nullid, self->nodelen, 0, 0, + comp_mode_inline, comp_mode_inline, rank_unknown); if (!self->nullentry) return -1; diff --git a/mercurial/changegroup.py b/mercurial/changegroup.py --- a/mercurial/changegroup.py +++ b/mercurial/changegroup.py @@ -350,10 +350,11 @@ class cg1unpacker(object): def ondupchangelog(cl, rev): if rev < clstart: - duprevs.append(rev) + duprevs.append(rev) # pytype: disable=attribute-error def onchangelog(cl, rev): ctx = cl.changelogrevision(rev) + assert efilesset is not None # help pytype efilesset.update(ctx.files) repo.register_changeset(rev, ctx) diff --git a/mercurial/chgserver.py b/mercurial/chgserver.py --- a/mercurial/chgserver.py +++ b/mercurial/chgserver.py @@ -643,6 +643,13 @@ class chgunixservicehandler(object): def __init__(self, ui): self.ui = ui + + # TODO: use PEP 526 syntax (`_hashstate: hashstate` at the class level) + # when 3.5 support is dropped. + self._hashstate = None # type: hashstate + self._baseaddress = None # type: bytes + self._realaddress = None # type: bytes + self._idletimeout = ui.configint(b'chgserver', b'idletimeout') self._lastactive = time.time() diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py --- a/mercurial/cmdutil.py +++ b/mercurial/cmdutil.py @@ -522,8 +522,10 @@ def dorecord( # 1. filter patch, since we are intending to apply subset of it try: chunks, newopts = filterfn(ui, original_headers, match) - except error.PatchError as err: + except error.PatchParseError as err: raise error.InputError(_(b'error parsing patch: %s') % err) + except error.PatchApplicationError as err: + raise error.StateError(_(b'error applying patch: %s') % err) opts.update(newopts) # We need to keep a backup of files that have been newly added and @@ -608,8 +610,10 @@ def dorecord( ui.debug(b'applying patch\n') ui.debug(fp.getvalue()) patch.internalpatch(ui, repo, fp, 1, eolmode=None) - except error.PatchError as err: + except error.PatchParseError as err: raise error.InputError(pycompat.bytestr(err)) + except error.PatchApplicationError as err: + raise error.StateError(pycompat.bytestr(err)) del fp # 4. We prepared working directory according to filtered @@ -2020,9 +2024,16 @@ def tryimportone(ui, repo, patchdata, pa eolmode=None, similarity=sim / 100.0, ) - except error.PatchError as e: + except error.PatchParseError as e: + raise error.InputError( + pycompat.bytestr(e), + hint=_( + b'check that whitespace in the patch has not been mangled' + ), + ) + except error.PatchApplicationError as e: if not partial: - raise error.Abort(pycompat.bytestr(e)) + raise error.StateError(pycompat.bytestr(e)) if partial: rejects = True @@ -2079,8 +2090,15 @@ def tryimportone(ui, repo, patchdata, pa files, eolmode=None, ) - except error.PatchError as e: - raise error.Abort(stringutil.forcebytestr(e)) + except error.PatchParseError as e: + raise error.InputError( + stringutil.forcebytestr(e), + hint=_( + b'check that whitespace in the patch has not been mangled' + ), + ) + except error.PatchApplicationError as e: + raise error.StateError(stringutil.forcebytestr(e)) if opts.get(b'exact'): editor = None else: @@ -3628,15 +3646,14 @@ def _performrevert( prntstatusmsg(b'drop', f) repo.dirstate.set_untracked(f) - normal = None - if node == parent: - # We're reverting to our parent. If possible, we'd like status - # to report the file as clean. We have to use normallookup for - # merges to avoid losing information about merged/dirty files. - if p2 != repo.nullid: - normal = repo.dirstate.set_tracked - else: - normal = repo.dirstate.set_clean + # We are reverting to our parent. If possible, we had like `hg status` + # to report the file as clean. We have to be less agressive for + # merges to avoid losing information about copy introduced by the merge. + # This might comes with bugs ? + reset_copy = p2 == repo.nullid + + def normal(filename): + return repo.dirstate.set_tracked(filename, reset_copy=reset_copy) newlyaddedandmodifiedfiles = set() if interactive: @@ -3674,8 +3691,10 @@ def _performrevert( if operation == b'discard': chunks = patch.reversehunks(chunks) - except error.PatchError as err: - raise error.Abort(_(b'error parsing patch: %s') % err) + except error.PatchParseError as err: + raise error.InputError(_(b'error parsing patch: %s') % err) + except error.PatchApplicationError as err: + raise error.StateError(_(b'error applying patch: %s') % err) # FIXME: when doing an interactive revert of a copy, there's no way of # performing a partial revert of the added file, the only option is @@ -3710,8 +3729,10 @@ def _performrevert( if dopatch: try: patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None) - except error.PatchError as err: - raise error.Abort(pycompat.bytestr(err)) + except error.PatchParseError as err: + raise error.InputError(pycompat.bytestr(err)) + except error.PatchApplicationError as err: + raise error.StateError(pycompat.bytestr(err)) del fp else: for f in actions[b'revert'][0]: @@ -3727,9 +3748,6 @@ def _performrevert( checkout(f) repo.dirstate.set_tracked(f) - normal = repo.dirstate.set_tracked - if node == parent and p2 == repo.nullid: - normal = repo.dirstate.set_clean for f in actions[b'undelete'][0]: if interactive: choice = repo.ui.promptchoice( diff --git a/mercurial/color.py b/mercurial/color.py --- a/mercurial/color.py +++ b/mercurial/color.py @@ -248,28 +248,19 @@ def _modesetup(ui): if pycompat.iswindows: from . import win32 - term = encoding.environ.get(b'TERM') - # TERM won't be defined in a vanilla cmd.exe environment. - - # UNIX-like environments on Windows such as Cygwin and MSYS will - # set TERM. They appear to make a best effort attempt at setting it - # to something appropriate. However, not all environments with TERM - # defined support ANSI. - ansienviron = term and b'xterm' in term - if mode == b'auto': # Since "ansi" could result in terminal gibberish, we error on the # side of selecting "win32". However, if w32effects is not defined, # we almost certainly don't support "win32", so don't even try. # w32effects is not populated when stdout is redirected, so checking # it first avoids win32 calls in a state known to error out. - if ansienviron or not w32effects or win32.enablevtmode(): + if not w32effects or win32.enablevtmode(): realmode = b'ansi' else: realmode = b'win32' # An empty w32effects is a clue that stdout is redirected, and thus # cannot enable VT mode. - elif mode == b'ansi' and w32effects and not ansienviron: + elif mode == b'ansi' and w32effects: win32.enablevtmode() elif mode == b'auto': realmode = b'ansi' diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -3309,7 +3309,9 @@ def _dograft(ui, repo, *revs, **opts): overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')} base = ctx.p1() if basectx is None else basectx with ui.configoverride(overrides, b'graft'): - stats = mergemod.graft(repo, ctx, base, [b'local', b'graft']) + stats = mergemod.graft( + repo, ctx, base, [b'local', b'graft', b'parent of graft'] + ) # report any conflicts if stats.unresolvedcount > 0: # write out state for --continue @@ -4914,7 +4916,7 @@ def merge(ui, repo, node=None, **opts): overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')} with ui.configoverride(overrides, b'merge'): force = opts.get(b'force') - labels = [b'working copy', b'merge rev'] + labels = [b'working copy', b'merge rev', b'common ancestor'] return hg.merge(ctx, force=force, labels=labels) @@ -6130,7 +6132,6 @@ def resolve(ui, repo, *pats, **opts): ret = 0 didwork = False - tocomplete = [] hasconflictmarkers = [] if mark: markcheck = ui.config(b'commands', b'resolve.mark-check') @@ -6183,24 +6184,20 @@ def resolve(ui, repo, *pats, **opts): # preresolve file overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')} with ui.configoverride(overrides, b'resolve'): - complete, r = ms.preresolve(f, wctx) - if not complete: - tocomplete.append(f) - elif r: + r = ms.resolve(f, wctx) + if r: ret = 1 finally: ms.commit() - # replace filemerge's .orig file with our resolve file, but only - # for merges that are complete - if complete: - try: - util.rename( - a + b".resolve", scmutil.backuppath(ui, repo, f) - ) - except OSError as inst: - if inst.errno != errno.ENOENT: - raise + # replace filemerge's .orig file with our resolve file + try: + util.rename( + a + b".resolve", scmutil.backuppath(ui, repo, f) + ) + except OSError as inst: + if inst.errno != errno.ENOENT: + raise if hasconflictmarkers: ui.warn( @@ -6218,25 +6215,6 @@ def resolve(ui, repo, *pats, **opts): hint=_(b'use --all to mark anyway'), ) - for f in tocomplete: - try: - # resolve file - overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')} - with ui.configoverride(overrides, b'resolve'): - r = ms.resolve(f, wctx) - if r: - ret = 1 - finally: - ms.commit() - - # replace filemerge's .orig file with our resolve file - a = repo.wjoin(f) - try: - util.rename(a + b".resolve", scmutil.backuppath(ui, repo, f)) - except OSError as inst: - if inst.errno != errno.ENOENT: - raise - ms.commit() branchmerge = repo.dirstate.p2() != repo.nullid # resolve is not doing a parent change here, however, `record updates` @@ -6897,9 +6875,9 @@ def status(ui, repo, *pats, **opts): cmdutil.check_at_most_one_arg(opts, 'rev', 'change') opts = pycompat.byteskwargs(opts) - revs = opts.get(b'rev') - change = opts.get(b'change') - terse = opts.get(b'terse') + revs = opts.get(b'rev', []) + change = opts.get(b'change', b'') + terse = opts.get(b'terse', _NOTTERSE) if terse is _NOTTERSE: if revs: terse = b'' @@ -7832,9 +7810,9 @@ def update(ui, repo, node=None, **opts): raise error.InputError(_(b"you can't specify a revision and a date")) updatecheck = None - if check: + if check or merge is not None and not merge: updatecheck = b'abort' - elif merge: + elif merge or check is not None and not check: updatecheck = b'none' with repo.wlock(): diff --git a/mercurial/commit.py b/mercurial/commit.py --- a/mercurial/commit.py +++ b/mercurial/commit.py @@ -134,7 +134,13 @@ def _prepare_files(tr, ctx, error=False, for s in salvaged: files.mark_salvaged(s) - if ctx.manifestnode(): + narrow_files = {} + if not ctx.repo().narrowmatch().always(): + for f, e in ms.allextras().items(): + action = e.get(b'outside-narrow-merge-action') + if action is not None: + narrow_files[f] = action + if ctx.manifestnode() and not narrow_files: # reuse an existing manifest revision repo.ui.debug(b'reusing known manifest\n') mn = ctx.manifestnode() @@ -142,11 +148,11 @@ def _prepare_files(tr, ctx, error=False, if writechangesetcopy: files.update_added(ctx.filesadded()) files.update_removed(ctx.filesremoved()) - elif not ctx.files(): + elif not ctx.files() and not narrow_files: repo.ui.debug(b'reusing manifest from p1 (no file change)\n') mn = p1.manifestnode() else: - mn = _process_files(tr, ctx, ms, files, error=error) + mn = _process_files(tr, ctx, ms, files, narrow_files, error=error) if origctx and origctx.manifestnode() == mn: origfiles = origctx.files() @@ -177,7 +183,7 @@ def _get_salvaged(repo, ms, ctx): return salvaged -def _process_files(tr, ctx, ms, files, error=False): +def _process_files(tr, ctx, ms, files, narrow_files=None, error=False): repo = ctx.repo() p1 = ctx.p1() p2 = ctx.p2() @@ -198,8 +204,33 @@ def _process_files(tr, ctx, ms, files, e linkrev = len(repo) repo.ui.note(_(b"committing files:\n")) uipathfn = scmutil.getuipathfn(repo) - for f in sorted(ctx.modified() + ctx.added()): + all_files = ctx.modified() + ctx.added() + all_files.extend(narrow_files.keys()) + all_files.sort() + for f in all_files: repo.ui.note(uipathfn(f) + b"\n") + if f in narrow_files: + narrow_action = narrow_files.get(f) + if narrow_action == mergestate.CHANGE_REMOVED: + files.mark_removed(f) + removed.append(f) + elif narrow_action == mergestate.CHANGE_ADDED: + files.mark_added(f) + added.append(f) + m[f] = m2[f] + flags = m2ctx.find(f)[1] or b'' + m.setflag(f, flags) + elif narrow_action == mergestate.CHANGE_MODIFIED: + files.mark_touched(f) + added.append(f) + m[f] = m2[f] + flags = m2ctx.find(f)[1] or b'' + m.setflag(f, flags) + else: + msg = _(b"corrupted mergestate, unknown narrow action: %b") + hint = _(b"restart the merge") + raise error.Abort(msg, hint=hint) + continue try: fctx = ctx[f] if fctx is None: @@ -239,7 +270,17 @@ def _process_files(tr, ctx, ms, files, e if not rf(f): files.mark_removed(f) - mn = _commit_manifest(tr, linkrev, ctx, mctx, m, files.touched, added, drop) + mn = _commit_manifest( + tr, + linkrev, + ctx, + mctx, + m, + files.touched, + added, + drop, + bool(narrow_files), + ) return mn @@ -409,7 +450,17 @@ def _filecommit( return fnode, touched -def _commit_manifest(tr, linkrev, ctx, mctx, manifest, files, added, drop): +def _commit_manifest( + tr, + linkrev, + ctx, + mctx, + manifest, + files, + added, + drop, + has_some_narrow_action=False, +): """make a new manifest entry (or reuse a new one) given an initialised manifest context and precomputed list of @@ -451,6 +502,10 @@ def _commit_manifest(tr, linkrev, ctx, m # at this point is merges, and we already error out in the # case where the merge has files outside of the narrowspec, # so this is safe. + if has_some_narrow_action: + match = None + else: + match = repo.narrowmatch() mn = mctx.write( tr, linkrev, @@ -458,7 +513,7 @@ def _commit_manifest(tr, linkrev, ctx, m p2.manifestnode(), added, drop, - match=repo.narrowmatch(), + match=match, ) else: repo.ui.debug( diff --git a/mercurial/configitems.py b/mercurial/configitems.py --- a/mercurial/configitems.py +++ b/mercurial/configitems.py @@ -1042,11 +1042,6 @@ coreconfigitem( ) coreconfigitem( b'experimental', - b'mergetempdirprefix', - default=None, -) -coreconfigitem( - b'experimental', b'mmapindexthreshold', default=None, ) @@ -1102,16 +1097,6 @@ coreconfigitem( ) coreconfigitem( b'experimental', - b'httppeer.advertise-v2', - default=False, -) -coreconfigitem( - b'experimental', - b'httppeer.v2-encoder-order', - default=None, -) -coreconfigitem( - b'experimental', b'httppostargs', default=False, ) @@ -1211,11 +1196,6 @@ coreconfigitem( ) coreconfigitem( b'experimental', - b'sshserver.support-v2', - default=False, -) -coreconfigitem( - b'experimental', b'sparse-read', default=False, ) @@ -1241,26 +1221,6 @@ coreconfigitem( ) coreconfigitem( b'experimental', - b'sshpeer.advertise-v2', - default=False, -) -coreconfigitem( - b'experimental', - b'web.apiserver', - default=False, -) -coreconfigitem( - b'experimental', - b'web.api.http-v2', - default=False, -) -coreconfigitem( - b'experimental', - b'web.api.debugreflect', - default=False, -) -coreconfigitem( - b'experimental', b'web.full-garbage-collection-rate', default=1, # still forcing a full collection on each request ) @@ -1281,11 +1241,17 @@ coreconfigitem( ) coreconfigitem( b'extensions', - b'.*', + b'[^:]*', default=None, generic=True, ) coreconfigitem( + b'extensions', + b'[^:]*:required', + default=False, + generic=True, +) +coreconfigitem( b'extdata', b'.*', default=None, @@ -1313,6 +1279,18 @@ coreconfigitem( ) coreconfigitem( b'format', + b'use-dirstate-tracked-hint', + default=False, + experimental=True, +) +coreconfigitem( + b'format', + b'use-dirstate-tracked-hint.version', + default=1, + experimental=True, +) +coreconfigitem( + b'format', b'dotencode', default=True, ) @@ -1352,10 +1330,10 @@ coreconfigitem( ) # Experimental TODOs: # -# * Same as for evlogv2 (but for the reduction of the number of files) +# * Same as for revlogv2 (but for the reduction of the number of files) +# * Actually computing the rank of changesets # * Improvement to investigate # - storing .hgtags fnode -# - storing `rank` of changesets # - storing branch related identifier coreconfigitem( @@ -1405,7 +1383,7 @@ coreconfigitem( coreconfigitem( b'format', b'use-share-safe', - default=False, + default=True, ) coreconfigitem( b'format', diff --git a/mercurial/context.py b/mercurial/context.py --- a/mercurial/context.py +++ b/mercurial/context.py @@ -20,7 +20,6 @@ from .node import ( ) from .pycompat import ( getattr, - open, ) from . import ( dagop, @@ -46,6 +45,9 @@ from .utils import ( dateutil, stringutil, ) +from .dirstateutils import ( + timestamp, +) propertycache = util.propertycache @@ -682,6 +684,14 @@ class changectx(basectx): """Return a list of byte bookmark names.""" return self._repo.nodebookmarks(self._node) + def fast_rank(self): + repo = self._repo + if self._maybe_filtered: + cl = repo.changelog + else: + cl = repo.unfiltered().changelog + return cl.fast_rank(self._rev) + def phase(self): return self._repo._phasecache.phase(self._repo, self._rev) @@ -1793,13 +1803,14 @@ class workingctx(committablectx): sane.append(f) return sane - def _checklookup(self, files): + def _checklookup(self, files, mtime_boundary): # check for any possibly clean files if not files: - return [], [], [] + return [], [], [], [] modified = [] deleted = [] + clean = [] fixup = [] pctx = self._parents[0] # do a full compare of any files that might have changed @@ -1813,8 +1824,18 @@ class workingctx(committablectx): or pctx[f].cmp(self[f]) ): modified.append(f) + elif mtime_boundary is None: + clean.append(f) else: - fixup.append(f) + s = self[f].lstat() + mode = s.st_mode + size = s.st_size + file_mtime = timestamp.reliable_mtime_of(s, mtime_boundary) + if file_mtime is not None: + cache_info = (mode, size, file_mtime) + fixup.append((f, cache_info)) + else: + clean.append(f) except (IOError, OSError): # A file become inaccessible in between? Mark it as deleted, # matching dirstate behavior (issue5584). @@ -1824,7 +1845,7 @@ class workingctx(committablectx): # it's in the dirstate. deleted.append(f) - return modified, deleted, fixup + return modified, deleted, clean, fixup def _poststatusfixup(self, status, fixup): """update dirstate for files that are actually clean""" @@ -1842,13 +1863,13 @@ class workingctx(committablectx): if dirstate.identity() == oldid: if fixup: if dirstate.pendingparentchange(): - normal = lambda f: dirstate.update_file( + normal = lambda f, pfd: dirstate.update_file( f, p1_tracked=True, wc_tracked=True ) else: normal = dirstate.set_clean - for f in fixup: - normal(f) + for f, pdf in fixup: + normal(f, pdf) # write changes out explicitly, because nesting # wlock at runtime may prevent 'wlock.release()' # after this block from doing so for subsequent @@ -1878,19 +1899,23 @@ class workingctx(committablectx): subrepos = [] if b'.hgsub' in self: subrepos = sorted(self.substate) - cmp, s = self._repo.dirstate.status( + cmp, s, mtime_boundary = self._repo.dirstate.status( match, subrepos, ignored=ignored, clean=clean, unknown=unknown ) # check for any possibly clean files fixup = [] if cmp: - modified2, deleted2, fixup = self._checklookup(cmp) + modified2, deleted2, clean_set, fixup = self._checklookup( + cmp, mtime_boundary + ) s.modified.extend(modified2) s.deleted.extend(deleted2) + if clean_set and clean: + s.clean.extend(clean_set) if fixup and clean: - s.clean.extend(fixup) + s.clean.extend((f for f, _ in fixup)) self._poststatusfixup(s, fixup) @@ -3111,13 +3136,11 @@ class arbitraryfilectx(object): return util.readfile(self._path) def decodeddata(self): - with open(self._path, b"rb") as f: - return f.read() + return util.readfile(self._path) def remove(self): util.unlink(self._path) def write(self, data, flags, **kwargs): assert not flags - with open(self._path, b"wb") as f: - f.write(data) + util.writefile(self._path, data) diff --git a/mercurial/copies.py b/mercurial/copies.py --- a/mercurial/copies.py +++ b/mercurial/copies.py @@ -246,7 +246,6 @@ def _changesetforwardcopies(a, b, match) return {} repo = a.repo().unfiltered() - children = {} cl = repo.changelog isancestor = cl.isancestorrev @@ -290,7 +289,7 @@ def _changesetforwardcopies(a, b, match) # no common revision to track copies from return {} if has_graph_roots: - # this deal with the special case mentionned in the [1] footnotes. We + # this deal with the special case mentioned in the [1] footnotes. We # must filter out revisions that leads to non-common graphroots. roots = list(roots) m = min(roots) @@ -301,11 +300,11 @@ def _changesetforwardcopies(a, b, match) if repo.filecopiesmode == b'changeset-sidedata': # When using side-data, we will process the edges "from" the children. - # We iterate over the childre, gathering previous collected data for + # We iterate over the children, gathering previous collected data for # the parents. Do know when the parents data is no longer necessary, we # keep a counter of how many children each revision has. # - # An interresting property of `children_count` is that it only contains + # An interesting property of `children_count` is that it only contains # revision that will be relevant for a edge of the graph. So if a # children has parent not in `children_count`, that edges should not be # processed. @@ -449,7 +448,11 @@ def _combine_changeset_copies( # filter out internal details and return a {dest: source mapping} final_copies = {} - for dest, (tt, source) in all_copies[targetrev].items(): + + targetrev_items = all_copies[targetrev] + assert targetrev_items is not None # help pytype + + for dest, (tt, source) in targetrev_items.items(): if source is not None: final_copies[dest] = source if not alwaysmatch: diff --git a/mercurial/dagop.py b/mercurial/dagop.py --- a/mercurial/dagop.py +++ b/mercurial/dagop.py @@ -9,7 +9,6 @@ from __future__ import absolute_import import heapq -from .node import nullrev from .thirdparty import attr from .node import nullrev from . import ( diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py --- a/mercurial/debugcommands.py +++ b/mercurial/debugcommands.py @@ -91,7 +91,6 @@ from . import ( vfs as vfsmod, wireprotoframing, wireprotoserver, - wireprotov2peer, ) from .interfaces import repository from .utils import ( @@ -179,6 +178,12 @@ def debugapplystreamclonebundle(ui, repo _(b'add single file all revs overwrite'), ), (b'n', b'new-file', None, _(b'add new file at each rev')), + ( + b'', + b'from-existing', + None, + _(b'continue from a non-empty repository'), + ), ], _(b'[OPTION]... [TEXT]'), ) @@ -189,6 +194,7 @@ def debugbuilddag( mergeable_file=False, overwritten_file=False, new_file=False, + from_existing=False, ): """builds a repo with a given DAG from scratch in the current empty repo @@ -227,7 +233,7 @@ def debugbuilddag( text = ui.fin.read() cl = repo.changelog - if len(cl) > 0: + if len(cl) > 0 and not from_existing: raise error.Abort(_(b'repository is not empty')) # determine number of revs in DAG @@ -273,7 +279,10 @@ def debugbuilddag( x[fn].data() for x in (pa, p1, p2) ] m3 = simplemerge.Merge3Text(base, local, other) - ml = [l.strip() for l in m3.merge_lines()] + ml = [ + l.strip() + for l in simplemerge.render_minimized(m3)[0] + ] ml.append(b"") elif at > 0: ml = p1[fn].data().split(b"\n") @@ -4352,8 +4361,8 @@ def debugwireproto(ui, repo, path=None, ``--peer`` can be used to bypass the handshake protocol and construct a peer instance using the specified class type. Valid values are ``raw``, - ``http2``, ``ssh1``, and ``ssh2``. ``raw`` instances only allow sending - raw data payloads and don't support higher-level command actions. + ``ssh1``. ``raw`` instances only allow sending raw data payloads and + don't support higher-level command actions. ``--noreadstderr`` can be used to disable automatic reading from stderr of the peer (for SSH connections only). Disabling automatic reading of @@ -4528,13 +4537,11 @@ def debugwireproto(ui, repo, path=None, if opts[b'peer'] and opts[b'peer'] not in ( b'raw', - b'http2', b'ssh1', - b'ssh2', ): raise error.Abort( _(b'invalid value for --peer'), - hint=_(b'valid values are "raw", "ssh1", and "ssh2"'), + hint=_(b'valid values are "raw" and "ssh1"'), ) if path and opts[b'localssh']: @@ -4602,18 +4609,6 @@ def debugwireproto(ui, repo, path=None, None, autoreadstderr=autoreadstderr, ) - elif opts[b'peer'] == b'ssh2': - ui.write(_(b'creating ssh peer for wire protocol version 2\n')) - peer = sshpeer.sshv2peer( - ui, - url, - proc, - stdin, - stdout, - stderr, - None, - autoreadstderr=autoreadstderr, - ) elif opts[b'peer'] == b'raw': ui.write(_(b'using raw connection to peer\n')) peer = None @@ -4666,34 +4661,7 @@ def debugwireproto(ui, repo, path=None, opener = urlmod.opener(ui, authinfo, **openerargs) - if opts[b'peer'] == b'http2': - ui.write(_(b'creating http peer for wire protocol version 2\n')) - # We go through makepeer() because we need an API descriptor for - # the peer instance to be useful. - maybe_silent = ( - ui.silent() - if opts[b'nologhandshake'] - else util.nullcontextmanager() - ) - with maybe_silent, ui.configoverride( - {(b'experimental', b'httppeer.advertise-v2'): True} - ): - peer = httppeer.makepeer(ui, path, opener=opener) - - if not isinstance(peer, httppeer.httpv2peer): - raise error.Abort( - _( - b'could not instantiate HTTP peer for ' - b'wire protocol version 2' - ), - hint=_( - b'the server may not have the feature ' - b'enabled or is not allowing this ' - b'client version' - ), - ) - - elif opts[b'peer'] == b'raw': + if opts[b'peer'] == b'raw': ui.write(_(b'using raw connection to peer\n')) peer = None elif opts[b'peer']: @@ -4774,17 +4742,10 @@ def debugwireproto(ui, repo, path=None, with peer.commandexecutor() as e: res = e.callcommand(command, args).result() - if isinstance(res, wireprotov2peer.commandresponse): - val = res.objects() - ui.status( - _(b'response: %s\n') - % stringutil.pprint(val, bprefix=True, indent=2) - ) - else: - ui.status( - _(b'response: %s\n') - % stringutil.pprint(res, bprefix=True, indent=2) - ) + ui.status( + _(b'response: %s\n') + % stringutil.pprint(res, bprefix=True, indent=2) + ) elif action == b'batchbegin': if batchedcommands is not None: diff --git a/mercurial/destutil.py b/mercurial/destutil.py --- a/mercurial/destutil.py +++ b/mercurial/destutil.py @@ -65,9 +65,8 @@ def _destupdateobs(repo, clean): # replaced changesets: same as divergent except we know there # is no conflict # - # pruned changeset: no update is done; though, we could - # consider updating to the first non-obsolete parent, - # similar to what is current done for 'hg prune' + # pruned changeset: update to the closest non-obsolete ancestor, + # similar to what 'hg prune' currently does if successors: # flatten the list here handles both divergent (len > 1) @@ -77,8 +76,15 @@ def _destupdateobs(repo, clean): # get the max revision for the given successors set, # i.e. the 'tip' of a set node = repo.revs(b'max(%ln)', successors).first() - if bookmarks.isactivewdirparent(repo): - movemark = repo[b'.'].node() + else: + p1 = p1.p1() + while p1.obsolete(): + p1 = p1.p1() + node = p1.node() + + if node is not None and bookmarks.isactivewdirparent(repo): + movemark = repo[b'.'].node() + return node, movemark, None diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py --- a/mercurial/dirstate.py +++ b/mercurial/dirstate.py @@ -12,6 +12,7 @@ import contextlib import errno import os import stat +import uuid from .i18n import _ from .pycompat import delattr @@ -23,6 +24,7 @@ from . import ( encoding, error, match as matchmod, + node, pathutil, policy, pycompat, @@ -66,16 +68,6 @@ class rootcache(filecache): return obj._join(fname) -def _getfsnow(vfs): - '''Get "now" timestamp on filesystem''' - tmpfd, tmpname = vfs.mkstemp() - try: - return timestamp.mtime_of(os.fstat(tmpfd)) - finally: - os.close(tmpfd) - vfs.unlink(tmpname) - - def requires_parents_change(func): def wrap(self, *args, **kwargs): if not self.pendingparentchange(): @@ -109,6 +101,7 @@ class dirstate(object): sparsematchfn, nodeconstants, use_dirstate_v2, + use_tracked_hint=False, ): """Create a new dirstate object. @@ -117,6 +110,7 @@ class dirstate(object): the dirstate. """ self._use_dirstate_v2 = use_dirstate_v2 + self._use_tracked_hint = use_tracked_hint self._nodeconstants = nodeconstants self._opener = opener self._validate = validate @@ -125,12 +119,15 @@ class dirstate(object): # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is # UNC path pointing to root share (issue4557) self._rootdir = pathutil.normasprefix(root) + # True is any internal state may be different self._dirty = False - self._lastnormaltime = timestamp.zero() + # True if the set of tracked file may be different + self._dirty_tracked_set = False self._ui = ui self._filecache = {} self._parentwriters = 0 self._filename = b'dirstate' + self._filename_th = b'dirstate-tracked-hint' self._pendingfilename = b'%s.pending' % self._filename self._plchangecallbacks = {} self._origpl = None @@ -332,27 +329,6 @@ class dirstate(object): return util.pconvert(path) return path - def __getitem__(self, key): - """Return the current state of key (a filename) in the dirstate. - - States are: - n normal - m needs merging - r marked for removal - a marked for addition - ? not tracked - - XXX The "state" is a bit obscure to be in the "public" API. we should - consider migrating all user of this to going through the dirstate entry - instead. - """ - msg = b"don't use dirstate[file], use dirstate.get_entry(file)" - util.nouideprecwarn(msg, b'6.1', stacklevel=2) - entry = self._map.get(key) - if entry is not None: - return entry.state - return b'?' - def get_entry(self, path): """return a DirstateItem for the associated path""" entry = self._map.get(path) @@ -440,8 +416,8 @@ class dirstate(object): for a in ("_map", "_branch", "_ignore"): if a in self.__dict__: delattr(self, a) - self._lastnormaltime = timestamp.zero() self._dirty = False + self._dirty_tracked_set = False self._parentwriters = 0 self._origpl = None @@ -462,19 +438,26 @@ class dirstate(object): return self._map.copymap @requires_no_parents_change - def set_tracked(self, filename): + def set_tracked(self, filename, reset_copy=False): """a "public" method for generic code to mark a file as tracked This function is to be called outside of "update/merge" case. For example by a command like `hg add X`. + if reset_copy is set, any existing copy information will be dropped. + return True the file was previously untracked, False otherwise. """ self._dirty = True entry = self._map.get(filename) if entry is None or not entry.tracked: self._check_new_tracked_filename(filename) - return self._map.set_tracked(filename) + pre_tracked = self._map.set_tracked(filename) + if reset_copy: + self._map.copymap.pop(filename, None) + if pre_tracked: + self._dirty_tracked_set = True + return pre_tracked @requires_no_parents_change def set_untracked(self, filename): @@ -488,24 +471,17 @@ class dirstate(object): ret = self._map.set_untracked(filename) if ret: self._dirty = True + self._dirty_tracked_set = True return ret @requires_no_parents_change - def set_clean(self, filename, parentfiledata=None): + def set_clean(self, filename, parentfiledata): """record that the current state of the file on disk is known to be clean""" self._dirty = True - if parentfiledata: - (mode, size, mtime) = parentfiledata - else: - (mode, size, mtime) = self._get_filedata(filename) if not self._map[filename].tracked: self._check_new_tracked_filename(filename) + (mode, size, mtime) = parentfiledata self._map.set_clean(filename, mode, size, mtime) - if mtime > self._lastnormaltime: - # Remember the most recent modification timeslot for status(), - # to make sure we won't miss future size-preserving file content - # modifications that happen within the same timeslot. - self._lastnormaltime = mtime @requires_no_parents_change def set_possibly_dirty(self, filename): @@ -544,10 +520,6 @@ class dirstate(object): if entry is not None and entry.added: return # avoid dropping copy information (maybe?) - parentfiledata = None - if wc_tracked and p1_tracked: - parentfiledata = self._get_filedata(filename) - self._map.reset_state( filename, wc_tracked, @@ -555,16 +527,7 @@ class dirstate(object): # the underlying reference might have changed, we will have to # check it. has_meaningful_mtime=False, - parentfiledata=parentfiledata, ) - if ( - parentfiledata is not None - and parentfiledata[2] > self._lastnormaltime - ): - # Remember the most recent modification timeslot for status(), - # to make sure we won't miss future size-preserving file content - # modifications that happen within the same timeslot. - self._lastnormaltime = parentfiledata[2] @requires_parents_change def update_file( @@ -593,13 +556,13 @@ class dirstate(object): # this. The test agrees self._dirty = True - - need_parent_file_data = ( - not possibly_dirty and not p2_info and wc_tracked and p1_tracked - ) - - if need_parent_file_data and parentfiledata is None: - parentfiledata = self._get_filedata(filename) + old_entry = self._map.get(filename) + if old_entry is None: + prev_tracked = False + else: + prev_tracked = old_entry.tracked + if prev_tracked != wc_tracked: + self._dirty_tracked_set = True self._map.reset_state( filename, @@ -609,14 +572,6 @@ class dirstate(object): has_meaningful_mtime=not possibly_dirty, parentfiledata=parentfiledata, ) - if ( - parentfiledata is not None - and parentfiledata[2] > self._lastnormaltime - ): - # Remember the most recent modification timeslot for status(), - # to make sure we won't miss future size-preserving file content - # modifications that happen within the same timeslot. - self._lastnormaltime = parentfiledata[2] def _check_new_tracked_filename(self, filename): scmutil.checkfilename(filename) @@ -634,14 +589,6 @@ class dirstate(object): msg %= (pycompat.bytestr(d), pycompat.bytestr(filename)) raise error.Abort(msg) - def _get_filedata(self, filename): - """returns""" - s = os.lstat(self._join(filename)) - mode = s.st_mode - size = s.st_size - mtime = timestamp.mtime_of(s) - return (mode, size, mtime) - def _discoverpath(self, path, normed, ignoremissing, exists, storemap): if exists is None: exists = os.path.lexists(os.path.join(self._root, path)) @@ -720,7 +667,6 @@ class dirstate(object): def clear(self): self._map.clear() - self._lastnormaltime = timestamp.zero() self._dirty = True def rebuild(self, parent, allfiles, changedfiles=None): @@ -728,9 +674,7 @@ class dirstate(object): # Rebuild entire dirstate to_lookup = allfiles to_drop = [] - lastnormaltime = self._lastnormaltime self.clear() - self._lastnormaltime = lastnormaltime elif len(changedfiles) < 10: # Avoid turning allfiles into a set, which can be expensive if it's # large. @@ -777,28 +721,41 @@ class dirstate(object): if not self._dirty: return - filename = self._filename + write_key = self._use_tracked_hint and self._dirty_tracked_set if tr: - # 'dirstate.write()' is not only for writing in-memory - # changes out, but also for dropping ambiguous timestamp. - # delayed writing re-raise "ambiguous timestamp issue". - # See also the wiki page below for detail: - # https://www.mercurial-scm.org/wiki/DirstateTransactionPlan - - # record when mtime start to be ambiguous - now = _getfsnow(self._opener) - # delay writing in-memory changes out tr.addfilegenerator( - b'dirstate', + b'dirstate-1-main', (self._filename,), - lambda f: self._writedirstate(tr, f, now=now), + lambda f: self._writedirstate(tr, f), location=b'plain', + post_finalize=True, ) + if write_key: + tr.addfilegenerator( + b'dirstate-2-key-post', + (self._filename_th,), + lambda f: self._write_tracked_hint(tr, f), + location=b'plain', + post_finalize=True, + ) return - st = self._opener(filename, b"w", atomictemp=True, checkambig=True) - self._writedirstate(tr, st) + file = lambda f: self._opener(f, b"w", atomictemp=True, checkambig=True) + with file(self._filename) as f: + self._writedirstate(tr, f) + if write_key: + # we update the key-file after writing to make sure reader have a + # key that match the newly written content + with file(self._filename_th) as f: + self._write_tracked_hint(tr, f) + + def delete_tracked_hint(self): + """remove the tracked_hint file + + To be used by format downgrades operation""" + self._opener.unlink(self._filename_th) + self._use_tracked_hint = False def addparentchangecallback(self, category, callback): """add a callback to be called when the wd parents are changed @@ -811,7 +768,7 @@ class dirstate(object): """ self._plchangecallbacks[category] = callback - def _writedirstate(self, tr, st, now=None): + def _writedirstate(self, tr, st): # notify callbacks about parents change if self._origpl is not None and self._origpl != self._pl: for c, callback in sorted( @@ -819,34 +776,13 @@ class dirstate(object): ): callback(self, self._origpl, self._pl) self._origpl = None - - if now is None: - # use the modification time of the newly created temporary file as the - # filesystem's notion of 'now' - now = timestamp.mtime_of(util.fstat(st)) + self._map.write(tr, st) + self._dirty = False + self._dirty_tracked_set = False - # enough 'delaywrite' prevents 'pack_dirstate' from dropping - # timestamp of each entries in dirstate, because of 'now > mtime' - delaywrite = self._ui.configint(b'debug', b'dirstate.delaywrite') - if delaywrite > 0: - # do we have any files to delay for? - for f, e in pycompat.iteritems(self._map): - if e.need_delay(now): - import time # to avoid useless import - - # rather than sleep n seconds, sleep until the next - # multiple of n seconds - clock = time.time() - start = int(clock) - (int(clock) % delaywrite) - end = start + delaywrite - time.sleep(end - clock) - # trust our estimate that the end is near now - now = timestamp.timestamp((end, 0)) - break - - self._map.write(tr, st, now) - self._lastnormaltime = timestamp.zero() - self._dirty = False + def _write_tracked_hint(self, tr, f): + key = node.hex(uuid.uuid4().bytes) + f.write(b"1\n%s\n" % key) # 1 is the format version def _dirignore(self, f): if self._ignore(f): @@ -1243,7 +1179,6 @@ class dirstate(object): self._rootdir, self._ignorefiles(), self._checkexec, - self._lastnormaltime, bool(list_clean), bool(list_ignored), bool(list_unknown), @@ -1335,11 +1270,20 @@ class dirstate(object): # Some matchers have yet to be implemented use_rust = False + # Get the time from the filesystem so we can disambiguate files that + # appear modified in the present or future. + try: + mtime_boundary = timestamp.get_fs_now(self._opener) + except OSError: + # In largefiles or readonly context + mtime_boundary = None + if use_rust: try: - return self._rust_status( + res = self._rust_status( match, listclean, listignored, listunknown ) + return res + (mtime_boundary,) except rustmod.FallbackError: pass @@ -1361,7 +1305,6 @@ class dirstate(object): checkexec = self._checkexec checklink = self._checklink copymap = self._map.copymap - lastnormaltime = self._lastnormaltime # We need to do full walks when either # - we're listing all clean files, or @@ -1417,19 +1360,17 @@ class dirstate(object): else: madd(fn) elif not t.mtime_likely_equal_to(timestamp.mtime_of(st)): - ladd(fn) - elif timestamp.mtime_of(st) == lastnormaltime: - # fn may have just been marked as normal and it may have - # changed in the same second without changing its size. - # This can happen if we quickly do multiple commits. - # Force lookup, so we don't miss such a racy file change. + # There might be a change in the future if for example the + # internal clock is off, but this is a case where the issues + # the user would face would be a lot worse and there is + # nothing we can really do. ladd(fn) elif listclean: cadd(fn) status = scmutil.status( modified, added, removed, deleted, unknown, ignored, clean ) - return (lookup, status) + return (lookup, status, mtime_boundary) def matches(self, match): """ @@ -1477,10 +1418,11 @@ class dirstate(object): # changes written out above, even if dirstate is never # changed after this tr.addfilegenerator( - b'dirstate', + b'dirstate-1-main', (self._filename,), lambda f: self._writedirstate(tr, f), location=b'plain', + post_finalize=True, ) # ensure that pending file written above is unlinked at diff --git a/mercurial/dirstatemap.py b/mercurial/dirstatemap.py --- a/mercurial/dirstatemap.py +++ b/mercurial/dirstatemap.py @@ -444,13 +444,13 @@ class dirstatemap(_dirstatemapcommon): self.__getitem__ = self._map.__getitem__ self.get = self._map.get - def write(self, tr, st, now): + def write(self, tr, st): if self._use_dirstate_v2: - packed, meta = v2.pack_dirstate(self._map, self.copymap, now) + packed, meta = v2.pack_dirstate(self._map, self.copymap) self.write_v2_no_append(tr, st, meta, packed) else: packed = parsers.pack_dirstate( - self._map, self.copymap, self.parents(), now + self._map, self.copymap, self.parents() ) st.write(packed) st.close() @@ -655,10 +655,10 @@ if rustmod is not None: self._map return self.identity - def write(self, tr, st, now): + def write(self, tr, st): if not self._use_dirstate_v2: p1, p2 = self.parents() - packed = self._map.write_v1(p1, p2, now) + packed = self._map.write_v1(p1, p2) st.write(packed) st.close() self._dirtyparents = False @@ -666,7 +666,7 @@ if rustmod is not None: # We can only append to an existing data file if there is one can_append = self.docket.uuid is not None - packed, meta, append = self._map.write_v2(now, can_append) + packed, meta, append = self._map.write_v2(can_append) if append: docket = self.docket data_filename = docket.data_filename() diff --git a/mercurial/dirstateutils/timestamp.py b/mercurial/dirstateutils/timestamp.py --- a/mercurial/dirstateutils/timestamp.py +++ b/mercurial/dirstateutils/timestamp.py @@ -6,8 +6,11 @@ from __future__ import absolute_import import functools +import os import stat +from .. import error + rangemask = 0x7FFFFFFF @@ -18,40 +21,45 @@ class timestamp(tuple): A Unix timestamp with optional nanoseconds precision, modulo 2**31 seconds. - A 2-tuple containing: + A 3-tuple containing: `truncated_seconds`: seconds since the Unix epoch, truncated to its lower 31 bits `subsecond_nanoseconds`: number of nanoseconds since `truncated_seconds`. When this is zero, the sub-second precision is considered unknown. + + `second_ambiguous`: whether this timestamp is still "reliable" + (see `reliable_mtime_of`) if we drop its sub-second component. """ def __new__(cls, value): - truncated_seconds, subsec_nanos = value - value = (truncated_seconds & rangemask, subsec_nanos) + truncated_seconds, subsec_nanos, second_ambiguous = value + value = (truncated_seconds & rangemask, subsec_nanos, second_ambiguous) return super(timestamp, cls).__new__(cls, value) def __eq__(self, other): - self_secs, self_subsec_nanos = self - other_secs, other_subsec_nanos = other - return self_secs == other_secs and ( - self_subsec_nanos == other_subsec_nanos - or self_subsec_nanos == 0 - or other_subsec_nanos == 0 + raise error.ProgrammingError( + 'timestamp should never be compared directly' ) def __gt__(self, other): - self_secs, self_subsec_nanos = self - other_secs, other_subsec_nanos = other - if self_secs > other_secs: - return True - if self_secs < other_secs: - return False - if self_subsec_nanos == 0 or other_subsec_nanos == 0: - # they are considered equal, so not "greater than" - return False - return self_subsec_nanos > other_subsec_nanos + raise error.ProgrammingError( + 'timestamp should never be compared directly' + ) + + +def get_fs_now(vfs): + """return a timestamp for "now" in the current vfs + + This will raise an exception if no temporary files could be created. + """ + tmpfd, tmpname = vfs.mkstemp() + try: + return mtime_of(os.fstat(tmpfd)) + finally: + os.close(tmpfd) + vfs.unlink(tmpname) def zero(): @@ -84,4 +92,37 @@ def mtime_of(stat_result): secs = nanos // billion subsec_nanos = nanos % billion - return timestamp((secs, subsec_nanos)) + return timestamp((secs, subsec_nanos, False)) + + +def reliable_mtime_of(stat_result, present_mtime): + """Same as `mtime_of`, but return `None` or a `Timestamp` with + `second_ambiguous` set if the date might be ambiguous. + + A modification time is reliable if it is older than "present_time" (or + sufficiently in the future). + + Otherwise a concurrent modification might happens with the same mtime. + """ + file_mtime = mtime_of(stat_result) + file_second = file_mtime[0] + file_ns = file_mtime[1] + boundary_second = present_mtime[0] + boundary_ns = present_mtime[1] + # If the mtime of the ambiguous file is younger (or equal) to the starting + # point of the `status` walk, we cannot garantee that another, racy, write + # will not happen right after with the same mtime and we cannot cache the + # information. + # + # However if the mtime is far away in the future, this is likely some + # mismatch between the current clock and previous file system operation. So + # mtime more than one days in the future are considered fine. + if boundary_second == file_second: + if file_ns and boundary_ns: + if file_ns < boundary_ns: + return timestamp((file_second, file_ns, True)) + return None + elif boundary_second < file_second < (3600 * 24 + boundary_second): + return None + else: + return file_mtime diff --git a/mercurial/dirstateutils/v2.py b/mercurial/dirstateutils/v2.py --- a/mercurial/dirstateutils/v2.py +++ b/mercurial/dirstateutils/v2.py @@ -174,12 +174,10 @@ class Node(object): ) -def pack_dirstate(map, copy_map, now): +def pack_dirstate(map, copy_map): """ Pack `map` and `copy_map` into the dirstate v2 binary format and return the bytearray. - `now` is a timestamp of the current filesystem time used to detect race - conditions in writing the dirstate to disk, see inline comment. The on-disk format expects a tree-like structure where the leaves are written first (and sorted per-directory), going up levels until the root @@ -284,17 +282,6 @@ def pack_dirstate(map, copy_map, now): stack.append(current_node) for index, (path, entry) in enumerate(sorted_map, 1): - if entry.need_delay(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. - entry.set_possibly_dirty() nodes_with_entry_count += 1 if path in copy_map: nodes_with_copy_source_count += 1 diff --git a/mercurial/discovery.py b/mercurial/discovery.py --- a/mercurial/discovery.py +++ b/mercurial/discovery.py @@ -19,6 +19,7 @@ from . import ( bookmarks, branchmap, error, + obsolete, phases, pycompat, scmutil, @@ -141,17 +142,6 @@ class outgoing(object): self._computecommonmissing() return self._missing - @property - def missingheads(self): - util.nouideprecwarn( - b'outgoing.missingheads never contained what the name suggests and ' - b'was renamed to outgoing.ancestorsof. check your code for ' - b'correctness.', - b'5.5', - stacklevel=2, - ) - return self.ancestorsof - def findcommonoutgoing( repo, other, onlyheads=None, force=False, commoninc=None, portable=False @@ -556,12 +546,16 @@ def _postprocessobsolete(pushop, futurec if len(localcandidate) == 1: return unknownheads | set(candidate_newhs), set() + obsrevs = obsolete.getrevs(unfi, b'obsolete') + futurenonobsolete = frozenset(futurecommon) - obsrevs + # actually process branch replacement while localcandidate: nh = localcandidate.pop() + r = torev(nh) current_branch = unfi[nh].branch() # run this check early to skip the evaluation of the whole branch - if torev(nh) in futurecommon or ispublic(torev(nh)): + if ispublic(r) or r not in obsrevs: newhs.add(nh) continue @@ -583,7 +577,7 @@ def _postprocessobsolete(pushop, futurec # * if we have no markers to push to obsolete it. if ( any(ispublic(r) for r in branchrevs) - or any(torev(n) in futurecommon for n in branchnodes) + or any(torev(n) in futurenonobsolete for n in branchnodes) or any(not hasoutmarker(n) for n in branchnodes) ): newhs.add(nh) diff --git a/mercurial/encoding.py b/mercurial/encoding.py --- a/mercurial/encoding.py +++ b/mercurial/encoding.py @@ -511,17 +511,21 @@ def trim(s, width, ellipsis=b'', leftsid if width <= 0: # no enough room even for ellipsis return ellipsis[: width + len(ellipsis)] + chars = list(u) if leftside: - uslice = lambda i: u[i:] - concat = lambda s: ellipsis + s - else: - uslice = lambda i: u[:-i] - concat = lambda s: s + ellipsis - for i in pycompat.xrange(1, len(u)): - usub = uslice(i) - if ucolwidth(usub) <= width: - return concat(usub.encode(_sysstr(encoding))) - return ellipsis # no enough room for multi-column characters + chars.reverse() + width_so_far = 0 + for i, c in enumerate(chars): + width_so_far += ucolwidth(c) + if width_so_far > width: + break + chars = chars[:i] + if leftside: + chars.reverse() + u = u''.join(chars).encode(_sysstr(encoding)) + if leftside: + return ellipsis + u + return u + ellipsis class normcasespecs(object): diff --git a/mercurial/error.py b/mercurial/error.py --- a/mercurial/error.py +++ b/mercurial/error.py @@ -388,6 +388,14 @@ class PatchError(Exception): __bytes__ = _tobytes +class PatchParseError(PatchError): + __bytes__ = _tobytes + + +class PatchApplicationError(PatchError): + __bytes__ = _tobytes + + def getsimilar(symbols, value): # type: (Iterable[bytes], bytes) -> List[bytes] sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio() diff --git a/mercurial/exchange.py b/mercurial/exchange.py --- a/mercurial/exchange.py +++ b/mercurial/exchange.py @@ -22,7 +22,6 @@ from . import ( changegroup, discovery, error, - exchangev2, lock as lockmod, logexchange, narrowspec, @@ -522,8 +521,16 @@ def _pushdiscovery(pushop): def _checksubrepostate(pushop): """Ensure all outgoing referenced subrepo revisions are present locally""" + + repo = pushop.repo + + # If the repository does not use subrepos, skip the expensive + # manifest checks. + if not len(repo.file(b'.hgsub')) or not len(repo.file(b'.hgsubstate')): + return + for n in pushop.outgoing.missing: - ctx = pushop.repo[n] + ctx = repo[n] if b'.hgsub' in ctx.manifest() and b'.hgsubstate' in ctx.files(): for subpath in sorted(ctx.substate): @@ -1666,21 +1673,17 @@ def pull( ): add_confirm_callback(repo, pullop) - # Use the modern wire protocol, if available. - if remote.capable(b'command-changesetdata'): - exchangev2.pull(pullop) - else: - # This should ideally be in _pullbundle2(). However, it needs to run - # before discovery to avoid extra work. - _maybeapplyclonebundle(pullop) - streamclone.maybeperformlegacystreamclone(pullop) - _pulldiscovery(pullop) - if pullop.canusebundle2: - _fullpullbundle2(repo, pullop) - _pullchangeset(pullop) - _pullphase(pullop) - _pullbookmarks(pullop) - _pullobsolete(pullop) + # This should ideally be in _pullbundle2(). However, it needs to run + # before discovery to avoid extra work. + _maybeapplyclonebundle(pullop) + streamclone.maybeperformlegacystreamclone(pullop) + _pulldiscovery(pullop) + if pullop.canusebundle2: + _fullpullbundle2(repo, pullop) + _pullchangeset(pullop) + _pullphase(pullop) + _pullbookmarks(pullop) + _pullobsolete(pullop) # storing remotenames if repo.ui.configbool(b'experimental', b'remotenames'): diff --git a/mercurial/exchangev2.py b/mercurial/exchangev2.py deleted file mode 100644 --- a/mercurial/exchangev2.py +++ /dev/null @@ -1,804 +0,0 @@ -# exchangev2.py - repository exchange for wire protocol version 2 -# -# Copyright 2018 Gregory Szorc -# -# 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 weakref - -from .i18n import _ -from .node import short -from . import ( - bookmarks, - error, - mdiff, - narrowspec, - phases, - pycompat, - requirements as requirementsmod, - setdiscovery, -) -from .interfaces import repository - - -def pull(pullop): - """Pull using wire protocol version 2.""" - repo = pullop.repo - remote = pullop.remote - - usingrawchangelogandmanifest = _checkuserawstorefiledata(pullop) - - # If this is a clone and it was requested to perform a "stream clone", - # we obtain the raw files data from the remote then fall back to an - # incremental pull. This is somewhat hacky and is not nearly robust enough - # for long-term usage. - if usingrawchangelogandmanifest: - with repo.transaction(b'clone'): - _fetchrawstorefiles(repo, remote) - repo.invalidate(clearfilecache=True) - - tr = pullop.trmanager.transaction() - - # We don't use the repo's narrow matcher here because the patterns passed - # to exchange.pull() could be different. - narrowmatcher = narrowspec.match( - repo.root, - # Empty maps to nevermatcher. So always - # set includes if missing. - pullop.includepats or {b'path:.'}, - pullop.excludepats, - ) - - if pullop.includepats or pullop.excludepats: - pathfilter = {} - if pullop.includepats: - pathfilter[b'include'] = sorted(pullop.includepats) - if pullop.excludepats: - pathfilter[b'exclude'] = sorted(pullop.excludepats) - else: - pathfilter = None - - # Figure out what needs to be fetched. - common, fetch, remoteheads = _pullchangesetdiscovery( - repo, remote, pullop.heads, abortwhenunrelated=pullop.force - ) - - # And fetch the data. - pullheads = pullop.heads or remoteheads - csetres = _fetchchangesets(repo, tr, remote, common, fetch, pullheads) - - # New revisions are written to the changelog. But all other updates - # are deferred. Do those now. - - # Ensure all new changesets are draft by default. If the repo is - # publishing, the phase will be adjusted by the loop below. - if csetres[b'added']: - phases.registernew( - repo, tr, phases.draft, [repo[n].rev() for n in csetres[b'added']] - ) - - # And adjust the phase of all changesets accordingly. - for phasenumber, phase in phases.phasenames.items(): - if phase == b'secret' or not csetres[b'nodesbyphase'][phase]: - continue - - phases.advanceboundary( - repo, - tr, - phasenumber, - csetres[b'nodesbyphase'][phase], - ) - - # Write bookmark updates. - bookmarks.updatefromremote( - repo.ui, - repo, - csetres[b'bookmarks'], - remote.url(), - pullop.gettransaction, - explicit=pullop.explicitbookmarks, - ) - - manres = _fetchmanifests(repo, tr, remote, csetres[b'manifestnodes']) - - # We don't properly support shallow changeset and manifest yet. So we apply - # depth limiting locally. - if pullop.depth: - relevantcsetnodes = set() - clnode = repo.changelog.node - - for rev in repo.revs( - b'ancestors(%ln, %s)', pullheads, pullop.depth - 1 - ): - relevantcsetnodes.add(clnode(rev)) - - csetrelevantfilter = lambda n: n in relevantcsetnodes - - else: - csetrelevantfilter = lambda n: True - - # If obtaining the raw store files, we need to scan the full repo to - # derive all the changesets, manifests, and linkrevs. - if usingrawchangelogandmanifest: - csetsforfiles = [] - mnodesforfiles = [] - manifestlinkrevs = {} - - for rev in repo: - ctx = repo[rev] - node = ctx.node() - - if not csetrelevantfilter(node): - continue - - mnode = ctx.manifestnode() - - csetsforfiles.append(node) - mnodesforfiles.append(mnode) - manifestlinkrevs[mnode] = rev - - else: - csetsforfiles = [n for n in csetres[b'added'] if csetrelevantfilter(n)] - mnodesforfiles = manres[b'added'] - manifestlinkrevs = manres[b'linkrevs'] - - # Find all file nodes referenced by added manifests and fetch those - # revisions. - fnodes = _derivefilesfrommanifests(repo, narrowmatcher, mnodesforfiles) - _fetchfilesfromcsets( - repo, - tr, - remote, - pathfilter, - fnodes, - csetsforfiles, - manifestlinkrevs, - shallow=bool(pullop.depth), - ) - - -def _checkuserawstorefiledata(pullop): - """Check whether we should use rawstorefiledata command to retrieve data.""" - - repo = pullop.repo - remote = pullop.remote - - # Command to obtain raw store data isn't available. - if b'rawstorefiledata' not in remote.apidescriptor[b'commands']: - return False - - # Only honor if user requested stream clone operation. - if not pullop.streamclonerequested: - return False - - # Only works on empty repos. - if len(repo): - return False - - # 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 requirementsmod.REVLOGV1_REQUIREMENT not in repo.requirements: - return False - - return True - - -def _fetchrawstorefiles(repo, remote): - with remote.commandexecutor() as e: - objs = e.callcommand( - b'rawstorefiledata', - { - b'files': [b'changelog', b'manifestlog'], - }, - ).result() - - # First object is a summary of files data that follows. - overall = next(objs) - - progress = repo.ui.makeprogress( - _(b'clone'), total=overall[b'totalsize'], unit=_(b'bytes') - ) - with progress: - progress.update(0) - - # Next are pairs of file metadata, data. - while True: - try: - filemeta = next(objs) - except StopIteration: - break - - for k in (b'location', b'path', b'size'): - if k not in filemeta: - raise error.Abort( - _(b'remote file data missing key: %s') % k - ) - - if filemeta[b'location'] == b'store': - vfs = repo.svfs - else: - raise error.Abort( - _(b'invalid location for raw file data: %s') - % filemeta[b'location'] - ) - - bytesremaining = filemeta[b'size'] - - with vfs.open(filemeta[b'path'], b'wb') as fh: - while True: - try: - chunk = next(objs) - except StopIteration: - break - - bytesremaining -= len(chunk) - - if bytesremaining < 0: - raise error.Abort( - _( - b'received invalid number of bytes for file ' - b'data; expected %d, got extra' - ) - % filemeta[b'size'] - ) - - progress.increment(step=len(chunk)) - fh.write(chunk) - - try: - if chunk.islast: - break - except AttributeError: - raise error.Abort( - _( - b'did not receive indefinite length bytestring ' - b'for file data' - ) - ) - - if bytesremaining: - raise error.Abort( - _( - b'received invalid number of bytes for' - b'file data; expected %d got %d' - ) - % ( - filemeta[b'size'], - filemeta[b'size'] - bytesremaining, - ) - ) - - -def _pullchangesetdiscovery(repo, remote, heads, abortwhenunrelated=True): - """Determine which changesets need to be pulled.""" - - if heads: - knownnode = repo.changelog.hasnode - if all(knownnode(head) for head in heads): - return heads, False, heads - - # TODO wire protocol version 2 is capable of more efficient discovery - # than setdiscovery. Consider implementing something better. - common, fetch, remoteheads = setdiscovery.findcommonheads( - repo.ui, repo, remote, abortwhenunrelated=abortwhenunrelated - ) - - common = set(common) - remoteheads = set(remoteheads) - - # If a remote head is filtered locally, put it back in the common set. - # See the comment in exchange._pulldiscoverychangegroup() for more. - - if fetch and remoteheads: - has_node = repo.unfiltered().changelog.index.has_node - - common |= {head for head in remoteheads if has_node(head)} - - if set(remoteheads).issubset(common): - fetch = [] - - common.discard(repo.nullid) - - return common, fetch, remoteheads - - -def _fetchchangesets(repo, tr, remote, common, fetch, remoteheads): - # TODO consider adding a step here where we obtain the DAG shape first - # (or ask the server to slice changesets into chunks for us) so that - # we can perform multiple fetches in batches. This will facilitate - # resuming interrupted clones, higher server-side cache hit rates due - # to smaller segments, etc. - with remote.commandexecutor() as e: - objs = e.callcommand( - b'changesetdata', - { - b'revisions': [ - { - b'type': b'changesetdagrange', - b'roots': sorted(common), - b'heads': sorted(remoteheads), - } - ], - b'fields': {b'bookmarks', b'parents', b'phase', b'revision'}, - }, - ).result() - - # The context manager waits on all response data when exiting. So - # we need to remain in the context manager in order to stream data. - return _processchangesetdata(repo, tr, objs) - - -def _processchangesetdata(repo, tr, objs): - repo.hook(b'prechangegroup', throw=True, **pycompat.strkwargs(tr.hookargs)) - - urepo = repo.unfiltered() - cl = urepo.changelog - - cl.delayupdate(tr) - - # The first emitted object is a header describing the data that - # follows. - meta = next(objs) - - progress = repo.ui.makeprogress( - _(b'changesets'), unit=_(b'chunks'), total=meta.get(b'totalitems') - ) - - manifestnodes = {} - added = [] - - def linkrev(node): - repo.ui.debug(b'add changeset %s\n' % short(node)) - # Linkrev for changelog is always self. - return len(cl) - - def ondupchangeset(cl, rev): - added.append(cl.node(rev)) - - def onchangeset(cl, rev): - progress.increment() - - 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[rev] = revision.manifest - - repo.register_changeset(rev, revision) - - nodesbyphase = {phase: set() for phase in phases.phasenames.values()} - remotebookmarks = {} - - # addgroup() expects a 7-tuple describing revisions. This normalizes - # the wire data to that format. - # - # This loop also aggregates non-revision metadata, such as phase - # data. - def iterrevisions(): - for cset in objs: - node = cset[b'node'] - - if b'phase' in cset: - nodesbyphase[cset[b'phase']].add(node) - - for mark in cset.get(b'bookmarks', []): - remotebookmarks[mark] = node - - # TODO add mechanism for extensions to examine records so they - # can siphon off custom data fields. - - extrafields = {} - - for field, size in cset.get(b'fieldsfollowing', []): - extrafields[field] = next(objs) - - # Some entries might only be metadata only updates. - if b'revision' not in extrafields: - continue - - data = extrafields[b'revision'] - - yield ( - node, - cset[b'parents'][0], - cset[b'parents'][1], - # Linknode is always itself for changesets. - cset[b'node'], - # We always send full revisions. So delta base is not set. - repo.nullid, - 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, - ) - - progress.complete() - - return { - b'added': added, - b'nodesbyphase': nodesbyphase, - b'bookmarks': remotebookmarks, - b'manifestnodes': manifestnodes, - } - - -def _fetchmanifests(repo, tr, remote, manifestnodes): - rootmanifest = repo.manifestlog.getstorage(b'') - - # Some manifests can be shared between changesets. Filter out revisions - # we already know about. - fetchnodes = [] - linkrevs = {} - seen = set() - - for clrev, node in sorted(pycompat.iteritems(manifestnodes)): - if node in seen: - continue - - try: - rootmanifest.rev(node) - except error.LookupError: - fetchnodes.append(node) - linkrevs[node] = clrev - - seen.add(node) - - # TODO handle tree manifests - - # addgroup() expects 7-tuple describing revisions. This normalizes - # the wire data to that format. - def iterrevisions(objs, progress): - for manifest in objs: - node = manifest[b'node'] - - extrafields = {} - - for field, size in manifest.get(b'fieldsfollowing', []): - extrafields[field] = next(objs) - - if b'delta' in extrafields: - basenode = manifest[b'deltabasenode'] - delta = extrafields[b'delta'] - elif b'revision' in extrafields: - basenode = repo.nullid - revision = extrafields[b'revision'] - delta = mdiff.trivialdiffheader(len(revision)) + revision - else: - continue - - yield ( - node, - manifest[b'parents'][0], - manifest[b'parents'][1], - # The value passed in is passed to the lookup function passed - # to addgroup(). We already have a map of manifest node to - # changelog revision number. So we just pass in the - # manifest node here and use linkrevs.__getitem__ as the - # resolution function. - node, - basenode, - delta, - # Flags not yet supported. - 0, - # Sidedata not yet supported. - {}, - ) - - progress.increment() - - progress = repo.ui.makeprogress( - _(b'manifests'), unit=_(b'chunks'), total=len(fetchnodes) - ) - - commandmeta = remote.apidescriptor[b'commands'][b'manifestdata'] - batchsize = commandmeta.get(b'recommendedbatchsize', 10000) - # TODO make size configurable on client? - - # We send commands 1 at a time to the remote. This is not the most - # efficient because we incur a round trip at the end of each batch. - # However, the existing frame-based reactor keeps consuming server - # data in the background. And this results in response data buffering - # in memory. This can consume gigabytes of memory. - # TODO send multiple commands in a request once background buffering - # issues are resolved. - - added = [] - - for i in pycompat.xrange(0, len(fetchnodes), batchsize): - batch = [node for node in fetchnodes[i : i + batchsize]] - if not batch: - continue - - with remote.commandexecutor() as e: - objs = e.callcommand( - b'manifestdata', - { - b'tree': b'', - b'nodes': batch, - b'fields': {b'parents', b'revision'}, - b'haveparents': True, - }, - ).result() - - # Chomp off header object. - next(objs) - - def onchangeset(cl, rev): - added.append(cl.node(rev)) - - rootmanifest.addgroup( - iterrevisions(objs, progress), - linkrevs.__getitem__, - weakref.proxy(tr), - addrevisioncb=onchangeset, - duplicaterevisioncb=onchangeset, - ) - - progress.complete() - - return { - b'added': added, - b'linkrevs': linkrevs, - } - - -def _derivefilesfrommanifests(repo, matcher, manifestnodes): - """Determine what file nodes are relevant given a set of manifest nodes. - - Returns a dict mapping file paths to dicts of file node to first manifest - node. - """ - ml = repo.manifestlog - fnodes = collections.defaultdict(dict) - - progress = repo.ui.makeprogress( - _(b'scanning manifests'), total=len(manifestnodes) - ) - - with progress: - for manifestnode in manifestnodes: - m = ml.get(b'', manifestnode) - - # TODO this will pull in unwanted nodes because it takes the storage - # delta into consideration. What we really want is something that - # takes the delta between the manifest's parents. And ideally we - # would ignore file nodes that are known locally. For now, ignore - # both these limitations. This will result in incremental fetches - # requesting data we already have. So this is far from ideal. - md = m.readfast() - - for path, fnode in md.items(): - if matcher(path): - fnodes[path].setdefault(fnode, manifestnode) - - progress.increment() - - return fnodes - - -def _fetchfiles(repo, tr, remote, fnodes, linkrevs): - """Fetch file data from explicit file revisions.""" - - def iterrevisions(objs, progress): - for filerevision in objs: - node = filerevision[b'node'] - - extrafields = {} - - for field, size in filerevision.get(b'fieldsfollowing', []): - extrafields[field] = next(objs) - - if b'delta' in extrafields: - basenode = filerevision[b'deltabasenode'] - delta = extrafields[b'delta'] - elif b'revision' in extrafields: - basenode = repo.nullid - revision = extrafields[b'revision'] - delta = mdiff.trivialdiffheader(len(revision)) + revision - else: - continue - - yield ( - node, - filerevision[b'parents'][0], - filerevision[b'parents'][1], - node, - basenode, - delta, - # Flags not yet supported. - 0, - # Sidedata not yet supported. - {}, - ) - - progress.increment() - - progress = repo.ui.makeprogress( - _(b'files'), - unit=_(b'chunks'), - total=sum(len(v) for v in pycompat.itervalues(fnodes)), - ) - - # TODO make batch size configurable - batchsize = 10000 - fnodeslist = [x for x in sorted(fnodes.items())] - - for i in pycompat.xrange(0, len(fnodeslist), batchsize): - batch = [x for x in fnodeslist[i : i + batchsize]] - if not batch: - continue - - with remote.commandexecutor() as e: - fs = [] - locallinkrevs = {} - - for path, nodes in batch: - fs.append( - ( - path, - e.callcommand( - b'filedata', - { - b'path': path, - b'nodes': sorted(nodes), - b'fields': {b'parents', b'revision'}, - b'haveparents': True, - }, - ), - ) - ) - - locallinkrevs[path] = { - node: linkrevs[manifestnode] - for node, manifestnode in pycompat.iteritems(nodes) - } - - for path, f in fs: - objs = f.result() - - # Chomp off header objects. - next(objs) - - store = repo.file(path) - store.addgroup( - iterrevisions(objs, progress), - locallinkrevs[path].__getitem__, - weakref.proxy(tr), - ) - - -def _fetchfilesfromcsets( - repo, tr, remote, pathfilter, fnodes, csets, manlinkrevs, shallow=False -): - """Fetch file data from explicit changeset revisions.""" - - def iterrevisions(objs, remaining, progress): - while remaining: - filerevision = next(objs) - - node = filerevision[b'node'] - - extrafields = {} - - for field, size in filerevision.get(b'fieldsfollowing', []): - extrafields[field] = next(objs) - - if b'delta' in extrafields: - basenode = filerevision[b'deltabasenode'] - delta = extrafields[b'delta'] - elif b'revision' in extrafields: - basenode = repo.nullid - revision = extrafields[b'revision'] - delta = mdiff.trivialdiffheader(len(revision)) + revision - else: - continue - - if b'linknode' in filerevision: - linknode = filerevision[b'linknode'] - else: - linknode = node - - yield ( - node, - filerevision[b'parents'][0], - filerevision[b'parents'][1], - linknode, - basenode, - delta, - # Flags not yet supported. - 0, - # Sidedata not yet supported. - {}, - ) - - progress.increment() - remaining -= 1 - - progress = repo.ui.makeprogress( - _(b'files'), - unit=_(b'chunks'), - total=sum(len(v) for v in pycompat.itervalues(fnodes)), - ) - - commandmeta = remote.apidescriptor[b'commands'][b'filesdata'] - batchsize = commandmeta.get(b'recommendedbatchsize', 50000) - - shallowfiles = repository.REPO_FEATURE_SHALLOW_FILE_STORAGE in repo.features - fields = {b'parents', b'revision'} - clrev = repo.changelog.rev - - # There are no guarantees that we'll have ancestor revisions if - # a) this repo has shallow file storage b) shallow data fetching is enabled. - # Force remote to not delta against possibly unknown revisions when these - # conditions hold. - haveparents = not (shallowfiles or shallow) - - # Similarly, we may not have calculated linkrevs for all incoming file - # revisions. Ask the remote to do work for us in this case. - if not haveparents: - fields.add(b'linknode') - - for i in pycompat.xrange(0, len(csets), batchsize): - batch = [x for x in csets[i : i + batchsize]] - if not batch: - continue - - with remote.commandexecutor() as e: - args = { - b'revisions': [ - { - b'type': b'changesetexplicit', - b'nodes': batch, - } - ], - b'fields': fields, - b'haveparents': haveparents, - } - - if pathfilter: - args[b'pathfilter'] = pathfilter - - objs = e.callcommand(b'filesdata', args).result() - - # First object is an overall header. - overall = next(objs) - - # We have overall['totalpaths'] segments. - for i in pycompat.xrange(overall[b'totalpaths']): - header = next(objs) - - path = header[b'path'] - store = repo.file(path) - - linkrevs = { - fnode: manlinkrevs[mnode] - for fnode, mnode in pycompat.iteritems(fnodes[path]) - } - - def getlinkrev(node): - if node in linkrevs: - return linkrevs[node] - else: - return clrev(node) - - store.addgroup( - iterrevisions(objs, header[b'totalitems'], progress), - getlinkrev, - weakref.proxy(tr), - maybemissingparents=shallow, - ) diff --git a/mercurial/extensions.py b/mercurial/extensions.py --- a/mercurial/extensions.py +++ b/mercurial/extensions.py @@ -282,6 +282,7 @@ def loadall(ui, whitelist=None): result = ui.configitems(b"extensions") if whitelist is not None: result = [(k, v) for (k, v) in result if k in whitelist] + result = [(k, v) for (k, v) in result if b':' not in k] newindex = len(_order) ui.log( b'extension', @@ -290,6 +291,8 @@ def loadall(ui, whitelist=None): ) ui.log(b'extension', b'- processing %d entries\n', len(result)) with util.timedcm('load all extensions') as stats: + default_sub_options = ui.configsuboptions(b"extensions", b"*")[1] + for (name, path) in result: if path: if path[0:1] == b'!': @@ -306,18 +309,32 @@ def loadall(ui, whitelist=None): except Exception as inst: msg = stringutil.forcebytestr(inst) if path: - ui.warn( - _(b"*** failed to import extension %s from %s: %s\n") - % (name, path, msg) + error_msg = _( + b'failed to import extension "%s" from %s: %s' ) + error_msg %= (name, path, msg) else: - ui.warn( - _(b"*** failed to import extension %s: %s\n") - % (name, msg) - ) - if isinstance(inst, error.Hint) and inst.hint: - ui.warn(_(b"*** (%s)\n") % inst.hint) - ui.traceback() + error_msg = _(b'failed to import extension "%s": %s') + error_msg %= (name, msg) + + options = default_sub_options.copy() + ext_options = ui.configsuboptions(b"extensions", name)[1] + options.update(ext_options) + if stringutil.parsebool(options.get(b"required", b'no')): + hint = None + if isinstance(inst, error.Hint) and inst.hint: + hint = inst.hint + if hint is None: + hint = _( + b"loading of this extension was required, " + b"see `hg help config.extensions` for details" + ) + raise error.Abort(error_msg, hint=hint) + else: + ui.warn((b"*** %s\n") % error_msg) + if isinstance(inst, error.Hint) and inst.hint: + ui.warn(_(b"*** (%s)\n") % inst.hint) + ui.traceback() ui.log( b'extension', diff --git a/mercurial/filelog.py b/mercurial/filelog.py --- a/mercurial/filelog.py +++ b/mercurial/filelog.py @@ -97,8 +97,8 @@ class filelog(object): def iscensored(self, rev): return self._revlog.iscensored(rev) - def revision(self, node, _df=None, raw=False): - return self._revlog.revision(node, _df=_df, raw=raw) + def revision(self, node, _df=None): + return self._revlog.revision(node, _df=_df) def rawdata(self, node, _df=None): return self._revlog.rawdata(node, _df=_df) diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py --- a/mercurial/filemerge.py +++ b/mercurial/filemerge.py @@ -19,7 +19,6 @@ from .node import ( ) from .pycompat import ( getattr, - open, ) from . import ( @@ -293,9 +292,9 @@ def _eoltype(data): return None # unknown -def _matcheol(file, back): +def _matcheol(file, backup): """Convert EOL markers in a file to match origfile""" - tostyle = _eoltype(back.data()) # No repo.wread filters? + tostyle = _eoltype(backup.data()) # No repo.wread filters? if tostyle: data = util.readfile(file) style = _eoltype(data) @@ -306,27 +305,27 @@ def _matcheol(file, back): @internaltool(b'prompt', nomerge) -def _iprompt(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None): +def _iprompt(repo, mynode, local, other, base, toolconf): """Asks the user which of the local `p1()` or the other `p2()` version to keep as the merged version.""" ui = repo.ui - fd = fcd.path() + fd = local.fctx.path() uipathfn = scmutil.getuipathfn(repo) # Avoid prompting during an in-memory merge since it doesn't support merge # conflicts. - if fcd.changectx().isinmemory(): + if local.fctx.changectx().isinmemory(): raise error.InMemoryMergeConflictsError( b'in-memory merge does not support file conflicts' ) - prompts = partextras(labels) + prompts = partextras([local.label, other.label]) prompts[b'fd'] = uipathfn(fd) try: - if fco.isabsent(): + if other.fctx.isabsent(): index = ui.promptchoice(_localchangedotherdeletedmsg % prompts, 2) choice = [b'local', b'other', b'unresolved'][index] - elif fcd.isabsent(): + elif local.fctx.isabsent(): index = ui.promptchoice(_otherchangedlocaldeletedmsg % prompts, 2) choice = [b'other', b'local', b'unresolved'][index] else: @@ -347,44 +346,48 @@ def _iprompt(repo, mynode, orig, fcd, fc choice = [b'local', b'other', b'unresolved'][index] if choice == b'other': - return _iother(repo, mynode, orig, fcd, fco, fca, toolconf, labels) + return _iother(repo, mynode, local, other, base, toolconf) elif choice == b'local': - return _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf, labels) + return _ilocal(repo, mynode, local, other, base, toolconf) elif choice == b'unresolved': - return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, labels) + return _ifail(repo, mynode, local, other, base, toolconf) except error.ResponseExpected: ui.write(b"\n") - return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, labels) + return _ifail(repo, mynode, local, other, base, toolconf) @internaltool(b'local', nomerge) -def _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None): +def _ilocal(repo, mynode, local, other, base, toolconf): """Uses the local `p1()` version of files as the merged version.""" - return 0, fcd.isabsent() + return 0, local.fctx.isabsent() @internaltool(b'other', nomerge) -def _iother(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None): +def _iother(repo, mynode, local, other, base, toolconf): """Uses the other `p2()` version of files as the merged version.""" - if fco.isabsent(): + if other.fctx.isabsent(): # local changed, remote deleted -- 'deleted' picked - _underlyingfctxifabsent(fcd).remove() + _underlyingfctxifabsent(local.fctx).remove() deleted = True else: - _underlyingfctxifabsent(fcd).write(fco.data(), fco.flags()) + _underlyingfctxifabsent(local.fctx).write( + other.fctx.data(), other.fctx.flags() + ) deleted = False return 0, deleted @internaltool(b'fail', nomerge) -def _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None): +def _ifail(repo, mynode, local, other, base, toolconf): """ Rather than attempting to merge files that were modified on both branches, it marks them as unresolved. The resolve command must be used to resolve these conflicts.""" # for change/delete conflicts write out the changed version, then fail - if fcd.isabsent(): - _underlyingfctxifabsent(fcd).write(fco.data(), fco.flags()) + if local.fctx.isabsent(): + _underlyingfctxifabsent(local.fctx).write( + other.fctx.data(), other.fctx.flags() + ) return 1, False @@ -399,11 +402,18 @@ def _underlyingfctxifabsent(filectx): return filectx -def _premerge(repo, fcd, fco, fca, toolconf, files, labels=None): +def _verifytext(input, ui): + """verifies that text is non-binary""" + if stringutil.binary(input.text()): + msg = _(b"%s looks like a binary file.") % input.fctx.path() + ui.warn(_(b'warning: %s\n') % msg) + raise error.Abort(msg) + + +def _premerge(repo, local, other, base, toolconf): tool, toolpath, binary, symlink, scriptfn = toolconf - if symlink or fcd.isabsent() or fco.isabsent(): + if symlink or local.fctx.isabsent() or other.fctx.isabsent(): return 1 - unused, unused, unused, back = files ui = repo.ui @@ -423,26 +433,28 @@ def _premerge(repo, fcd, fco, fca, toolc if premerge: mode = b'merge' - if premerge in {b'keep-merge3', b'keep-mergediff'}: - if not labels: - labels = _defaultconflictlabels - if len(labels) < 3: - labels.append(b'base') - if premerge == b'keep-mergediff': - mode = b'mergediff' - r = simplemerge.simplemerge( - ui, fcd, fca, fco, quiet=True, label=labels, mode=mode + if premerge == b'keep-mergediff': + mode = b'mergediff' + elif premerge == b'keep-merge3': + mode = b'merge3' + if any( + stringutil.binary(input.text()) for input in (local, base, other) + ): + return 1 # continue merging + merged_text, conflicts = simplemerge.simplemerge( + local, base, other, mode=mode ) - if not r: + if not conflicts or premerge in validkeep: + # fcd.flags() already has the merged flags (done in + # mergestate.resolve()) + local.fctx.write(merged_text, local.fctx.flags()) + if not conflicts: ui.debug(b" premerge successful\n") return 0 - if premerge not in validkeep: - # restore from backup and try again - _restorebackup(fcd, back) return 1 # continue merging -def _mergecheck(repo, mynode, orig, fcd, fco, fca, toolconf): +def _mergecheck(repo, mynode, fcd, fco, fca, toolconf): tool, toolpath, binary, symlink, scriptfn = toolconf uipathfn = scmutil.getuipathfn(repo) if symlink: @@ -463,7 +475,7 @@ def _mergecheck(repo, mynode, orig, fcd, return True -def _merge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, mode): +def _merge(repo, local, other, base, mode): """ Uses the internal non-interactive simple merge algorithm for merging files. It will fail if there are any conflicts and leave markers in @@ -471,8 +483,20 @@ def _merge(repo, mynode, orig, fcd, fco, of merge, unless mode equals 'union' which suppresses the markers.""" ui = repo.ui - r = simplemerge.simplemerge(ui, fcd, fca, fco, label=labels, mode=mode) - return True, r, False + try: + _verifytext(local, ui) + _verifytext(base, ui) + _verifytext(other, ui) + except error.Abort: + return True, True, False + else: + merged_text, conflicts = simplemerge.simplemerge( + local, base, other, mode=mode + ) + # fcd.flags() already has the merged flags (done in + # mergestate.resolve()) + local.fctx.write(merged_text, local.fctx.flags()) + return True, conflicts, False @internaltool( @@ -484,14 +508,12 @@ def _merge(repo, mynode, orig, fcd, fco, ), precheck=_mergecheck, ) -def _iunion(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None): +def _iunion(repo, mynode, local, other, base, toolconf, backup): """ Uses the internal non-interactive simple merge algorithm for merging files. It will use both left and right sides for conflict regions. No markers are inserted.""" - return _merge( - repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, b'union' - ) + return _merge(repo, local, other, base, b'union') @internaltool( @@ -503,15 +525,13 @@ def _iunion(repo, mynode, orig, fcd, fco ), precheck=_mergecheck, ) -def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None): +def _imerge(repo, mynode, local, other, base, toolconf, backup): """ Uses the internal non-interactive simple merge algorithm for merging files. It will fail if there are any conflicts and leave markers in the partially merged file. Markers will have two sections, one for each side of merge.""" - return _merge( - repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, b'merge' - ) + return _merge(repo, local, other, base, b'merge') @internaltool( @@ -523,17 +543,13 @@ def _imerge(repo, mynode, orig, fcd, fco ), precheck=_mergecheck, ) -def _imerge3(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None): +def _imerge3(repo, mynode, local, other, base, toolconf, backup): """ Uses the internal non-interactive simple merge algorithm for merging files. It will fail if there are any conflicts and leave markers in the partially merged file. Marker will have three sections, one from each side of the merge and one for the base content.""" - if not labels: - labels = _defaultconflictlabels - if len(labels) < 3: - labels.append(b'base') - return _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels) + return _merge(repo, local, other, base, b'merge3') @internaltool( @@ -564,62 +580,30 @@ def _imerge3alwaysgood(*args, **kwargs): ), precheck=_mergecheck, ) -def _imerge_diff( - repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None -): +def _imerge_diff(repo, mynode, local, other, base, toolconf, backup): """ Uses the internal non-interactive simple merge algorithm for merging files. It will fail if there are any conflicts and leave markers in the partially merged file. The marker will have two sections, one with the content from one side of the merge, and one with a diff from the base content to the content on the other side. (experimental)""" - if not labels: - labels = _defaultconflictlabels - if len(labels) < 3: - labels.append(b'base') - return _merge( - repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, b'mergediff' - ) - - -def _imergeauto( - repo, - mynode, - orig, - fcd, - fco, - fca, - toolconf, - files, - labels=None, - localorother=None, -): - """ - Generic driver for _imergelocal and _imergeother - """ - assert localorother is not None - r = simplemerge.simplemerge( - repo.ui, fcd, fca, fco, label=labels, localorother=localorother - ) - return True, r + return _merge(repo, local, other, base, b'mergediff') @internaltool(b'merge-local', mergeonly, precheck=_mergecheck) -def _imergelocal(*args, **kwargs): +def _imergelocal(repo, mynode, local, other, base, toolconf, backup): """ Like :merge, but resolve all conflicts non-interactively in favor of the local `p1()` changes.""" - success, status = _imergeauto(localorother=b'local', *args, **kwargs) - return success, status, False + return _merge(repo, local, other, base, b'local') @internaltool(b'merge-other', mergeonly, precheck=_mergecheck) -def _imergeother(*args, **kwargs): +def _imergeother(repo, mynode, local, other, base, toolconf, backup): """ Like :merge, but resolve all conflicts non-interactively in favor of the other `p2()` changes.""" - success, status = _imergeauto(localorother=b'other', *args, **kwargs) - return success, status, False + return _merge(repo, local, other, base, b'other') @internaltool( @@ -631,16 +615,16 @@ def _imergeother(*args, **kwargs): b"tool of your choice)\n" ), ) -def _itagmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None): +def _itagmerge(repo, mynode, local, other, base, toolconf, backup): """ Uses the internal tag merge algorithm (experimental). """ - success, status = tagmerge.merge(repo, fcd, fco, fca) + success, status = tagmerge.merge(repo, local.fctx, other.fctx, base.fctx) return success, status, False @internaltool(b'dump', fullmerge, binary=True, symlink=True) -def _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None): +def _idump(repo, mynode, local, other, base, toolconf, backup): """ Creates three versions of the files to merge, containing the contents of local, other and base. These files can then be used to @@ -652,33 +636,31 @@ def _idump(repo, mynode, orig, fcd, fco, This implies premerge. Therefore, files aren't dumped, if premerge runs successfully. Use :forcedump to forcibly write files out. """ - a = _workingpath(repo, fcd) - fd = fcd.path() + a = _workingpath(repo, local.fctx) + fd = local.fctx.path() from . import context - if isinstance(fcd, context.overlayworkingfilectx): + if isinstance(local.fctx, context.overlayworkingfilectx): raise error.InMemoryMergeConflictsError( b'in-memory merge does not support the :dump tool.' ) - util.writefile(a + b".local", fcd.decodeddata()) - repo.wwrite(fd + b".other", fco.data(), fco.flags()) - repo.wwrite(fd + b".base", fca.data(), fca.flags()) + util.writefile(a + b".local", local.fctx.decodeddata()) + repo.wwrite(fd + b".other", other.fctx.data(), other.fctx.flags()) + repo.wwrite(fd + b".base", base.fctx.data(), base.fctx.flags()) return False, 1, False @internaltool(b'forcedump', mergeonly, binary=True, symlink=True) -def _forcedump(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None): +def _forcedump(repo, mynode, local, other, base, toolconf, backup): """ Creates three versions of the files as same as :dump, but omits premerge. """ - return _idump( - repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=labels - ) + return _idump(repo, mynode, local, other, base, toolconf, backup) -def _xmergeimm(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None): +def _xmergeimm(repo, mynode, local, other, base, toolconf, backup): # In-memory merge simply raises an exception on all external merge tools, # for now. # @@ -746,7 +728,10 @@ def _describemerge(ui, repo, mynode, fcl ui.status(t.renderdefault(props)) -def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels): +def _xmerge(repo, mynode, local, other, base, toolconf, backup): + fcd = local.fctx + fco = other.fctx + fca = base.fctx tool, toolpath, binary, symlink, scriptfn = toolconf uipathfn = scmutil.getuipathfn(repo) if fcd.isabsent() or fco.isabsent(): @@ -755,20 +740,35 @@ def _xmerge(repo, mynode, orig, fcd, fco % (tool, uipathfn(fcd.path())) ) return False, 1, None - unused, unused, unused, back = files localpath = _workingpath(repo, fcd) args = _toolstr(repo.ui, tool, b"args") - with _maketempfiles( - repo, fco, fca, repo.wvfs.join(back.path()), b"$output" in args - ) as temppaths: - basepath, otherpath, localoutputpath = temppaths - outpath = b"" - mylabel, otherlabel = labels[:2] - if len(labels) >= 3: - baselabel = labels[2] - else: - baselabel = b'base' + files = [ + (b"base", fca.path(), fca.decodeddata()), + (b"other", fco.path(), fco.decodeddata()), + ] + outpath = b"" + if b"$output" in args: + # read input from backup, write to original + outpath = localpath + localoutputpath = backup.path() + # Remove the .orig to make syntax-highlighting more likely. + if localoutputpath.endswith(b'.orig'): + localoutputpath, ext = os.path.splitext(localoutputpath) + localdata = util.readfile(localpath) + files.append((b"local", localoutputpath, localdata)) + + with _maketempfiles(files) as temppaths: + basepath, otherpath = temppaths[:2] + if len(temppaths) == 3: + localpath = temppaths[2] + + def format_label(input): + if input.label_detail: + return b'%s: %s' % (input.label, input.label_detail) + else: + return input.label + env = { b'HG_FILE': fcd.path(), b'HG_MY_NODE': short(mynode), @@ -777,24 +777,20 @@ def _xmerge(repo, mynode, orig, fcd, fco b'HG_MY_ISLINK': b'l' in fcd.flags(), b'HG_OTHER_ISLINK': b'l' in fco.flags(), b'HG_BASE_ISLINK': b'l' in fca.flags(), - b'HG_MY_LABEL': mylabel, - b'HG_OTHER_LABEL': otherlabel, - b'HG_BASE_LABEL': baselabel, + b'HG_MY_LABEL': format_label(local), + b'HG_OTHER_LABEL': format_label(other), + b'HG_BASE_LABEL': format_label(base), } ui = repo.ui - if b"$output" in args: - # read input from backup, write to original - outpath = localpath - localpath = localoutputpath replace = { b'local': localpath, b'base': basepath, b'other': otherpath, b'output': outpath, - b'labellocal': mylabel, - b'labelother': otherlabel, - b'labelbase': baselabel, + b'labellocal': format_label(local), + b'labelother': format_label(other), + b'labelbase': format_label(base), } args = util.interpolate( br'\$', @@ -846,40 +842,19 @@ def _xmerge(repo, mynode, orig, fcd, fco return True, r, False -def _formatconflictmarker(ctx, template, label, pad): - """Applies the given template to the ctx, prefixed by the label. - - Pad is the minimum width of the label prefix, so that multiple markers - can have aligned templated parts. - """ +def _populate_label_detail(input, template): + """Applies the given template to the ctx and stores it in the input.""" + ctx = input.fctx.changectx() if ctx.node() is None: ctx = ctx.p1() props = {b'ctx': ctx} templateresult = template.renderdefault(props) - - label = (b'%s:' % label).ljust(pad + 1) - mark = b'%s %s' % (label, templateresult) - - if mark: - mark = mark.splitlines()[0] # split for safety - - # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ') - return stringutil.ellipsis(mark, 80 - 8) + input.label_detail = templateresult.splitlines()[0] # split for safety -_defaultconflictlabels = [b'local', b'other'] - - -def _formatlabels(repo, fcd, fco, fca, labels, tool=None): - """Formats the given labels using the conflict marker template. - - Returns a list of formatted labels. - """ - cd = fcd.changectx() - co = fco.changectx() - ca = fca.changectx() - +def _populate_label_details(repo, inputs, tool=None): + """Populates the label details using the conflict marker template.""" ui = repo.ui template = ui.config(b'command-templates', b'mergemarker') if tool is not None: @@ -890,15 +865,8 @@ def _formatlabels(repo, fcd, fco, fca, l ui, template, defaults=templatekw.keywords, resources=tres ) - pad = max(len(l) for l in labels) - - newlabels = [ - _formatconflictmarker(cd, tmpl, labels[0], pad), - _formatconflictmarker(co, tmpl, labels[1], pad), - ] - if len(labels) > 2: - newlabels.append(_formatconflictmarker(ca, tmpl, labels[2], pad)) - return newlabels + for input in inputs: + _populate_label_detail(input, tmpl) def partextras(labels): @@ -918,13 +886,7 @@ def partextras(labels): } -def _restorebackup(fcd, back): - # TODO: Add a workingfilectx.write(otherfilectx) path so we can use - # util.copy here instead. - fcd.write(back.data(), fcd.flags()) - - -def _makebackup(repo, ui, wctx, fcd, premerge): +def _makebackup(repo, ui, fcd): """Makes and returns a filectx-like object for ``fcd``'s backup file. In addition to preserving the user's pre-existing modifications to `fcd` @@ -932,8 +894,8 @@ def _makebackup(repo, ui, wctx, fcd, pre merge changed anything, and determine what line endings the new file should have. - Backups only need to be written once (right before the premerge) since their - content doesn't change afterwards. + Backups only need to be written once since their content doesn't change + afterwards. """ if fcd.isabsent(): return None @@ -941,96 +903,47 @@ def _makebackup(repo, ui, wctx, fcd, pre # merge -> filemerge). (I suspect the fileset import is the weakest link) from . import context - back = scmutil.backuppath(ui, repo, fcd.path()) - inworkingdir = back.startswith(repo.wvfs.base) and not back.startswith( - repo.vfs.base - ) - if isinstance(fcd, context.overlayworkingfilectx) and inworkingdir: - # If the backup file is to be in the working directory, and we're - # merging in-memory, we must redirect the backup to the memory context - # so we don't disturb the working directory. - relpath = back[len(repo.wvfs.base) + 1 :] - if premerge: - wctx[relpath].write(fcd.data(), fcd.flags()) - return wctx[relpath] + if isinstance(fcd, context.overlayworkingfilectx): + # If we're merging in-memory, we're free to put the backup anywhere. + fd, backup = pycompat.mkstemp(b'hg-merge-backup') + with os.fdopen(fd, 'wb') as f: + f.write(fcd.data()) else: - if premerge: - # Otherwise, write to wherever path the user specified the backups - # should go. We still need to switch based on whether the source is - # in-memory so we can use the fast path of ``util.copy`` if both are - # on disk. - if isinstance(fcd, context.overlayworkingfilectx): - util.writefile(back, fcd.data()) - else: - a = _workingpath(repo, fcd) - util.copyfile(a, back) - # A arbitraryfilectx is returned, so we can run the same functions on - # the backup context regardless of where it lives. - return context.arbitraryfilectx(back, repo=repo) + backup = scmutil.backuppath(ui, repo, fcd.path()) + a = _workingpath(repo, fcd) + util.copyfile(a, backup) + + return context.arbitraryfilectx(backup, repo=repo) @contextlib.contextmanager -def _maketempfiles(repo, fco, fca, localpath, uselocalpath): - """Writes out `fco` and `fca` as temporary files, and (if uselocalpath) - copies `localpath` to another temporary file, so an external merge tool may - use them. +def _maketempfiles(files): + """Creates a temporary file for each (prefix, path, data) tuple in `files`, + so an external merge tool may use them. """ - tmproot = None - tmprootprefix = repo.ui.config(b'experimental', b'mergetempdirprefix') - if tmprootprefix: - tmproot = pycompat.mkdtemp(prefix=tmprootprefix) + tmproot = pycompat.mkdtemp(prefix=b'hgmerge-') - def maketempfrompath(prefix, path): + def maketempfrompath(prefix, path, data): fullbase, ext = os.path.splitext(path) pre = b"%s~%s" % (os.path.basename(fullbase), prefix) - if tmproot: - name = os.path.join(tmproot, pre) - if ext: - name += ext - f = open(name, "wb") - else: - fd, name = pycompat.mkstemp(prefix=pre + b'.', suffix=ext) - f = os.fdopen(fd, "wb") - return f, name - - def tempfromcontext(prefix, ctx): - f, name = maketempfrompath(prefix, ctx.path()) - data = repo.wwritedata(ctx.path(), ctx.data()) - f.write(data) - f.close() + name = os.path.join(tmproot, pre) + if ext: + name += ext + util.writefile(name, data) return name - b = tempfromcontext(b"base", fca) - c = tempfromcontext(b"other", fco) - d = localpath - if uselocalpath: - # We start off with this being the backup filename, so remove the .orig - # to make syntax-highlighting more likely. - if d.endswith(b'.orig'): - d, _ = os.path.splitext(d) - f, d = maketempfrompath(b"local", d) - with open(localpath, b'rb') as src: - f.write(src.read()) - f.close() - + temp_files = [] + for prefix, path, data in files: + temp_files.append(maketempfrompath(prefix, path, data)) try: - yield b, c, d + yield temp_files finally: - if tmproot: - shutil.rmtree(tmproot) - else: - util.unlink(b) - util.unlink(c) - # if not uselocalpath, d is the 'orig'/backup file which we - # shouldn't delete. - if d and uselocalpath: - util.unlink(d) + shutil.rmtree(tmproot) -def _filemerge(premerge, repo, wctx, mynode, orig, fcd, fco, fca, labels=None): +def filemerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None): """perform a 3-way merge in the working directory - premerge = whether this is a premerge mynode = parent node before merge orig = original local filename before merge fco = other file context @@ -1039,10 +952,6 @@ def _filemerge(premerge, repo, wctx, myn Returns whether the merge is complete, the return value of the merge, and a boolean indicating whether the file was deleted from disk.""" - - if not fco.cmp(fcd): # files identical? - return True, None, False - ui = repo.ui fd = fcd.path() uipathfn = scmutil.getuipathfn(repo) @@ -1098,32 +1007,43 @@ def _filemerge(premerge, repo, wctx, myn toolconf = tool, toolpath, binary, symlink, scriptfn + if not labels: + labels = [b'local', b'other'] + if len(labels) < 3: + labels.append(b'base') + local = simplemerge.MergeInput(fcd, labels[0]) + other = simplemerge.MergeInput(fco, labels[1]) + base = simplemerge.MergeInput(fca, labels[2]) if mergetype == nomerge: - r, deleted = func(repo, mynode, orig, fcd, fco, fca, toolconf, labels) - return True, r, deleted + return func( + repo, + mynode, + local, + other, + base, + toolconf, + ) - if premerge: - if orig != fco.path(): - ui.status( - _(b"merging %s and %s to %s\n") - % (uipathfn(orig), uipathfn(fco.path()), fduipath) - ) - else: - ui.status(_(b"merging %s\n") % fduipath) + if orig != fco.path(): + ui.status( + _(b"merging %s and %s to %s\n") + % (uipathfn(orig), uipathfn(fco.path()), fduipath) + ) + else: + ui.status(_(b"merging %s\n") % fduipath) ui.debug(b"my %s other %s ancestor %s\n" % (fcd, fco, fca)) - if precheck and not precheck(repo, mynode, orig, fcd, fco, fca, toolconf): + if precheck and not precheck(repo, mynode, fcd, fco, fca, toolconf): if onfailure: if wctx.isinmemory(): raise error.InMemoryMergeConflictsError( b'in-memory merge does not support merge conflicts' ) ui.warn(onfailure % fduipath) - return True, 1, False + return 1, False - back = _makebackup(repo, ui, wctx, fcd, premerge) - files = (None, None, None, back) + backup = _makebackup(repo, ui, fcd) r = 1 try: internalmarkerstyle = ui.config(b'ui', b'mergemarkers') @@ -1132,51 +1052,53 @@ def _filemerge(premerge, repo, wctx, myn else: markerstyle = internalmarkerstyle - if not labels: - labels = _defaultconflictlabels - formattedlabels = labels - if markerstyle != b'basic': - formattedlabels = _formatlabels( - repo, fcd, fco, fca, labels, tool=tool - ) - - if premerge and mergetype == fullmerge: + if mergetype == fullmerge: # conflict markers generated by premerge will use 'detailed' # settings if either ui.mergemarkers or the tool's mergemarkers # setting is 'detailed'. This way tools can have basic labels in # space-constrained areas of the UI, but still get full information # in conflict markers if premerge is 'keep' or 'keep-merge3'. - premergelabels = labels labeltool = None if markerstyle != b'basic': # respect 'tool's mergemarkertemplate (which defaults to # command-templates.mergemarker) labeltool = tool if internalmarkerstyle != b'basic' or markerstyle != b'basic': - premergelabels = _formatlabels( - repo, fcd, fco, fca, premergelabels, tool=labeltool + _populate_label_details( + repo, [local, other, base], tool=labeltool ) r = _premerge( - repo, fcd, fco, fca, toolconf, files, labels=premergelabels + repo, + local, + other, + base, + toolconf, ) - # complete if premerge successful (r is 0) - return not r, r, False + # we're done if premerge was successful (r is 0) + if not r: + return r, False + + # Reset to basic labels + local.label_detail = None + other.label_detail = None + base.label_detail = None + + if markerstyle != b'basic': + _populate_label_details(repo, [local, other, base], tool=tool) needcheck, r, deleted = func( repo, mynode, - orig, - fcd, - fco, - fca, + local, + other, + base, toolconf, - files, - labels=formattedlabels, + backup, ) if needcheck: - r = _check(repo, r, ui, tool, fcd, files) + r = _check(repo, r, ui, tool, fcd, backup) if r: if onfailure: @@ -1189,10 +1111,10 @@ def _filemerge(premerge, repo, wctx, myn ui.warn(onfailure % fduipath) _onfilemergefailure(ui) - return True, r, deleted + return r, deleted finally: - if not r and back is not None: - back.remove() + if not r and backup is not None: + backup.remove() def _haltmerge(): @@ -1225,10 +1147,9 @@ def hasconflictmarkers(data): ) -def _check(repo, r, ui, tool, fcd, files): +def _check(repo, r, ui, tool, fcd, backup): fd = fcd.path() uipathfn = scmutil.getuipathfn(repo) - unused, unused, unused, back = files if not r and ( _toolbool(ui, tool, b"checkconflicts") @@ -1255,7 +1176,7 @@ def _check(repo, r, ui, tool, fcd, files or b'changed' in _toollist(ui, tool, b"check") ) ): - if back is not None and not fcd.cmp(back): + if backup is not None and not fcd.cmp(backup): if ui.promptchoice( _( b" output file %s appears unchanged\n" @@ -1267,8 +1188,8 @@ def _check(repo, r, ui, tool, fcd, files ): r = 1 - if back is not None and _toolbool(ui, tool, b"fixeol"): - _matcheol(_workingpath(repo, fcd), back) + if backup is not None and _toolbool(ui, tool, b"fixeol"): + _matcheol(_workingpath(repo, fcd), backup) return r @@ -1277,18 +1198,6 @@ def _workingpath(repo, ctx): return repo.wjoin(ctx.path()) -def premerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None): - return _filemerge( - True, repo, wctx, mynode, orig, fcd, fco, fca, labels=labels - ) - - -def filemerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None): - return _filemerge( - False, repo, wctx, mynode, orig, fcd, fco, fca, labels=labels - ) - - def loadinternalmerge(ui, extname, registrarobj): """Load internal merge tool from specified registrarobj""" for name, func in pycompat.iteritems(registrarobj._table): diff --git a/mercurial/helptext/config.txt b/mercurial/helptext/config.txt --- a/mercurial/helptext/config.txt +++ b/mercurial/helptext/config.txt @@ -513,13 +513,18 @@ effect and style see :hg:`help color`. ``update.check`` Determines what level of checking :hg:`update` will perform before moving to a destination revision. Valid values are ``abort``, ``none``, - ``linear``, and ``noconflict``. ``abort`` always fails if the working - directory has uncommitted changes. ``none`` performs no checking, and may - result in a merge with uncommitted changes. ``linear`` allows any update - as long as it follows a straight line in the revision history, and may - trigger a merge with uncommitted changes. ``noconflict`` will allow any - update which would not trigger a merge with uncommitted changes, if any - are present. + ``linear``, and ``noconflict``. + + - ``abort`` always fails if the working directory has uncommitted changes. + + - ``none`` performs no checking, and may result in a merge with uncommitted changes. + + - ``linear`` allows any update as long as it follows a straight line in the + revision history, and may trigger a merge with uncommitted changes. + + - ``noconflict`` will allow any update which would not trigger a merge with + uncommitted changes, if any are present. + (default: ``linear``) ``update.requiredest`` @@ -850,6 +855,24 @@ Example for ``~/.hgrc``:: # (this extension will get loaded from the file specified) myfeature = ~/.hgext/myfeature.py +If an extension fails to load, a warning will be issued, and Mercurial will +proceed. To enforce that an extension must be loaded, one can set the `required` +suboption in the config:: + + [extensions] + myfeature = ~/.hgext/myfeature.py + myfeature:required = yes + +To debug extension loading issue, one can add `--traceback` to their mercurial +invocation. + +A default setting can we set using the special `*` extension key:: + + [extensions] + *:required = yes + myfeature = ~/.hgext/myfeature.py + rebase= + ``format`` ---------- @@ -921,6 +944,38 @@ https://www.mercurial-scm.org/wiki/Missi For a more comprehensive guide, see :hg:`help internals.dirstate-v2`. +``use-dirstate-tracked-hint`` + Enable or disable the writing of "tracked key" file alongside the dirstate. + (default to disabled) + + That "tracked-hint" can help external automations to detect changes to the + set of tracked files. (i.e the result of `hg files` or `hg status -macd`) + + The tracked-hint is written in a new `.hg/dirstate-tracked-hint`. That file + contains two lines: + - the first line is the file version (currently: 1), + - the second line contains the "tracked-hint". + That file is written right after the dirstate is written. + + The tracked-hint changes whenever the set of file tracked in the dirstate + changes. The general idea is: + - if the hint is identical, the set of tracked file SHOULD be identical, + - if the hint is different, the set of tracked file MIGHT be different. + + The "hint is identical" case uses `SHOULD` as the dirstate and the hint file + are two distinct files and therefore that cannot be read or written to in an + atomic way. If the key is identical, nothing garantees that the dirstate is + not updated right after the hint file. This is considered a negligible + limitation for the intended usecase. It is actually possible to prevent this + race by taking the repository lock during read operations. + + They are two "ways" to use this feature: + + 1) monitoring changes to the `.hg/dirstate-tracked-hint`, if the file + changes, the tracked set might have changed. + + 2) storing the value and comparing it to a later value. + ``use-persistent-nodemap`` Enable or disable the "persistent-nodemap" feature which improves performance if the Rust extensions are available. @@ -975,7 +1030,7 @@ https://www.mercurial-scm.org/wiki/Missi Introduced in Mercurial 5.7. - Disabled by default. + Enabled by default in Mercurial 6.1. ``usestore`` Enable or disable the "store" repository format which improves diff --git a/mercurial/helptext/internals/wireprotocol.txt b/mercurial/helptext/internals/wireprotocol.txt --- a/mercurial/helptext/internals/wireprotocol.txt +++ b/mercurial/helptext/internals/wireprotocol.txt @@ -332,95 +332,6 @@ part of the response payload and not par after responses. In other words, the length of the response contains the trailing ``\n``. -Clients supporting version 2 of the SSH transport send a line beginning -with ``upgrade`` before the ``hello`` and ``between`` commands. The line -(which isn't a well-formed command line because it doesn't consist of a -single command name) serves to both communicate the client's intent to -switch to transport version 2 (transports are version 1 by default) as -well as to advertise the client's transport-level capabilities so the -server may satisfy that request immediately. - -The upgrade line has the form: - - upgrade - -That is the literal string ``upgrade`` followed by a space, followed by -a randomly generated string, followed by a space, followed by a string -denoting the client's transport capabilities. - -The token can be anything. However, a random UUID is recommended. (Use -of version 4 UUIDs is recommended because version 1 UUIDs can leak the -client's MAC address.) - -The transport capabilities string is a URL/percent encoded string -containing key-value pairs defining the client's transport-level -capabilities. The following capabilities are defined: - -proto - A comma-delimited list of transport protocol versions the client - supports. e.g. ``ssh-v2``. - -If the server does not recognize the ``upgrade`` line, it should issue -an empty response and continue processing the ``hello`` and ``between`` -commands. Here is an example handshake between a version 2 aware client -and a non version 2 aware server: - - c: upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=ssh-v2 - c: hello\n - c: between\n - c: pairs 81\n - c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - s: 0\n - s: 324\n - s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n - s: 1\n - s: \n - -(The initial ``0\n`` line from the server indicates an empty response to -the unknown ``upgrade ..`` command/line.) - -If the server recognizes the ``upgrade`` line and is willing to satisfy that -upgrade request, it replies to with a payload of the following form: - - upgraded \n - -This line is the literal string ``upgraded``, a space, the token that was -specified by the client in its ``upgrade ...`` request line, a space, and the -name of the transport protocol that was chosen by the server. The transport -name MUST match one of the names the client specified in the ``proto`` field -of its ``upgrade ...`` request line. - -If a server issues an ``upgraded`` response, it MUST also read and ignore -the lines associated with the ``hello`` and ``between`` command requests -that were issued by the server. It is assumed that the negotiated transport -will respond with equivalent requested information following the transport -handshake. - -All data following the ``\n`` terminating the ``upgraded`` line is the -domain of the negotiated transport. It is common for the data immediately -following to contain additional metadata about the state of the transport and -the server. However, this isn't strictly speaking part of the transport -handshake and isn't covered by this section. - -Here is an example handshake between a version 2 aware client and a version -2 aware server: - - c: upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=ssh-v2 - c: hello\n - c: between\n - c: pairs 81\n - c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - s: upgraded 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a ssh-v2\n - s: - -The client-issued token that is echoed in the response provides a more -resilient mechanism for differentiating *banner* output from Mercurial -output. In version 1, properly formatted banner output could get confused -for Mercurial server output. By submitting a randomly generated token -that is then present in the response, the client can look for that token -in response lines and have reasonable certainty that the line did not -originate from a *banner* message. - SSH Version 1 Transport ----------------------- @@ -488,31 +399,6 @@ If the server announces support for the should issue a ``protocaps`` command after the initial handshake to annonunce its own capabilities. The client capabilities are persistent. -SSH Version 2 Transport ------------------------ - -**Experimental and under development** - -Version 2 of the SSH transport behaves identically to version 1 of the SSH -transport with the exception of handshake semantics. See above for how -version 2 of the SSH transport is negotiated. - -Immediately following the ``upgraded`` line signaling a switch to version -2 of the SSH protocol, the server automatically sends additional details -about the capabilities of the remote server. This has the form: - - \n - capabilities: ...\n - -e.g. - - s: upgraded 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a ssh-v2\n - s: 240\n - s: capabilities: known getbundle batch ...\n - -Following capabilities advertisement, the peers communicate using version -1 of the SSH transport. - Capabilities ============ diff --git a/mercurial/helptext/patterns.txt b/mercurial/helptext/patterns.txt --- a/mercurial/helptext/patterns.txt +++ b/mercurial/helptext/patterns.txt @@ -1,8 +1,10 @@ Mercurial accepts several notations for identifying one or more files at a time. -By default, Mercurial treats filenames as shell-style extended glob -patterns. +By default, Mercurial treats filenames verbatim without pattern +matching, relative to the current working directory. Note that your +system shell might perform pattern matching of its own before passing +filenames into Mercurial. Alternate pattern notations must be specified explicitly. diff --git a/mercurial/hg.py b/mercurial/hg.py --- a/mercurial/hg.py +++ b/mercurial/hg.py @@ -132,13 +132,6 @@ def addbranchrevs(lrepo, other, branches return revs, revs[0] -def parseurl(path, branches=None): - '''parse url#branch, returning (url, (branch, branches))''' - msg = b'parseurl(...) moved to mercurial.utils.urlutil' - util.nouideprecwarn(msg, b'6.0', stacklevel=2) - return urlutil.parseurl(path, branches=branches) - - schemes = { b'bundle': bundlerepo, b'union': unionrepo, diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py --- a/mercurial/hgweb/hgweb_mod.py +++ b/mercurial/hgweb/hgweb_mod.py @@ -366,17 +366,6 @@ class hgweb(object): # replace it. res.headers[b'Content-Security-Policy'] = rctx.csp - # /api/* is reserved for various API implementations. Dispatch - # accordingly. But URL paths can conflict with subrepos and virtual - # repos in hgwebdir. So until we have a workaround for this, only - # expose the URLs if the feature is enabled. - apienabled = rctx.repo.ui.configbool(b'experimental', b'web.apiserver') - if apienabled and req.dispatchparts and req.dispatchparts[0] == b'api': - wireprotoserver.handlewsgiapirequest( - rctx, req, res, self.check_perm - ) - return res.sendresponse() - handled = wireprotoserver.handlewsgirequest( rctx, req, res, self.check_perm ) diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py --- a/mercurial/hgweb/webcommands.py +++ b/mercurial/hgweb/webcommands.py @@ -519,6 +519,7 @@ rev = webcommand(b'rev')(changeset) def decodepath(path): + # type: (bytes) -> bytes """Hook for mapping a path in the repository to a path in the working copy. @@ -616,7 +617,9 @@ def manifest(web): yield { b"parity": next(parity), b"path": path, + # pytype: disable=wrong-arg-types b"emptydirs": b"/".join(emptydirs), + # pytype: enable=wrong-arg-types b"basename": d, } diff --git a/mercurial/httppeer.py b/mercurial/httppeer.py --- a/mercurial/httppeer.py +++ b/mercurial/httppeer.py @@ -13,7 +13,6 @@ import io import os import socket import struct -import weakref from .i18n import _ from .pycompat import getattr @@ -25,21 +24,9 @@ from . import ( statichttprepo, url as urlmod, util, - wireprotoframing, - wireprototypes, wireprotov1peer, - wireprotov2peer, - wireprotov2server, ) -from .interfaces import ( - repository, - util as interfaceutil, -) -from .utils import ( - cborutil, - stringutil, - urlutil, -) +from .utils import urlutil httplib = util.httplib urlerr = util.urlerr @@ -331,9 +318,7 @@ class RedirectedRepoError(error.RepoErro self.respurl = respurl -def parsev1commandresponse( - ui, baseurl, requrl, qs, resp, compressible, allowcbor=False -): +def parsev1commandresponse(ui, baseurl, requrl, qs, resp, compressible): # record the url we got redirected to redirected = False respurl = pycompat.bytesurl(resp.geturl()) @@ -376,17 +361,6 @@ def parsev1commandresponse( try: subtype = proto.split(b'-', 1)[1] - # Unless we end up supporting CBOR in the legacy wire protocol, - # this should ONLY be encountered for the initial capabilities - # request during handshake. - if subtype == b'cbor': - if allowcbor: - return respurl, proto, resp - else: - raise error.RepoError( - _(b'unexpected CBOR response from server') - ) - version_info = tuple([int(n) for n in subtype.split(b'.')]) except ValueError: raise error.RepoError( @@ -564,85 +538,6 @@ class httppeer(wireprotov1peer.wirepeer) raise exception -def sendv2request( - ui, opener, requestbuilder, apiurl, permission, requests, redirect -): - wireprotoframing.populatestreamencoders() - - uiencoders = ui.configlist(b'experimental', b'httppeer.v2-encoder-order') - - if uiencoders: - encoders = [] - - for encoder in uiencoders: - if encoder not in wireprotoframing.STREAM_ENCODERS: - ui.warn( - _( - b'wire protocol version 2 encoder referenced in ' - b'config (%s) is not known; ignoring\n' - ) - % encoder - ) - else: - encoders.append(encoder) - - else: - encoders = wireprotoframing.STREAM_ENCODERS_ORDER - - reactor = wireprotoframing.clientreactor( - ui, - hasmultiplesend=False, - buffersends=True, - clientcontentencoders=encoders, - ) - - handler = wireprotov2peer.clienthandler( - ui, reactor, opener=opener, requestbuilder=requestbuilder - ) - - url = b'%s/%s' % (apiurl, permission) - - if len(requests) > 1: - url += b'/multirequest' - else: - url += b'/%s' % requests[0][0] - - ui.debug(b'sending %d commands\n' % len(requests)) - for command, args, f in requests: - ui.debug( - b'sending command %s: %s\n' - % (command, stringutil.pprint(args, indent=2)) - ) - assert not list( - handler.callcommand(command, args, f, redirect=redirect) - ) - - # TODO stream this. - body = b''.join(map(bytes, handler.flushcommands())) - - # TODO modify user-agent to reflect v2 - headers = { - 'Accept': wireprotov2server.FRAMINGTYPE, - 'Content-Type': wireprotov2server.FRAMINGTYPE, - } - - req = requestbuilder(pycompat.strurl(url), body, headers) - req.add_unredirected_header('Content-Length', '%d' % len(body)) - - try: - res = opener.open(req) - except urlerr.httperror as e: - if e.code == 401: - raise error.Abort(_(b'authorization failed')) - - raise - except httplib.HTTPException as e: - ui.traceback() - raise IOError(None, e) - - return handler, res - - class queuedcommandfuture(pycompat.futures.Future): """Wraps result() on command futures to trigger submission on call.""" @@ -657,302 +552,6 @@ class queuedcommandfuture(pycompat.futur return self.result(timeout) -@interfaceutil.implementer(repository.ipeercommandexecutor) -class httpv2executor(object): - def __init__( - self, ui, opener, requestbuilder, apiurl, descriptor, redirect - ): - self._ui = ui - self._opener = opener - self._requestbuilder = requestbuilder - self._apiurl = apiurl - self._descriptor = descriptor - self._redirect = redirect - self._sent = False - self._closed = False - self._neededpermissions = set() - self._calls = [] - self._futures = weakref.WeakSet() - self._responseexecutor = None - self._responsef = None - - def __enter__(self): - return self - - def __exit__(self, exctype, excvalue, exctb): - self.close() - - def callcommand(self, command, args): - if self._sent: - raise error.ProgrammingError( - b'callcommand() cannot be used after commands are sent' - ) - - if self._closed: - raise error.ProgrammingError( - b'callcommand() cannot be used after close()' - ) - - # The service advertises which commands are available. So if we attempt - # to call an unknown command or pass an unknown argument, we can screen - # for this. - if command not in self._descriptor[b'commands']: - raise error.ProgrammingError( - b'wire protocol command %s is not available' % command - ) - - cmdinfo = self._descriptor[b'commands'][command] - unknownargs = set(args.keys()) - set(cmdinfo.get(b'args', {})) - - if unknownargs: - raise error.ProgrammingError( - b'wire protocol command %s does not accept argument: %s' - % (command, b', '.join(sorted(unknownargs))) - ) - - self._neededpermissions |= set(cmdinfo[b'permissions']) - - # TODO we /could/ also validate types here, since the API descriptor - # includes types... - - f = pycompat.futures.Future() - - # Monkeypatch it so result() triggers sendcommands(), otherwise result() - # could deadlock. - f.__class__ = queuedcommandfuture - f._peerexecutor = self - - self._futures.add(f) - self._calls.append((command, args, f)) - - return f - - def sendcommands(self): - if self._sent: - return - - if not self._calls: - return - - self._sent = True - - # Unhack any future types so caller sees a clean type and so we - # break reference cycle. - for f in self._futures: - if isinstance(f, queuedcommandfuture): - f.__class__ = pycompat.futures.Future - f._peerexecutor = None - - # Mark the future as running and filter out cancelled futures. - calls = [ - (command, args, f) - for command, args, f in self._calls - if f.set_running_or_notify_cancel() - ] - - # Clear out references, prevent improper object usage. - self._calls = None - - if not calls: - return - - permissions = set(self._neededpermissions) - - if b'push' in permissions and b'pull' in permissions: - permissions.remove(b'pull') - - if len(permissions) > 1: - raise error.RepoError( - _(b'cannot make request requiring multiple permissions: %s') - % _(b', ').join(sorted(permissions)) - ) - - permission = { - b'push': b'rw', - b'pull': b'ro', - }[permissions.pop()] - - handler, resp = sendv2request( - self._ui, - self._opener, - self._requestbuilder, - self._apiurl, - permission, - calls, - self._redirect, - ) - - # TODO we probably want to validate the HTTP code, media type, etc. - - self._responseexecutor = pycompat.futures.ThreadPoolExecutor(1) - self._responsef = self._responseexecutor.submit( - self._handleresponse, handler, resp - ) - - def close(self): - if self._closed: - return - - self.sendcommands() - - self._closed = True - - if not self._responsef: - return - - # TODO ^C here may not result in immediate program termination. - - try: - self._responsef.result() - finally: - self._responseexecutor.shutdown(wait=True) - self._responsef = None - self._responseexecutor = None - - # If any of our futures are still in progress, mark them as - # errored, otherwise a result() could wait indefinitely. - for f in self._futures: - if not f.done(): - f.set_exception( - error.ResponseError(_(b'unfulfilled command response')) - ) - - self._futures = None - - def _handleresponse(self, handler, resp): - # Called in a thread to read the response. - - while handler.readdata(resp): - pass - - -@interfaceutil.implementer(repository.ipeerv2) -class httpv2peer(object): - - limitedarguments = False - - def __init__( - self, ui, repourl, apipath, opener, requestbuilder, apidescriptor - ): - self.ui = ui - self.apidescriptor = apidescriptor - - if repourl.endswith(b'/'): - repourl = repourl[:-1] - - self._url = repourl - self._apipath = apipath - self._apiurl = b'%s/%s' % (repourl, apipath) - self._opener = opener - self._requestbuilder = requestbuilder - - self._redirect = wireprotov2peer.supportedredirects(ui, apidescriptor) - - # Start of ipeerconnection. - - def url(self): - return self._url - - def local(self): - return None - - def peer(self): - return self - - def canpush(self): - # TODO change once implemented. - return False - - def close(self): - self.ui.note( - _( - b'(sent %d HTTP requests and %d bytes; ' - b'received %d bytes in responses)\n' - ) - % ( - self._opener.requestscount, - self._opener.sentbytescount, - self._opener.receivedbytescount, - ) - ) - - # End of ipeerconnection. - - # Start of ipeercapabilities. - - def capable(self, name): - # The capabilities used internally historically map to capabilities - # advertised from the "capabilities" wire protocol command. However, - # version 2 of that command works differently. - - # Maps to commands that are available. - if name in ( - b'branchmap', - b'getbundle', - b'known', - b'lookup', - b'pushkey', - ): - return True - - # Other concepts. - if name in (b'bundle2',): - return True - - # Alias command-* to presence of command of that name. - if name.startswith(b'command-'): - return name[len(b'command-') :] in self.apidescriptor[b'commands'] - - return False - - def requirecap(self, name, purpose): - if self.capable(name): - return - - raise error.CapabilityError( - _( - b'cannot %s; client or remote repository does not support the ' - b'\'%s\' capability' - ) - % (purpose, name) - ) - - # End of ipeercapabilities. - - def _call(self, name, **args): - with self.commandexecutor() as e: - return e.callcommand(name, args).result() - - def commandexecutor(self): - return httpv2executor( - self.ui, - self._opener, - self._requestbuilder, - self._apiurl, - self.apidescriptor, - self._redirect, - ) - - -# Registry of API service names to metadata about peers that handle it. -# -# The following keys are meaningful: -# -# init -# Callable receiving (ui, repourl, servicepath, opener, requestbuilder, -# apidescriptor) to create a peer. -# -# priority -# Integer priority for the service. If we could choose from multiple -# services, we choose the one with the highest priority. -API_PEERS = { - wireprototypes.HTTP_WIREPROTO_V2: { - b'init': httpv2peer, - b'priority': 50, - }, -} - - def performhandshake(ui, url, opener, requestbuilder): # The handshake is a request to the capabilities command. @@ -963,28 +562,6 @@ def performhandshake(ui, url, opener, re args = {} - # The client advertises support for newer protocols by adding an - # X-HgUpgrade-* header with a list of supported APIs and an - # X-HgProto-* header advertising which serializing formats it supports. - # We only support the HTTP version 2 transport and CBOR responses for - # now. - advertisev2 = ui.configbool(b'experimental', b'httppeer.advertise-v2') - - if advertisev2: - args[b'headers'] = { - 'X-HgProto-1': 'cbor', - } - - args[b'headers'].update( - encodevalueinheaders( - b' '.join(sorted(API_PEERS)), - b'X-HgUpgrade', - # We don't know the header limit this early. - # So make it small. - 1024, - ) - ) - req, requrl, qs = makev1commandrequest( ui, requestbuilder, caps, capable, url, b'capabilities', args ) @@ -1004,7 +581,7 @@ def performhandshake(ui, url, opener, re # redirect that drops the query string to "just work." try: respurl, ct, resp = parsev1commandresponse( - ui, url, requrl, qs, resp, compressible=False, allowcbor=advertisev2 + ui, url, requrl, qs, resp, compressible=False ) except RedirectedRepoError as e: req, requrl, qs = makev1commandrequest( @@ -1012,7 +589,7 @@ def performhandshake(ui, url, opener, re ) resp = sendrequest(ui, opener, req) respurl, ct, resp = parsev1commandresponse( - ui, url, requrl, qs, resp, compressible=False, allowcbor=advertisev2 + ui, url, requrl, qs, resp, compressible=False ) try: @@ -1023,29 +600,7 @@ def performhandshake(ui, url, opener, re if not ct.startswith(b'application/mercurial-'): raise error.ProgrammingError(b'unexpected content-type: %s' % ct) - if advertisev2: - if ct == b'application/mercurial-cbor': - try: - info = cborutil.decodeall(rawdata)[0] - except cborutil.CBORDecodeError: - raise error.Abort( - _(b'error decoding CBOR from remote server'), - hint=_( - b'try again and consider contacting ' - b'the server operator' - ), - ) - - # We got a legacy response. That's fine. - elif ct in (b'application/mercurial-0.1', b'application/mercurial-0.2'): - info = {b'v1capabilities': set(rawdata.split())} - - else: - raise error.RepoError( - _(b'unexpected response type from server: %s') % ct - ) - else: - info = {b'v1capabilities': set(rawdata.split())} + info = {b'v1capabilities': set(rawdata.split())} return respurl, info @@ -1073,29 +628,6 @@ def makepeer(ui, path, opener=None, requ respurl, info = performhandshake(ui, url, opener, requestbuilder) - # Given the intersection of APIs that both we and the server support, - # sort by their advertised priority and pick the first one. - # - # TODO consider making this request-based and interface driven. For - # example, the caller could say "I want a peer that does X." It's quite - # possible that not all peers would do that. Since we know the service - # capabilities, we could filter out services not meeting the - # requirements. Possibly by consulting the interfaces defined by the - # peer type. - apipeerchoices = set(info.get(b'apis', {}).keys()) & set(API_PEERS.keys()) - - preferredchoices = sorted( - apipeerchoices, key=lambda x: API_PEERS[x][b'priority'], reverse=True - ) - - for service in preferredchoices: - apipath = b'%s/%s' % (info[b'apibase'].rstrip(b'/'), service) - - return API_PEERS[service][b'init']( - ui, respurl, apipath, opener, requestbuilder, info[b'apis'][service] - ) - - # Failed to construct an API peer. Fall back to legacy. return httppeer( ui, path, respurl, opener, requestbuilder, info[b'v1capabilities'] ) diff --git a/mercurial/interfaces/dirstate.py b/mercurial/interfaces/dirstate.py --- a/mercurial/interfaces/dirstate.py +++ b/mercurial/interfaces/dirstate.py @@ -66,17 +66,6 @@ class idirstate(interfaceutil.Interface) def pathto(f, cwd=None): pass - def __getitem__(key): - """Return the current state of key (a filename) in the dirstate. - - States are: - n normal - m needs merging - r marked for removal - a marked for addition - ? not tracked - """ - def __contains__(key): """Check if bytestring `key` is known to the dirstate.""" diff --git a/mercurial/interfaces/repository.py b/mercurial/interfaces/repository.py --- a/mercurial/interfaces/repository.py +++ b/mercurial/interfaces/repository.py @@ -1278,7 +1278,7 @@ class imanifeststorage(interfaceutil.Int def linkrev(rev): """Obtain the changeset revision number a revision is linked to.""" - def revision(node, _df=None, raw=False): + def revision(node, _df=None): """Obtain fulltext data for a node.""" def rawdata(node, _df=None): @@ -1495,13 +1495,6 @@ class ilocalrepositorymain(interfaceutil """null revision for the hash function used by the repository.""" ) - supportedformats = interfaceutil.Attribute( - """Set of requirements that apply to stream clone. - - This is actually a class attribute and is shared among all instances. - """ - ) - supported = interfaceutil.Attribute( """Set of requirements that this repo is capable of opening.""" ) @@ -1794,7 +1787,7 @@ class ilocalrepositorymain(interfaceutil DANGEROUS. """ - def updatecaches(tr=None, full=False): + def updatecaches(tr=None, full=False, caches=None): """Warm repo caches.""" def invalidatecaches(): diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py --- a/mercurial/localrepo.py +++ b/mercurial/localrepo.py @@ -1,4 +1,5 @@ # localrepo.py - read/write repository class for mercurial +# coding: utf-8 # # Copyright 2005-2007 Olivia Mackall # @@ -931,7 +932,7 @@ def gathersupportedrequirements(ui): if engine.available() and engine.revlogheader(): supported.add(b'exp-compression-%s' % name) if engine.name() == b'zstd': - supported.add(b'revlog-compression-zstd') + supported.add(requirementsmod.REVLOG_COMPRESSION_ZSTD) return supported @@ -1273,32 +1274,26 @@ class localrepository(object): used. """ - # obsolete experimental requirements: - # - manifestv2: An experimental new manifest format that allowed - # for stem compression of long paths. Experiment ended up not - # being successful (repository sizes went up due to worse delta - # chains), and the code was deleted in 4.6. - supportedformats = { + _basesupported = { + requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT, + requirementsmod.CHANGELOGV2_REQUIREMENT, + requirementsmod.COPIESSDC_REQUIREMENT, + requirementsmod.DIRSTATE_TRACKED_HINT_V1, + requirementsmod.DIRSTATE_V2_REQUIREMENT, + requirementsmod.DOTENCODE_REQUIREMENT, + requirementsmod.FNCACHE_REQUIREMENT, + requirementsmod.GENERALDELTA_REQUIREMENT, + requirementsmod.INTERNAL_PHASE_REQUIREMENT, + requirementsmod.NODEMAP_REQUIREMENT, + requirementsmod.RELATIVE_SHARED_REQUIREMENT, requirementsmod.REVLOGV1_REQUIREMENT, - requirementsmod.GENERALDELTA_REQUIREMENT, - requirementsmod.TREEMANIFEST_REQUIREMENT, - requirementsmod.COPIESSDC_REQUIREMENT, requirementsmod.REVLOGV2_REQUIREMENT, - requirementsmod.CHANGELOGV2_REQUIREMENT, + requirementsmod.SHARED_REQUIREMENT, + requirementsmod.SHARESAFE_REQUIREMENT, + requirementsmod.SPARSE_REQUIREMENT, requirementsmod.SPARSEREVLOG_REQUIREMENT, - requirementsmod.NODEMAP_REQUIREMENT, - bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT, - requirementsmod.SHARESAFE_REQUIREMENT, - requirementsmod.DIRSTATE_V2_REQUIREMENT, - } - _basesupported = supportedformats | { requirementsmod.STORE_REQUIREMENT, - requirementsmod.FNCACHE_REQUIREMENT, - requirementsmod.SHARED_REQUIREMENT, - requirementsmod.RELATIVE_SHARED_REQUIREMENT, - requirementsmod.DOTENCODE_REQUIREMENT, - requirementsmod.SPARSE_REQUIREMENT, - requirementsmod.INTERNAL_PHASE_REQUIREMENT, + requirementsmod.TREEMANIFEST_REQUIREMENT, } # list of prefix for file which can be written without 'wlock' @@ -1748,7 +1743,9 @@ class localrepository(object): """Extension point for wrapping the dirstate per-repo.""" sparsematchfn = lambda: sparse.matcher(self) v2_req = requirementsmod.DIRSTATE_V2_REQUIREMENT + th = requirementsmod.DIRSTATE_TRACKED_HINT_V1 use_dirstate_v2 = v2_req in self.requirements + use_tracked_hint = th in self.requirements return dirstate.dirstate( self.vfs, @@ -1758,6 +1755,7 @@ class localrepository(object): sparsematchfn, self.nodeconstants, use_dirstate_v2, + use_tracked_hint=use_tracked_hint, ) def _dirstatevalidate(self, node): @@ -3551,6 +3549,10 @@ def clone_requirements(ui, createopts, s depends on the configuration """ target_requirements = set() + if not srcrepo.requirements: + # this is a legacy revlog "v0" repository, we cannot do anything fancy + # with it. + return target_requirements createopts = defaultcreateopts(ui, createopts=createopts) for r in newreporequirements(ui, createopts): if r in requirementsmod.WORKING_DIR_REQUIREMENTS: @@ -3568,16 +3570,6 @@ def newreporequirements(ui, createopts): Extensions can wrap this function to specify custom requirements for new repositories. """ - # If the repo is being created from a shared repository, we copy - # its requirements. - if b'sharedrepo' in createopts: - requirements = set(createopts[b'sharedrepo'].requirements) - if createopts.get(b'sharedrelative'): - requirements.add(requirementsmod.RELATIVE_SHARED_REQUIREMENT) - else: - requirements.add(requirementsmod.SHARED_REQUIREMENT) - - return requirements if b'backend' not in createopts: raise error.ProgrammingError( @@ -3663,7 +3655,7 @@ def newreporequirements(ui, createopts): requirements.add(b'lfs') if ui.configbool(b'format', b'bookmarks-in-store'): - requirements.add(bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT) + requirements.add(requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT) if ui.configbool(b'format', b'use-persistent-nodemap'): requirements.add(requirementsmod.NODEMAP_REQUIREMENT) @@ -3673,6 +3665,45 @@ def newreporequirements(ui, createopts): if ui.configbool(b'format', b'use-share-safe'): requirements.add(requirementsmod.SHARESAFE_REQUIREMENT) + # if we are creating a share-repo¹ we have to handle requirement + # differently. + # + # [1] (i.e. reusing the store from another repository, just having a + # working copy) + if b'sharedrepo' in createopts: + source_requirements = set(createopts[b'sharedrepo'].requirements) + + if requirementsmod.SHARESAFE_REQUIREMENT not in source_requirements: + # share to an old school repository, we have to copy the + # requirements and hope for the best. + requirements = source_requirements + else: + # We have control on the working copy only, so "copy" the non + # working copy part over, ignoring previous logic. + to_drop = set() + for req in requirements: + if req in requirementsmod.WORKING_DIR_REQUIREMENTS: + continue + if req in source_requirements: + continue + to_drop.add(req) + requirements -= to_drop + requirements |= source_requirements + + if createopts.get(b'sharedrelative'): + requirements.add(requirementsmod.RELATIVE_SHARED_REQUIREMENT) + else: + requirements.add(requirementsmod.SHARED_REQUIREMENT) + + if ui.configbool(b'format', b'use-dirstate-tracked-hint'): + version = ui.configint(b'format', b'use-dirstate-tracked-hint.version') + msg = _("ignoring unknown tracked key version: %d\n") + hint = _("see `hg help config.format.use-dirstate-tracked-hint-version") + if version != 1: + ui.warn(msg % version, hint=hint) + else: + requirements.add(requirementsmod.DIRSTATE_TRACKED_HINT_V1) + return requirements @@ -3685,7 +3716,7 @@ def checkrequirementscompat(ui, requirem dropped = set() if requirementsmod.STORE_REQUIREMENT not in requirements: - if bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT in requirements: + if requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT in requirements: ui.warn( _( b'ignoring enabled \'format.bookmarks-in-store\' config ' @@ -3693,7 +3724,7 @@ def checkrequirementscompat(ui, requirem b'\'format.usestore\' config\n' ) ) - dropped.add(bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT) + dropped.add(requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT) if ( requirementsmod.SHARED_REQUIREMENT in requirements @@ -3707,13 +3738,13 @@ def checkrequirementscompat(ui, requirem ) if requirementsmod.SHARESAFE_REQUIREMENT in requirements: - ui.warn( - _( + if ui.hasconfig(b'format', b'use-share-safe'): + msg = _( b"ignoring enabled 'format.use-share-safe' config because " b"it is incompatible with disabled 'format.usestore'" b" config\n" ) - ) + ui.warn(msg) dropped.add(requirementsmod.SHARESAFE_REQUIREMENT) return dropped diff --git a/mercurial/logcmdutil.py b/mercurial/logcmdutil.py --- a/mercurial/logcmdutil.py +++ b/mercurial/logcmdutil.py @@ -62,9 +62,9 @@ def getlimit(opts): try: limit = int(limit) except ValueError: - raise error.Abort(_(b'limit must be a positive integer')) + raise error.InputError(_(b'limit must be a positive integer')) if limit <= 0: - raise error.Abort(_(b'limit must be positive')) + raise error.InputError(_(b'limit must be positive')) else: limit = None return limit @@ -831,7 +831,7 @@ def _makematcher(repo, revs, wopts): # take the slow path. found = slowpath = True if not found: - raise error.Abort( + raise error.StateError( _( b'cannot follow file not in any of the specified ' b'revisions: "%s"' @@ -847,7 +847,7 @@ def _makematcher(repo, revs, wopts): slowpath = True continue else: - raise error.Abort( + raise error.StateError( _( b'cannot follow file not in parent ' b'revision: "%s"' @@ -858,7 +858,7 @@ def _makematcher(repo, revs, wopts): if not filelog: # A file exists in wdir but not in history, which means # the file isn't committed yet. - raise error.Abort( + raise error.StateError( _(b'cannot follow nonexistent file: "%s"') % f ) else: @@ -1108,11 +1108,13 @@ def _parselinerangeopt(repo, opts): try: pat, linerange = pat.rsplit(b',', 1) except ValueError: - raise error.Abort(_(b'malformatted line-range pattern %s') % pat) + raise error.InputError( + _(b'malformatted line-range pattern %s') % pat + ) try: fromline, toline = map(int, linerange.split(b':')) except ValueError: - raise error.Abort(_(b"invalid line range for %s") % pat) + raise error.InputError(_(b"invalid line range for %s") % pat) msg = _(b"line range pattern '%s' must match exactly one file") % pat fname = scmutil.parsefollowlinespattern(repo, None, pat, msg) linerangebyfname.append( @@ -1136,7 +1138,7 @@ def getlinerangerevs(repo, userrevs, opt linerangesbyrev = {} for fname, (fromline, toline) in _parselinerangeopt(repo, opts): if fname not in wctx: - raise error.Abort( + raise error.StateError( _(b'cannot follow file not in parent revision: "%s"') % fname ) fctx = wctx.filectx(fname) @@ -1271,7 +1273,7 @@ def displayrevs(ui, repo, revs, displaye def checkunsupportedgraphflags(pats, opts): for op in [b"newest_first"]: if op in opts and opts[op]: - raise error.Abort( + raise error.InputError( _(b"-G/--graph option is incompatible with --%s") % op.replace(b"_", b"-") ) diff --git a/mercurial/manifest.py b/mercurial/manifest.py --- a/mercurial/manifest.py +++ b/mercurial/manifest.py @@ -1819,8 +1819,8 @@ class manifestrevlog(object): def checksize(self): return self._revlog.checksize() - def revision(self, node, _df=None, raw=False): - return self._revlog.revision(node, _df=_df, raw=raw) + def revision(self, node, _df=None): + return self._revlog.revision(node, _df=_df) def rawdata(self, node, _df=None): return self._revlog.rawdata(node, _df=_df) diff --git a/mercurial/mdiff.py b/mercurial/mdiff.py --- a/mercurial/mdiff.py +++ b/mercurial/mdiff.py @@ -84,7 +84,7 @@ class diffopts(object): try: self.context = int(self.context) except ValueError: - raise error.Abort( + raise error.InputError( _(b'diff context lines count must be an integer, not %r') % pycompat.bytestr(self.context) ) diff --git a/mercurial/merge.py b/mercurial/merge.py --- a/mercurial/merge.py +++ b/mercurial/merge.py @@ -41,10 +41,9 @@ def _getcheckunknownconfig(repo, section valid = [b'abort', b'ignore', b'warn'] if config not in valid: validstr = b', '.join([b"'" + v + b"'" for v in valid]) - raise error.ConfigError( - _(b"%s.%s not valid ('%s' is none of %s)") - % (section, name, config, validstr) - ) + msg = _(b"%s.%s not valid ('%s' is none of %s)") + msg %= (section, name, config, validstr) + raise error.ConfigError(msg) return config @@ -337,10 +336,9 @@ def _checkcollision(repo, wmf, mresult): for f in pmmf: fold = util.normcase(f) if fold in foldmap: - raise error.StateError( - _(b"case-folding collision between %s and %s") - % (f, foldmap[fold]) - ) + msg = _(b"case-folding collision between %s and %s") + msg %= (f, foldmap[fold]) + raise error.StateError(msg) foldmap[fold] = f # check case-folding of directories @@ -348,10 +346,9 @@ def _checkcollision(repo, wmf, mresult): 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.StateError( - _(b"case-folding collision between %s and directory of %s") - % (lastfull, f) - ) + msg = _(b"case-folding collision between %s and directory of %s") + msg %= (lastfull, f) + raise error.StateError(msg) foldprefix = fold + b'/' unfoldprefix = f + b'/' lastfull = f @@ -491,7 +488,7 @@ def checkpathconflicts(repo, wctx, mctx, mresult.addfile( p, mergestatemod.ACTION_PATH_CONFLICT, - (pnew, mergestatemod.ACTION_REMOVE), + (pnew, b'r'), b'path conflict', ) remoteconflicts.remove(p) @@ -512,17 +509,6 @@ def _filternarrowactions(narrowmatch, br Raise an exception if the merge cannot be completed because the repo is narrowed. """ - # TODO: handle with nonconflicttypes - nonconflicttypes = { - mergestatemod.ACTION_ADD, - mergestatemod.ACTION_ADD_MODIFIED, - mergestatemod.ACTION_CREATED, - mergestatemod.ACTION_CREATED_MERGE, - mergestatemod.ACTION_FORGET, - mergestatemod.ACTION_GET, - mergestatemod.ACTION_REMOVE, - mergestatemod.ACTION_EXEC, - } # We mutate the items in the dict during iteration, so iterate # over a copy. for f, action in mresult.filemap(): @@ -530,21 +516,25 @@ def _filternarrowactions(narrowmatch, br pass elif not branchmerge: mresult.removefile(f) # just updating, ignore changes outside clone - elif action[0] in mergestatemod.NO_OP_ACTIONS: + elif action[0].no_op: mresult.removefile(f) # merge does not affect file - elif action[0] in nonconflicttypes: - raise error.Abort( - _( + elif action[0].narrow_safe: + if not f.endswith(b'/'): + mresult.removefile(f) # merge won't affect on-disk files + + mresult.addcommitinfo( + f, b'outside-narrow-merge-action', action[0].changes + ) + else: # TODO: handle the tree case + msg = _( b'merge affects file \'%s\' outside narrow, ' b'which is not yet supported' ) - % f, - hint=_(b'merging in the other direction may work'), - ) + hint = _(b'merging in the other direction may work') + raise error.Abort(msg % f, hint=hint) else: - raise error.Abort( - _(b'conflict in file \'%s\' is outside narrow clone') % f - ) + msg = _(b'conflict in file \'%s\' is outside narrow clone') + raise error.StateError(msg % f) class mergeresult(object): @@ -705,7 +695,7 @@ class mergeresult(object): mergestatemod.ACTION_PATH_CONFLICT_RESOLVE, ) and self._actionmapping[a] - and a not in mergestatemod.NO_OP_ACTIONS + and not a.no_op ): return True @@ -1207,7 +1197,7 @@ def calculateupdates( for f, a in mresult1.filemap(sort=True): m, args, msg = a - repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m)) + repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m.__bytes__())) if f in fbids: d = fbids[f] if m in d: @@ -1228,13 +1218,15 @@ def calculateupdates( repo.ui.debug(b" list of bids for %s:\n" % f) for m, l in sorted(bids.items()): for _f, args, msg in l: - repo.ui.debug(b' %s -> %s\n' % (msg, m)) + repo.ui.debug(b' %s -> %s\n' % (msg, m.__bytes__())) # bids is a mapping from action method to list af actions # Consensus? if len(bids) == 1: # all bids are the same kind of method m, l = list(bids.items())[0] if all(a == l[0] for a in l[1:]): # len(bids) is > 1 - repo.ui.note(_(b" %s: consensus for %s\n") % (f, m)) + repo.ui.note( + _(b" %s: consensus for %s\n") % (f, m.__bytes__()) + ) mresult.addfile(f, *l[0]) continue # If keep is an option, just do it. @@ -1292,11 +1284,12 @@ def calculateupdates( repo.ui.note(_(b' %s: multiple bids for merge action:\n') % f) for m, l in sorted(bids.items()): for _f, args, msg in l: - repo.ui.note(b' %s -> %s\n' % (msg, m)) + repo.ui.note(b' %s -> %s\n' % (msg, m.__bytes__())) # Pick random action. TODO: Instead, prompt user when resolving m, l = list(bids.items())[0] repo.ui.warn( - _(b' %s: ambiguous merge - picked %s action\n') % (f, m) + _(b' %s: ambiguous merge - picked %s action\n') + % (f, m.__bytes__()) ) mresult.addfile(f, *l[0]) continue @@ -1404,6 +1397,34 @@ def batchget(repo, mctx, wctx, wantfiled atomictemp=atomictemp, ) if wantfiledata: + # XXX note that there is a race window between the time we + # write the clean data into the file and we stats it. So another + # writing process meddling with the file content right after we + # wrote it could cause bad stat data to be gathered. + # + # They are 2 data we gather here + # - the mode: + # That we actually just wrote, we should not need to read + # it from disk, (except not all mode might have survived + # the disk round-trip, which is another issue: we should + # not depends on this) + # - the mtime, + # On system that support nanosecond precision, the mtime + # could be accurate enough to tell the two writes appart. + # However gathering it in a racy way make the mtime we + # gather "unreliable". + # + # (note: we get the size from the data we write, which is sane) + # + # So in theory the data returned here are fully racy, but in + # practice "it works mostly fine". + # + # Do not be surprised if you end up reading this while looking + # for the causes of some buggy status. Feel free to improve + # this in the future, but we cannot simply stop gathering + # information. Otherwise `hg status` call made after a large `hg + # update` runs would have to redo a similar amount of work to + # restore and compare all files content. s = wfctx.lstat() mode = s.st_mode mtime = timestamp.mtime_of(s) @@ -1495,7 +1516,8 @@ def applyupdates( # mergestate so that it can be reused on commit ms.addcommitinfo(f, op) - numupdates = mresult.len() - mresult.len(mergestatemod.NO_OP_ACTIONS) + num_no_op = mresult.len(mergestatemod.MergeAction.NO_OP_ACTIONS) + numupdates = mresult.len() - num_no_op progress = repo.ui.makeprogress( _(b'updating'), unit=_(b'files'), total=numupdates ) @@ -1599,9 +1621,9 @@ def applyupdates( progress.increment(item=f) # keep (noop, just log it) - for a in mergestatemod.NO_OP_ACTIONS: + for a in mergestatemod.MergeAction.NO_OP_ACTIONS: for f, args, msg in mresult.getactions((a,), sort=True): - repo.ui.debug(b" %s: %s -> %s\n" % (f, msg, a)) + repo.ui.debug(b" %s: %s -> %s\n" % (f, msg, a.__bytes__())) # no progress # directory rename, move local @@ -1690,10 +1712,8 @@ def applyupdates( ) try: - # premerge - tocomplete = [] for f, args, msg in mergeactions: - repo.ui.debug(b" %s: %s -> m (premerge)\n" % (f, msg)) + repo.ui.debug(b" %s: %s -> m\n" % (f, msg)) ms.addcommitinfo(f, {b'merged': b'yes'}) progress.increment(item=f) if f == b'.hgsubstate': # subrepo states need updating @@ -1702,16 +1722,6 @@ def applyupdates( ) continue wctx[f].audit() - complete, r = ms.preresolve(f, wctx) - if not complete: - numupdates += 1 - tocomplete.append((f, args, msg)) - - # 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) except error.InterventionRequired: @@ -1823,7 +1833,7 @@ def _update( If false, merging with an ancestor (fast-forward) is only allowed between different named branches. This flag is used by rebase extension as a temporary fix and should be avoided in general. - labels = labels to use for base, local and other + labels = labels to use for local, other, and base mergeforce = whether the merge was run with 'merge --force' (deprecated): if this is True, then 'force' should be True as well. @@ -1875,22 +1885,11 @@ def _update( # updatecheck='abort' to better suppport some of these callers. if updatecheck is None: updatecheck = UPDATECHECK_LINEAR - if updatecheck not in ( - UPDATECHECK_NONE, - UPDATECHECK_LINEAR, - UPDATECHECK_NO_CONFLICT, - ): - raise ValueError( - r'Invalid updatecheck %r (can accept %r)' - % ( - updatecheck, - ( - UPDATECHECK_NONE, - UPDATECHECK_LINEAR, - UPDATECHECK_NO_CONFLICT, - ), - ) - ) + okay = (UPDATECHECK_NONE, UPDATECHECK_LINEAR, UPDATECHECK_NO_CONFLICT) + if updatecheck not in okay: + msg = r'Invalid updatecheck %r (can accept %r)' + msg %= (updatecheck, okay) + raise ValueError(msg) if wc is not None and wc.isinmemory(): maybe_wlock = util.nullcontextmanager() else: @@ -1919,29 +1918,22 @@ def _update( raise error.StateError(_(b"outstanding uncommitted merge")) ms = wc.mergestate() if ms.unresolvedcount(): - raise error.StateError( - _(b"outstanding merge conflicts"), - hint=_(b"use 'hg resolve' to resolve"), - ) + msg = _(b"outstanding merge conflicts") + hint = _(b"use 'hg resolve' to resolve") + raise error.StateError(msg, hint=hint) if branchmerge: + m_a = _(b"merging with a working directory ancestor has no effect") if pas == [p2]: - raise error.Abort( - _( - b"merging with a working directory ancestor" - b" has no effect" - ) - ) + raise error.Abort(m_a) elif pas == [p1]: if not mergeancestor and wc.branch() == p2.branch(): - raise error.Abort( - _(b"nothing to merge"), - hint=_(b"use 'hg update' or check 'hg heads'"), - ) + msg = _(b"nothing to merge") + hint = _(b"use 'hg update' or check 'hg heads'") + raise error.Abort(msg, hint=hint) if not force and (wc.files() or wc.deleted()): - raise error.StateError( - _(b"uncommitted changes"), - hint=_(b"use 'hg status' to list changes"), - ) + msg = _(b"uncommitted changes") + hint = _(b"use 'hg status' to list changes") + raise error.StateError(msg, hint=hint) if not wc.isinmemory(): for s in sorted(wc.substate): wc.sub(s).bailifchanged() @@ -2144,6 +2136,71 @@ def _update( mresult.len((mergestatemod.ACTION_GET,)) if wantfiledata else 0 ) with repo.dirstate.parentchange(): + ### Filter Filedata + # + # We gathered "cache" information for the clean file while + # updating them: mtime, size and mode. + # + # At the time this comment is written, they are various issues + # with how we gather the `mode` and `mtime` information (see + # the comment in `batchget`). + # + # We are going to smooth one of this issue here : mtime ambiguity. + # + # i.e. even if the mtime gathered during `batchget` was + # correct[1] a change happening right after it could change the + # content while keeping the same mtime[2]. + # + # When we reach the current code, the "on disk" part of the + # update operation is finished. We still assume that no other + # process raced that "on disk" part, but we want to at least + # prevent later file change to alter the content of the file + # right after the update operation. So quickly that the same + # mtime is record for the operation. + # To prevent such ambiguity to happens, we will only keep the + # "file data" for files with mtime that are stricly in the past, + # i.e. whose mtime is strictly lower than the current time. + # + # This protect us from race conditions from operation that could + # run right after this one, especially other Mercurial + # operation that could be waiting for the wlock to touch files + # content and the dirstate. + # + # In an ideal world, we could only get reliable information in + # `getfiledata` (from `getbatch`), however the current approach + # have been a successful compromise since many years. + # + # At the time this comment is written, not using any "cache" + # file data at all here would not be viable. As it would result is + # a very large amount of work (equivalent to the previous `hg + # update` during the next status after an update). + # + # [1] the current code cannot grantee that the `mtime` and + # `mode` are correct, but the result is "okay in practice". + # (see the comment in `batchget`). # + # + # [2] using nano-second precision can greatly help here because + # it makes the "different write with same mtime" issue + # virtually vanish. However, dirstate v1 cannot store such + # precision and a bunch of python-runtime, operating-system and + # filesystem does not provide use with such precision, so we + # have to operate as if it wasn't available. + if getfiledata: + ambiguous_mtime = {} + now = timestamp.get_fs_now(repo.vfs) + if now is None: + # we can't write to the FS, so we won't actually update + # the dirstate content anyway, no need to put cache + # information. + getfiledata = None + else: + now_sec = now[0] + for f, m in pycompat.iteritems(getfiledata): + if m is not None and m[2][0] >= now_sec: + ambiguous_mtime[f] = (m[0], m[1], None) + for f, m in pycompat.iteritems(ambiguous_mtime): + getfiledata[f] = m + repo.setparents(fp1, fp2) mergestatemod.recordupdates( repo, mresult.actionsdict, branchmerge, getfiledata @@ -2199,7 +2256,7 @@ def update(ctx, updatecheck=None, wc=Non ctx.rev(), branchmerge=False, force=False, - labels=[b'working copy', b'destination'], + labels=[b'working copy', b'destination', b'working copy parent'], updatecheck=updatecheck, wc=wc, ) @@ -2311,9 +2368,8 @@ def graft( def back_out(ctx, parent=None, wc=None): if parent is None: if ctx.p2() is not None: - raise error.ProgrammingError( - b"must specify parent of merge commit to back out" - ) + msg = b"must specify parent of merge commit to back out" + raise error.ProgrammingError(msg) parent = ctx.p1() return _update( ctx.repo(), @@ -2386,13 +2442,13 @@ def purge( 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 + nb_unknown = len(status.unknown) + if nb_unknown and nb_ignored: + msg = _(b"permanently delete %d unknown and %d ignored files?") + msg %= (nb_unknown, nb_ignored) + elif nb_unknown: + msg = _(b"permanently delete %d unknown files?") + msg %= nb_unknown elif nb_ignored: msg = _(b"permanently delete %d ignored files?") msg %= nb_ignored diff --git a/mercurial/mergestate.py b/mercurial/mergestate.py --- a/mercurial/mergestate.py +++ b/mercurial/mergestate.py @@ -4,6 +4,7 @@ import collections import errno import shutil import struct +import weakref from .i18n import _ from .node import ( @@ -97,36 +98,102 @@ LEGACY_MERGE_DRIVER_STATE = b'm' # This record was release in 3.7 and usage was removed in 5.6 LEGACY_MERGE_DRIVER_MERGE = b'D' +CHANGE_ADDED = b'added' +CHANGE_REMOVED = b'removed' +CHANGE_MODIFIED = b'modified' -ACTION_FORGET = b'f' -ACTION_REMOVE = b'r' -ACTION_ADD = b'a' -ACTION_GET = b'g' -ACTION_PATH_CONFLICT = b'p' -ACTION_PATH_CONFLICT_RESOLVE = b'pr' -ACTION_ADD_MODIFIED = b'am' -ACTION_CREATED = b'c' -ACTION_DELETED_CHANGED = b'dc' -ACTION_CHANGED_DELETED = b'cd' -ACTION_MERGE = b'm' -ACTION_LOCAL_DIR_RENAME_GET = b'dg' -ACTION_DIR_RENAME_MOVE_LOCAL = b'dm' -ACTION_KEEP = b'k' + +class MergeAction(object): + """represent an "action" merge need to take for a given file + + Attributes: + + _short: internal representation used to identify each action + + no_op: True if the action does affect the file content or tracking status + + narrow_safe: + True if the action can be safely used for a file outside of the narrow + set + + changes: + The types of changes that this actions involves. This is a work in + progress and not all actions have one yet. In addition, some requires + user changes and cannot be fully decided. The value currently available + are: + + - ADDED: the files is new in both parents + - REMOVED: the files existed in one parent and is getting removed + - MODIFIED: the files existed in at least one parent and is getting changed + """ + + ALL_ACTIONS = weakref.WeakSet() + NO_OP_ACTIONS = weakref.WeakSet() + + def __init__(self, short, no_op=False, narrow_safe=False, changes=None): + self._short = short + self.ALL_ACTIONS.add(self) + self.no_op = no_op + if self.no_op: + self.NO_OP_ACTIONS.add(self) + self.narrow_safe = narrow_safe + self.changes = changes + + def __hash__(self): + return hash(self._short) + + def __repr__(self): + return 'MergeAction<%s>' % self._short.decode('ascii') + + def __bytes__(self): + return self._short + + def __eq__(self, other): + if other is None: + return False + assert isinstance(other, MergeAction) + return self._short == other._short + + def __lt__(self, other): + return self._short < other._short + + +ACTION_FORGET = MergeAction(b'f', narrow_safe=True, changes=CHANGE_REMOVED) +ACTION_REMOVE = MergeAction(b'r', narrow_safe=True, changes=CHANGE_REMOVED) +ACTION_ADD = MergeAction(b'a', narrow_safe=True, changes=CHANGE_ADDED) +ACTION_GET = MergeAction(b'g', narrow_safe=True, changes=CHANGE_MODIFIED) +ACTION_PATH_CONFLICT = MergeAction(b'p') +ACTION_PATH_CONFLICT_RESOLVE = MergeAction('pr') +ACTION_ADD_MODIFIED = MergeAction( + b'am', narrow_safe=True, changes=CHANGE_ADDED +) # not 100% about the changes value here +ACTION_CREATED = MergeAction(b'c', narrow_safe=True, changes=CHANGE_ADDED) +ACTION_DELETED_CHANGED = MergeAction(b'dc') +ACTION_CHANGED_DELETED = MergeAction(b'cd') +ACTION_MERGE = MergeAction(b'm') +ACTION_LOCAL_DIR_RENAME_GET = MergeAction(b'dg') +ACTION_DIR_RENAME_MOVE_LOCAL = MergeAction(b'dm') +ACTION_KEEP = MergeAction(b'k', no_op=True) # the file was absent on local side before merge and we should # keep it absent (absent means file not present, it can be a result # of file deletion, rename etc.) -ACTION_KEEP_ABSENT = b'ka' +ACTION_KEEP_ABSENT = MergeAction(b'ka', no_op=True) # the file is absent on the ancestor and remote side of the merge # hence this file is new and we should keep it -ACTION_KEEP_NEW = b'kn' -ACTION_EXEC = b'e' -ACTION_CREATED_MERGE = b'cm' +ACTION_KEEP_NEW = MergeAction(b'kn', no_op=True) +ACTION_EXEC = MergeAction(b'e', narrow_safe=True, changes=CHANGE_MODIFIED) +ACTION_CREATED_MERGE = MergeAction( + b'cm', narrow_safe=True, changes=CHANGE_ADDED +) + -# actions which are no op -NO_OP_ACTIONS = ( - ACTION_KEEP, - ACTION_KEEP_ABSENT, - ACTION_KEEP_NEW, +# Used by concert to detect situation it does not like, not sure what the exact +# criteria is +CONVERT_MERGE_ACTIONS = ( + ACTION_MERGE, + ACTION_DIR_RENAME_MOVE_LOCAL, + ACTION_CHANGED_DELETED, + ACTION_DELETED_CHANGED, ) @@ -313,16 +380,15 @@ class _mergestate_base(object): """return extras stored with the mergestate for the given filename""" return self._stateextras[filename] - def _resolve(self, preresolve, dfile, wctx): - """rerun merge process for file path `dfile`. - Returns whether the merge was completed and the return value of merge - obtained from filemerge._filemerge(). - """ + def resolve(self, dfile, wctx): + """run merge process for dfile + + Returns the exit code of the merge.""" if self[dfile] in ( MERGE_RECORD_RESOLVED, LEGACY_RECORD_DRIVER_RESOLVED, ): - return True, 0 + return 0 stateentry = self._state[dfile] state, localkey, lfile, afile, anode, ofile, onode, flags = stateentry octx = self._repo[self._other] @@ -341,84 +407,63 @@ class _mergestate_base(object): fla = fca.flags() if b'x' in flags + flo + fla and b'l' not in flags + flo + fla: if fca.rev() == nullrev and flags != flo: - if preresolve: - self._repo.ui.warn( - _( - b'warning: cannot merge flags for %s ' - b'without common ancestor - keeping local flags\n' - ) - % afile + self._repo.ui.warn( + _( + b'warning: cannot merge flags for %s ' + b'without common ancestor - keeping local flags\n' ) + % afile + ) elif flags == fla: flags = flo - if preresolve: - # restore local - if localkey != self._repo.nodeconstants.nullhex: - self._restore_backup(wctx[dfile], localkey, flags) - else: - wctx[dfile].remove(ignoremissing=True) - complete, merge_ret, deleted = filemerge.premerge( - self._repo, - wctx, - self._local, - lfile, - fcd, - fco, - fca, - labels=self._labels, - ) + # restore local + if localkey != self._repo.nodeconstants.nullhex: + self._restore_backup(wctx[dfile], localkey, flags) else: - complete, merge_ret, deleted = filemerge.filemerge( - self._repo, - wctx, - self._local, - lfile, - fcd, - fco, - fca, - labels=self._labels, - ) - if merge_ret is None: + wctx[dfile].remove(ignoremissing=True) + + if not fco.cmp(fcd): # files identical? # If return value of merge is None, then there are no real conflict del self._state[dfile] + self._results[dfile] = None, None self._dirty = True - elif not merge_ret: + return None + + merge_ret, deleted = filemerge.filemerge( + self._repo, + wctx, + self._local, + lfile, + fcd, + fco, + fca, + labels=self._labels, + ) + + if not merge_ret: self.mark(dfile, MERGE_RECORD_RESOLVED) - if complete: - action = None - if deleted: - if fcd.isabsent(): - # dc: local picked. Need to drop if present, which may - # happen on re-resolves. - action = ACTION_FORGET - else: - # cd: remote picked (or otherwise deleted) - action = ACTION_REMOVE + action = None + if deleted: + if fcd.isabsent(): + # dc: local picked. Need to drop if present, which may + # happen on re-resolves. + action = ACTION_FORGET else: - if fcd.isabsent(): # dc: remote picked - action = ACTION_GET - elif fco.isabsent(): # cd: local picked - if dfile in self.localctx: - action = ACTION_ADD_MODIFIED - else: - action = ACTION_ADD - # else: regular merges (no action necessary) - self._results[dfile] = merge_ret, action - - return complete, merge_ret + # cd: remote picked (or otherwise deleted) + action = ACTION_REMOVE + else: + if fcd.isabsent(): # dc: remote picked + action = ACTION_GET + elif fco.isabsent(): # cd: local picked + if dfile in self.localctx: + action = ACTION_ADD_MODIFIED + else: + action = ACTION_ADD + # else: regular merges (no action necessary) + self._results[dfile] = merge_ret, action - def preresolve(self, dfile, wctx): - """run premerge process for dfile - - Returns whether the merge is complete, and the exit code.""" - return self._resolve(True, dfile, wctx) - - def resolve(self, dfile, wctx): - """run merge process (assuming premerge was run) for dfile - - Returns the exit code of the merge.""" - return self._resolve(False, dfile, wctx)[1] + return merge_ret def counts(self): """return counts for updated, merged and removed files in this diff --git a/mercurial/narrowspec.py b/mercurial/narrowspec.py --- a/mercurial/narrowspec.py +++ b/mercurial/narrowspec.py @@ -109,23 +109,24 @@ def validatepatterns(pats): and patterns that are loaded from sources that use the internal, prefixed pattern representation (but can't necessarily be fully trusted). """ - if not isinstance(pats, set): - raise error.ProgrammingError( - b'narrow patterns should be a set; got %r' % pats - ) + with util.timedcm('narrowspec.validatepatterns(pats size=%d)', len(pats)): + if not isinstance(pats, set): + raise error.ProgrammingError( + b'narrow patterns should be a set; got %r' % pats + ) - for pat in pats: - if not pat.startswith(VALID_PREFIXES): - # Use a Mercurial exception because this can happen due to user - # bugs (e.g. manually updating spec file). - raise error.Abort( - _(b'invalid prefix on narrow pattern: %s') % pat, - hint=_( - b'narrow patterns must begin with one of ' - b'the following: %s' + for pat in pats: + if not pat.startswith(VALID_PREFIXES): + # Use a Mercurial exception because this can happen due to user + # bugs (e.g. manually updating spec file). + raise error.Abort( + _(b'invalid prefix on narrow pattern: %s') % pat, + hint=_( + b'narrow patterns must begin with one of ' + b'the following: %s' + ) + % b', '.join(VALID_PREFIXES), ) - % b', '.join(VALID_PREFIXES), - ) def format(includes, excludes): @@ -323,7 +324,7 @@ def updateworkingcopy(repo, assumeclean= removedmatch = matchmod.differencematcher(oldmatch, newmatch) ds = repo.dirstate - lookup, status = ds.status( + lookup, status, _mtime_boundary = ds.status( removedmatch, subrepos=[], ignored=True, clean=True, unknown=True ) trackeddirty = status.modified + status.added diff --git a/mercurial/obsolete.py b/mercurial/obsolete.py --- a/mercurial/obsolete.py +++ b/mercurial/obsolete.py @@ -73,10 +73,6 @@ import errno import struct from .i18n import _ -from .node import ( - bin, - hex, -) from .pycompat import getattr from .node import ( bin, @@ -579,6 +575,12 @@ class obsstore(object): return len(self._all) def __nonzero__(self): + from . import statichttprepo + + if isinstance(self.repo, statichttprepo.statichttprepository): + # If repo is accessed via static HTTP, then we can't use os.stat() + # to just peek at the file size. + return len(self._data) > 1 if not self._cached('_all'): try: return self.svfs.stat(b'obsstore').st_size > 1 @@ -944,8 +946,7 @@ def _computeobsoleteset(repo): getnode = repo.changelog.node notpublic = _mutablerevs(repo) isobs = repo.obsstore.successors.__contains__ - obs = {r for r in notpublic if isobs(getnode(r))} - return obs + return frozenset(r for r in notpublic if isobs(getnode(r))) @cachefor(b'orphan') @@ -963,14 +964,14 @@ def _computeorphanset(repo): if p in obsolete or p in unstable: unstable.add(r) break - return unstable + return frozenset(unstable) @cachefor(b'suspended') def _computesuspendedset(repo): """the set of obsolete parents with non obsolete descendants""" suspended = repo.changelog.ancestors(getrevs(repo, b'orphan')) - return {r for r in getrevs(repo, b'obsolete') if r in suspended} + return frozenset(r for r in getrevs(repo, b'obsolete') if r in suspended) @cachefor(b'extinct') @@ -1002,7 +1003,7 @@ def _computephasedivergentset(repo): # we have a public predecessor bumped.add(rev) break # Next draft! - return bumped + return frozenset(bumped) @cachefor(b'contentdivergent') @@ -1029,7 +1030,7 @@ def _computecontentdivergentset(repo): divergent.add(rev) break toprocess.update(obsstore.predecessors.get(prec, ())) - return divergent + return frozenset(divergent) def makefoldid(relation, user): diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py --- a/mercurial/obsutil.py +++ b/mercurial/obsutil.py @@ -218,7 +218,7 @@ def exclusivemarkers(repo, nodes): or - # (A0 rewritten as AX; AX rewritten as A1; AX is unkown locally) + # (A0 rewritten as AX; AX rewritten as A1; AX is unknown locally) # # <-1- A0 <-2- AX <-3- A1 # Marker "2,3" are exclusive to A1 diff --git a/mercurial/patch.py b/mercurial/patch.py --- a/mercurial/patch.py +++ b/mercurial/patch.py @@ -55,6 +55,8 @@ wordsplitter = re.compile( ) PatchError = error.PatchError +PatchParseError = error.PatchParseError +PatchApplicationError = error.PatchApplicationError # public functions @@ -107,7 +109,9 @@ def split(stream): def mimesplit(stream, cur): def msgfp(m): fp = stringio() + # pytype: disable=wrong-arg-types g = mail.Generator(fp, mangle_from_=False) + # pytype: enable=wrong-arg-types g.flatten(m) fp.seek(0) return fp @@ -553,7 +557,9 @@ class workingbackend(fsbackend): if not self.repo.dirstate.get_entry(fname).any_tracked and self.exists( fname ): - raise PatchError(_(b'cannot patch %s: file is not tracked') % fname) + raise PatchApplicationError( + _(b'cannot patch %s: file is not tracked') % fname + ) def setfile(self, fname, data, mode, copysource): self._checkknown(fname) @@ -637,7 +643,9 @@ class repobackend(abstractbackend): def _checkknown(self, fname): if fname not in self.ctx: - raise PatchError(_(b'cannot patch %s: file is not tracked') % fname) + raise PatchApplicationError( + _(b'cannot patch %s: file is not tracked') % fname + ) def getfile(self, fname): try: @@ -793,7 +801,7 @@ class patchfile(object): def apply(self, h): if not h.complete(): - raise PatchError( + raise PatchParseError( _(b"bad hunk #%d %s (%d %d %d %d)") % (h.number, h.desc, len(h.a), h.lena, len(h.b), h.lenb) ) @@ -1388,7 +1396,7 @@ class hunk(object): def read_unified_hunk(self, lr): m = unidesc.match(self.desc) if not m: - raise PatchError(_(b"bad hunk #%d") % self.number) + raise PatchParseError(_(b"bad hunk #%d") % self.number) self.starta, self.lena, self.startb, self.lenb = m.groups() if self.lena is None: self.lena = 1 @@ -1405,7 +1413,7 @@ class hunk(object): lr, self.hunk, self.lena, self.lenb, self.a, self.b ) except error.ParseError as e: - raise PatchError(_(b"bad hunk #%d: %s") % (self.number, e)) + raise PatchParseError(_(b"bad hunk #%d: %s") % (self.number, e)) # if we hit eof before finishing out the hunk, the last line will # be zero length. Lets try to fix it up. while len(self.hunk[-1]) == 0: @@ -1420,7 +1428,7 @@ class hunk(object): self.desc = lr.readline() m = contextdesc.match(self.desc) if not m: - raise PatchError(_(b"bad hunk #%d") % self.number) + raise PatchParseError(_(b"bad hunk #%d") % self.number) self.starta, aend = m.groups() self.starta = int(self.starta) if aend is None: @@ -1440,7 +1448,7 @@ class hunk(object): elif l.startswith(b' '): u = b' ' + s else: - raise PatchError( + raise PatchParseError( _(b"bad hunk #%d old text line %d") % (self.number, x) ) self.a.append(u) @@ -1454,7 +1462,7 @@ class hunk(object): l = lr.readline() m = contextdesc.match(l) if not m: - raise PatchError(_(b"bad hunk #%d") % self.number) + raise PatchParseError(_(b"bad hunk #%d") % self.number) self.startb, bend = m.groups() self.startb = int(self.startb) if bend is None: @@ -1487,7 +1495,7 @@ class hunk(object): lr.push(l) break else: - raise PatchError( + raise PatchParseError( _(b"bad hunk #%d old text line %d") % (self.number, x) ) self.b.append(s) @@ -1601,7 +1609,7 @@ class binhunk(object): while True: line = getline(lr, self.hunk) if not line: - raise PatchError( + raise PatchParseError( _(b'could not extract "%s" binary data') % self._fname ) if line.startswith(b'literal '): @@ -1622,14 +1630,14 @@ class binhunk(object): try: dec.append(util.b85decode(line[1:])[:l]) except ValueError as e: - raise PatchError( + raise PatchParseError( _(b'could not decode "%s" binary patch: %s') % (self._fname, stringutil.forcebytestr(e)) ) line = getline(lr, self.hunk) text = zlib.decompress(b''.join(dec)) if len(text) != size: - raise PatchError( + raise PatchParseError( _(b'"%s" length is %d bytes, should be %d') % (self._fname, len(text), size) ) @@ -1847,7 +1855,7 @@ def parsepatch(originalchunks, maxcontex try: p.transitions[state][newstate](p, data) except KeyError: - raise PatchError( + raise PatchParseError( b'unhandled transition: %s -> %s' % (state, newstate) ) state = newstate @@ -1874,7 +1882,7 @@ def pathtransform(path, strip, prefix): ('a//b/', 'd/e/c') >>> pathtransform(b'a/b/c', 3, b'') Traceback (most recent call last): - PatchError: unable to strip away 1 of 3 dirs from a/b/c + PatchApplicationError: unable to strip away 1 of 3 dirs from a/b/c """ pathlen = len(path) i = 0 @@ -1884,7 +1892,7 @@ def pathtransform(path, strip, prefix): while count > 0: i = path.find(b'/', i) if i == -1: - raise PatchError( + raise PatchApplicationError( _(b"unable to strip away %d of %d dirs from %s") % (count, strip, path) ) @@ -1947,7 +1955,7 @@ def makepatchmeta(backend, afile_orig, b elif not nulla: fname = afile else: - raise PatchError(_(b"undefined source and destination files")) + raise PatchParseError(_(b"undefined source and destination files")) gp = patchmeta(fname) if create: @@ -2097,7 +2105,7 @@ def iterhunks(fp): gp.copy(), ) if not gitpatches: - raise PatchError( + raise PatchParseError( _(b'failed to synchronize metadata for "%s"') % afile[2:] ) newfile = True @@ -2193,7 +2201,7 @@ def applybindelta(binchunk, data): out += binchunk[i:offset_end] i += cmd else: - raise PatchError(_(b'unexpected delta opcode 0')) + raise PatchApplicationError(_(b'unexpected delta opcode 0')) return out @@ -2270,7 +2278,7 @@ def _applydiff( data, mode = store.getfile(gp.oldpath)[:2] if data is None: # This means that the old path does not exist - raise PatchError( + raise PatchApplicationError( _(b"source file '%s' does not exist") % gp.oldpath ) if gp.mode: @@ -2283,7 +2291,7 @@ def _applydiff( if gp.op in (b'ADD', b'RENAME', b'COPY') and backend.exists( gp.path ): - raise PatchError( + raise PatchApplicationError( _( b"cannot create %s: destination " b"already exists" @@ -2365,7 +2373,7 @@ def _externalpatch(ui, repo, patcher, pa scmutil.marktouched(repo, files, similarity) code = fp.close() if code: - raise PatchError( + raise PatchApplicationError( _(b"patch command failed: %s") % procutil.explainexit(code) ) return fuzz @@ -2397,7 +2405,7 @@ def patchbackend( files.update(backend.close()) store.close() if ret < 0: - raise PatchError(_(b'patch failed to apply')) + raise PatchApplicationError(_(b'patch failed to apply')) return ret > 0 diff --git a/mercurial/pathutil.py b/mercurial/pathutil.py --- a/mercurial/pathutil.py +++ b/mercurial/pathutil.py @@ -79,20 +79,24 @@ class pathauditor(object): return # AIX ignores "/" at end of path, others raise EISDIR. if util.endswithsep(path): - raise error.Abort(_(b"path ends in directory separator: %s") % path) + raise error.InputError( + _(b"path ends in directory separator: %s") % path + ) parts = util.splitpath(path) if ( os.path.splitdrive(path)[0] or _lowerclean(parts[0]) in (b'.hg', b'.hg.', b'') or pycompat.ospardir in parts ): - raise error.Abort(_(b"path contains illegal component: %s") % path) + raise error.InputError( + _(b"path contains illegal component: %s") % path + ) # Windows shortname aliases for p in parts: if b"~" in p: first, last = p.split(b"~", 1) if last.isdigit() and first.upper() in [b"HG", b"HG8B6C"]: - raise error.Abort( + raise error.InputError( _(b"path contains illegal component: %s") % path ) if b'.hg' in _lowerclean(path): @@ -101,7 +105,7 @@ class pathauditor(object): if p in lparts[1:]: pos = lparts.index(p) base = os.path.join(*parts[:pos]) - raise error.Abort( + raise error.InputError( _(b"path '%s' is inside nested repo %r") % (path, pycompat.bytestr(base)) ) diff --git a/mercurial/pure/parsers.py b/mercurial/pure/parsers.py --- a/mercurial/pure/parsers.py +++ b/mercurial/pure/parsers.py @@ -104,6 +104,7 @@ class DirstateItem(object): _mtime_ns = attr.ib() _fallback_exec = attr.ib() _fallback_symlink = attr.ib() + _mtime_second_ambiguous = attr.ib() def __init__( self, @@ -127,24 +128,27 @@ class DirstateItem(object): self._size = None self._mtime_s = None self._mtime_ns = None + self._mtime_second_ambiguous = False if parentfiledata is None: has_meaningful_mtime = False has_meaningful_data = False + elif parentfiledata[2] is None: + has_meaningful_mtime = False if has_meaningful_data: self._mode = parentfiledata[0] self._size = parentfiledata[1] if has_meaningful_mtime: - self._mtime_s, self._mtime_ns = parentfiledata[2] + ( + self._mtime_s, + self._mtime_ns, + self._mtime_second_ambiguous, + ) = parentfiledata[2] @classmethod def from_v2_data(cls, flags, size, mtime_s, mtime_ns): """Build a new DirstateItem object from V2 data""" has_mode_size = bool(flags & DIRSTATE_V2_HAS_MODE_AND_SIZE) has_meaningful_mtime = bool(flags & DIRSTATE_V2_HAS_MTIME) - if flags & DIRSTATE_V2_MTIME_SECOND_AMBIGUOUS: - # The current code is not able to do the more subtle comparison that the - # MTIME_SECOND_AMBIGUOUS requires. So we ignore the mtime - has_meaningful_mtime = False mode = None if flags & +DIRSTATE_V2_EXPECTED_STATE_IS_MODIFIED: @@ -171,13 +175,15 @@ class DirstateItem(object): mode |= stat.S_IFLNK else: mode |= stat.S_IFREG + + second_ambiguous = flags & DIRSTATE_V2_MTIME_SECOND_AMBIGUOUS return cls( wc_tracked=bool(flags & DIRSTATE_V2_WDIR_TRACKED), p1_tracked=bool(flags & DIRSTATE_V2_P1_TRACKED), p2_info=bool(flags & DIRSTATE_V2_P2_INFO), has_meaningful_data=has_mode_size, has_meaningful_mtime=has_meaningful_mtime, - parentfiledata=(mode, size, (mtime_s, mtime_ns)), + parentfiledata=(mode, size, (mtime_s, mtime_ns, second_ambiguous)), fallback_exec=fallback_exec, fallback_symlink=fallback_symlink, ) @@ -214,13 +220,13 @@ class DirstateItem(object): wc_tracked=True, p1_tracked=True, has_meaningful_mtime=False, - parentfiledata=(mode, size, (42, 0)), + parentfiledata=(mode, size, (42, 0, False)), ) else: return cls( wc_tracked=True, p1_tracked=True, - parentfiledata=(mode, size, (mtime, 0)), + parentfiledata=(mode, size, (mtime, 0, False)), ) else: raise RuntimeError(b'unknown state: %s' % state) @@ -246,7 +252,7 @@ class DirstateItem(object): self._p1_tracked = True self._mode = mode self._size = size - self._mtime_s, self._mtime_ns = mtime + self._mtime_s, self._mtime_ns, self._mtime_second_ambiguous = mtime def set_tracked(self): """mark a file as tracked in the working copy @@ -301,10 +307,22 @@ class DirstateItem(object): if self_sec is None: return False self_ns = self._mtime_ns - other_sec, other_ns = other_mtime - return self_sec == other_sec and ( - self_ns == other_ns or self_ns == 0 or other_ns == 0 - ) + other_sec, other_ns, second_ambiguous = other_mtime + if self_sec != other_sec: + # seconds are different theses mtime are definitly not equal + return False + elif other_ns == 0 or self_ns == 0: + # at least one side as no nano-seconds information + + if self._mtime_second_ambiguous: + # We cannot trust the mtime in this case + return False + else: + # the "seconds" value was reliable on its own. We are good to go. + return True + else: + # We have nano second information, let us use them ! + return self_ns == other_ns @property def state(self): @@ -463,6 +481,8 @@ class DirstateItem(object): flags |= DIRSTATE_V2_MODE_IS_SYMLINK if self._mtime_s is not None: flags |= DIRSTATE_V2_HAS_MTIME + if self._mtime_second_ambiguous: + flags |= DIRSTATE_V2_MTIME_SECOND_AMBIGUOUS if self._fallback_exec is not None: flags |= DIRSTATE_V2_HAS_FALLBACK_EXEC @@ -531,13 +551,11 @@ class DirstateItem(object): return AMBIGUOUS_TIME elif not self._p1_tracked: return AMBIGUOUS_TIME + elif self._mtime_second_ambiguous: + return AMBIGUOUS_TIME else: return self._mtime_s - def need_delay(self, now): - """True if the stored mtime would be ambiguous with the current time""" - return self.v1_state() == b'n' and self._mtime_s == now[0] - def gettype(q): return int(q & 0xFFFF) @@ -566,18 +584,13 @@ class BaseIndexObject(object): 0, revlog_constants.COMP_MODE_INLINE, revlog_constants.COMP_MODE_INLINE, + revlog_constants.RANK_UNKNOWN, ) @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]" - util.nouideprecwarn(msg, b'5.3', stacklevel=2) - return self._nodemap - @util.propertycache def _nodemap(self): nodemap = nodemaputil.NodeMap({sha1nodeconstants.nullid: nullrev}) @@ -629,7 +642,7 @@ class BaseIndexObject(object): if not isinstance(i, int): raise TypeError(b"expecting int indexes") if i < 0 or i >= len(self): - raise IndexError + raise IndexError(i) def __getitem__(self, i): if i == -1: @@ -653,6 +666,7 @@ class BaseIndexObject(object): 0, revlog_constants.COMP_MODE_INLINE, revlog_constants.COMP_MODE_INLINE, + revlog_constants.RANK_UNKNOWN, ) return r @@ -785,9 +799,14 @@ class InlinedIndexObject(BaseIndexObject return self._offsets[i] -def parse_index2(data, inline, revlogv2=False): +def parse_index2(data, inline, format=revlog_constants.REVLOGV1): + if format == revlog_constants.CHANGELOGV2: + return parse_index_cl_v2(data) if not inline: - cls = IndexObject2 if revlogv2 else IndexObject + if format == revlog_constants.REVLOGV2: + cls = IndexObject2 + else: + cls = IndexObject return cls(data), None cls = InlinedIndexObject return cls(data, inline), (0, data) @@ -835,7 +854,7 @@ class IndexObject2(IndexObject): entry = data[:10] data_comp = data[10] & 3 sidedata_comp = (data[10] & (3 << 2)) >> 2 - return entry + (data_comp, sidedata_comp) + return entry + (data_comp, sidedata_comp, revlog_constants.RANK_UNKNOWN) def _pack_entry(self, rev, entry): data = entry[:10] @@ -860,20 +879,53 @@ class IndexObject2(IndexObject): class IndexChangelogV2(IndexObject2): index_format = revlog_constants.INDEX_ENTRY_CL_V2 + null_item = ( + IndexObject2.null_item[: revlog_constants.ENTRY_RANK] + + (0,) # rank of null is 0 + + IndexObject2.null_item[revlog_constants.ENTRY_RANK :] + ) + def _unpack_entry(self, rev, data, r=True): items = self.index_format.unpack(data) - entry = items[:3] + (rev, rev) + items[3:8] - data_comp = items[8] & 3 - sidedata_comp = (items[8] >> 2) & 3 - return entry + (data_comp, sidedata_comp) + return ( + items[revlog_constants.INDEX_ENTRY_V2_IDX_OFFSET], + items[revlog_constants.INDEX_ENTRY_V2_IDX_COMPRESSED_LENGTH], + items[revlog_constants.INDEX_ENTRY_V2_IDX_UNCOMPRESSED_LENGTH], + rev, + rev, + items[revlog_constants.INDEX_ENTRY_V2_IDX_PARENT_1], + items[revlog_constants.INDEX_ENTRY_V2_IDX_PARENT_2], + items[revlog_constants.INDEX_ENTRY_V2_IDX_NODEID], + items[revlog_constants.INDEX_ENTRY_V2_IDX_SIDEDATA_OFFSET], + items[ + revlog_constants.INDEX_ENTRY_V2_IDX_SIDEDATA_COMPRESSED_LENGTH + ], + items[revlog_constants.INDEX_ENTRY_V2_IDX_COMPRESSION_MODE] & 3, + (items[revlog_constants.INDEX_ENTRY_V2_IDX_COMPRESSION_MODE] >> 2) + & 3, + items[revlog_constants.INDEX_ENTRY_V2_IDX_RANK], + ) def _pack_entry(self, rev, entry): - assert entry[3] == rev, entry[3] - assert entry[4] == rev, entry[4] - data = entry[:3] + entry[5:10] - data_comp = entry[10] & 3 - sidedata_comp = (entry[11] & 3) << 2 - data += (data_comp | sidedata_comp,) + + base = entry[revlog_constants.ENTRY_DELTA_BASE] + link_rev = entry[revlog_constants.ENTRY_LINK_REV] + assert base == rev, (base, rev) + assert link_rev == rev, (link_rev, rev) + data = ( + entry[revlog_constants.ENTRY_DATA_OFFSET], + entry[revlog_constants.ENTRY_DATA_COMPRESSED_LENGTH], + entry[revlog_constants.ENTRY_DATA_UNCOMPRESSED_LENGTH], + entry[revlog_constants.ENTRY_PARENT_1], + entry[revlog_constants.ENTRY_PARENT_2], + entry[revlog_constants.ENTRY_NODE_ID], + entry[revlog_constants.ENTRY_SIDEDATA_OFFSET], + entry[revlog_constants.ENTRY_SIDEDATA_COMPRESSED_LENGTH], + entry[revlog_constants.ENTRY_DATA_COMPRESSION_MODE] & 3 + | (entry[revlog_constants.ENTRY_SIDEDATA_COMPRESSION_MODE] & 3) + << 2, + entry[revlog_constants.ENTRY_RANK], + ) return self.index_format.pack(*data) @@ -903,23 +955,11 @@ def parse_dirstate(dmap, copymap, st): return parents -def pack_dirstate(dmap, copymap, pl, now): +def pack_dirstate(dmap, copymap, pl): cs = stringio() write = cs.write write(b"".join(pl)) for f, e in pycompat.iteritems(dmap): - if e.need_delay(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. - e.set_possibly_dirty() - if f in copymap: f = b"%s\0%s" % (f, copymap[f]) e = _pack( diff --git a/mercurial/requirements.py b/mercurial/requirements.py --- a/mercurial/requirements.py +++ b/mercurial/requirements.py @@ -7,11 +7,18 @@ from __future__ import absolute_import +# obsolete experimental requirements: +# - manifestv2: An experimental new manifest format that allowed +# for stem compression of long paths. Experiment ended up not +# being successful (repository sizes went up due to worse delta +# chains), and the code was deleted in 4.6. + GENERALDELTA_REQUIREMENT = b'generaldelta' DOTENCODE_REQUIREMENT = b'dotencode' STORE_REQUIREMENT = b'store' FNCACHE_REQUIREMENT = b'fncache' +DIRSTATE_TRACKED_HINT_V1 = b'dirstate-tracked-key-v1' DIRSTATE_V2_REQUIREMENT = b'dirstate-v2' # When narrowing is finalized and no longer subject to format changes, @@ -30,6 +37,9 @@ TREEMANIFEST_REQUIREMENT = b'treemanifes REVLOGV1_REQUIREMENT = b'revlogv1' +# allow using ZSTD as compression engine for revlog content +REVLOG_COMPRESSION_ZSTD = b'revlog-compression-zstd' + # Increment the sub-version when the revlog v2 format changes to lock out old # clients. CHANGELOGV2_REQUIREMENT = b'exp-changelog-v2' @@ -66,6 +76,10 @@ RELATIVE_SHARED_REQUIREMENT = b'relshare # `.hg/store/requires` are present. SHARESAFE_REQUIREMENT = b'share-safe' +# Bookmarks must be stored in the `store` part of the repository and will be +# share accross shares +BOOKMARKS_IN_STORE_REQUIREMENT = b'bookmarksinstore' + # List of requirements which are working directory specific # These requirements cannot be shared between repositories if they # share the same store @@ -83,5 +97,25 @@ WORKING_DIR_REQUIREMENTS = { SHARED_REQUIREMENT, RELATIVE_SHARED_REQUIREMENT, SHARESAFE_REQUIREMENT, + DIRSTATE_TRACKED_HINT_V1, DIRSTATE_V2_REQUIREMENT, } + +# List of requirement that impact "stream-clone" (and hardlink clone) and +# cannot be changed in such cases. +# +# requirements not in this list are safe to be altered during stream-clone. +# +# note: the list is currently inherited from previous code and miss some relevant requirement while containing some irrelevant ones. +STREAM_FIXED_REQUIREMENTS = { + BOOKMARKS_IN_STORE_REQUIREMENT, + CHANGELOGV2_REQUIREMENT, + COPIESSDC_REQUIREMENT, + GENERALDELTA_REQUIREMENT, + INTERNAL_PHASE_REQUIREMENT, + REVLOG_COMPRESSION_ZSTD, + REVLOGV1_REQUIREMENT, + REVLOGV2_REQUIREMENT, + SPARSEREVLOG_REQUIREMENT, + TREEMANIFEST_REQUIREMENT, +} diff --git a/mercurial/revlog.py b/mercurial/revlog.py --- a/mercurial/revlog.py +++ b/mercurial/revlog.py @@ -40,11 +40,13 @@ from .revlogutils.constants import ( COMP_MODE_DEFAULT, COMP_MODE_INLINE, COMP_MODE_PLAIN, + ENTRY_RANK, FEATURES_BY_VERSION, FLAG_GENERALDELTA, FLAG_INLINE_DATA, INDEX_HEADER, KIND_CHANGELOG, + RANK_UNKNOWN, REVLOGV0, REVLOGV1, REVLOGV1_FLAGS, @@ -101,6 +103,7 @@ from .utils import ( REVLOGV0 REVLOGV1 REVLOGV2 +CHANGELOGV2 FLAG_INLINE_DATA FLAG_GENERALDELTA REVLOG_DEFAULT_FLAGS @@ -199,16 +202,13 @@ def parse_index_v1(data, inline): def parse_index_v2(data, inline): # call the C implementation to parse the index data - index, cache = parsers.parse_index2(data, inline, revlogv2=True) + index, cache = parsers.parse_index2(data, inline, format=REVLOGV2) return index, cache def parse_index_cl_v2(data, inline): # call the C implementation to parse the index data - assert not inline - from .pure.parsers import parse_index_cl_v2 - - index, cache = parse_index_cl_v2(data) + index, cache = parsers.parse_index2(data, inline, format=CHANGELOGV2) return index, cache @@ -741,21 +741,6 @@ class revlog(object): """iterate over all rev in this revlog (from start to stop)""" return storageutil.iterrevs(len(self), start=start, stop=stop) - @property - def nodemap(self): - msg = ( - b"revlog.nodemap is deprecated, " - b"use revlog.index.[has_node|rev|get_rev]" - ) - util.nouideprecwarn(msg, b'5.3', stacklevel=2) - return self.index.nodemap - - @property - def _nodecache(self): - msg = b"revlog._nodecache is deprecated, use revlog.index.nodemap" - util.nouideprecwarn(msg, b'5.3', stacklevel=2) - return self.index.nodemap - def hasnode(self, node): try: self.rev(node) @@ -870,7 +855,23 @@ class revlog(object): if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0: return self.rawsize(rev) - return len(self.revision(rev, raw=False)) + return len(self.revision(rev)) + + def fast_rank(self, rev): + """Return the rank of a revision if already known, or None otherwise. + + The rank of a revision is the size of the sub-graph it defines as a + head. Equivalently, the rank of a revision `r` is the size of the set + `ancestors(r)`, `r` included. + + This method returns the rank retrieved from the revlog in constant + time. It makes no attempt at computing unknown values for versions of + the revlog which do not persist the rank. + """ + rank = self.index[rev][ENTRY_RANK] + if rank == RANK_UNKNOWN: + return None + return rank def chainbase(self, rev): base = self._chainbasecache.get(rev) @@ -1776,33 +1777,13 @@ class revlog(object): return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2)) - def _processflags(self, text, flags, operation, raw=False): - """deprecated entry point to access flag processors""" - msg = b'_processflag(...) use the specialized variant' - util.nouideprecwarn(msg, b'5.2', stacklevel=2) - if raw: - return text, flagutil.processflagsraw(self, text, flags) - elif operation == b'read': - return flagutil.processflagsread(self, text, flags) - else: # write operation - return flagutil.processflagswrite(self, text, flags) - - def revision(self, nodeorrev, _df=None, raw=False): + def revision(self, nodeorrev, _df=None): """return an uncompressed revision of a given node or revision number. _df - an existing file handle to read from. (internal-only) - raw - an optional argument specifying if the revision data is to be - treated as raw data when applying flag transforms. 'raw' should be set - to True when generating changegroups or in debug commands. """ - if raw: - msg = ( - b'revlog.revision(..., raw=True) is deprecated, ' - b'use revlog.rawdata(...)' - ) - util.nouideprecwarn(msg, b'5.2', stacklevel=2) - return self._revisiondata(nodeorrev, _df, raw=raw) + return self._revisiondata(nodeorrev, _df) def sidedata(self, nodeorrev, _df=None): """a map of extra data related to the changeset but not part of the hash @@ -2479,6 +2460,19 @@ class revlog(object): # than ones we manually add. sidedata_offset = 0 + rank = RANK_UNKNOWN + if self._format_version == CHANGELOGV2: + if (p1r, p2r) == (nullrev, nullrev): + rank = 1 + elif p1r != nullrev and p2r == nullrev: + rank = 1 + self.fast_rank(p1r) + elif p1r == nullrev and p2r != nullrev: + rank = 1 + self.fast_rank(p2r) + else: # merge node + pmin, pmax = sorted((p1r, p2r)) + rank = 1 + self.fast_rank(pmax) + rank += sum(1 for _ in self.findmissingrevs([pmax], [pmin])) + e = revlogutils.entry( flags=flags, data_offset=offset, @@ -2493,6 +2487,7 @@ class revlog(object): sidedata_offset=sidedata_offset, sidedata_compressed_length=len(serialized_sidedata), sidedata_compression_mode=sidedata_compression_mode, + rank=rank, ) self.index.append(e) diff --git a/mercurial/revlogutils/__init__.py b/mercurial/revlogutils/__init__.py --- a/mercurial/revlogutils/__init__.py +++ b/mercurial/revlogutils/__init__.py @@ -12,6 +12,7 @@ from ..interfaces import repository # See mercurial.revlogutils.constants for doc COMP_MODE_INLINE = 2 +RANK_UNKNOWN = -1 def offset_type(offset, type): @@ -34,6 +35,7 @@ def entry( sidedata_offset=0, sidedata_compressed_length=0, sidedata_compression_mode=COMP_MODE_INLINE, + rank=RANK_UNKNOWN, ): """Build one entry from symbolic name @@ -56,6 +58,7 @@ def entry( sidedata_compressed_length, data_compression_mode, sidedata_compression_mode, + rank, ) diff --git a/mercurial/revlogutils/constants.py b/mercurial/revlogutils/constants.py --- a/mercurial/revlogutils/constants.py +++ b/mercurial/revlogutils/constants.py @@ -103,6 +103,17 @@ ENTRY_DATA_COMPRESSION_MODE = 10 # (see "COMP_MODE_*" constants for details) ENTRY_SIDEDATA_COMPRESSION_MODE = 11 +# [12] Revision rank: +# The number of revision under this one. +# +# Formally this is defined as : rank(X) = len(ancestors(X) + X) +# +# If rank == -1; then we do not have this information available. +# Only `null` has a rank of 0. +ENTRY_RANK = 12 + +RANK_UNKNOWN = -1 + ### main revlog header # We cannot rely on Struct.format is inconsistent for python <=3.6 versus above @@ -181,9 +192,20 @@ assert INDEX_ENTRY_V2.size == 32 * 3, IN # 8 bytes: sidedata offset # 4 bytes: sidedata compressed length # 1 bytes: compression mode (2 lower bit are data_compression_mode) -# 27 bytes: Padding to align to 96 bytes (see RevlogV2Plan wiki page) -INDEX_ENTRY_CL_V2 = struct.Struct(b">Qiiii20s12xQiB27x") -assert INDEX_ENTRY_CL_V2.size == 32 * 3, INDEX_ENTRY_V2.size +# 4 bytes: changeset rank (i.e. `len(::REV)`) +# 23 bytes: Padding to align to 96 bytes (see RevlogV2Plan wiki page) +INDEX_ENTRY_CL_V2 = struct.Struct(b">Qiiii20s12xQiBi23x") +assert INDEX_ENTRY_CL_V2.size == 32 * 3, INDEX_ENTRY_CL_V2.size +INDEX_ENTRY_V2_IDX_OFFSET = 0 +INDEX_ENTRY_V2_IDX_COMPRESSED_LENGTH = 1 +INDEX_ENTRY_V2_IDX_UNCOMPRESSED_LENGTH = 2 +INDEX_ENTRY_V2_IDX_PARENT_1 = 3 +INDEX_ENTRY_V2_IDX_PARENT_2 = 4 +INDEX_ENTRY_V2_IDX_NODEID = 5 +INDEX_ENTRY_V2_IDX_SIDEDATA_OFFSET = 6 +INDEX_ENTRY_V2_IDX_SIDEDATA_COMPRESSED_LENGTH = 7 +INDEX_ENTRY_V2_IDX_COMPRESSION_MODE = 8 +INDEX_ENTRY_V2_IDX_RANK = 9 # revlog index flags diff --git a/mercurial/revlogutils/deltas.py b/mercurial/revlogutils/deltas.py --- a/mercurial/revlogutils/deltas.py +++ b/mercurial/revlogutils/deltas.py @@ -526,7 +526,7 @@ def _textfromdelta(fh, revlog, baserev, else: # deltabase is rawtext before changed by flag processors, which is # equivalent to non-raw text - basetext = revlog.revision(baserev, _df=fh, raw=False) + basetext = revlog.revision(baserev, _df=fh) fulltext = mdiff.patch(basetext, delta) try: diff --git a/mercurial/revlogutils/flagutil.py b/mercurial/revlogutils/flagutil.py --- a/mercurial/revlogutils/flagutil.py +++ b/mercurial/revlogutils/flagutil.py @@ -32,6 +32,7 @@ REVIDX_DEFAULT_FLAGS REVIDX_FLAGS_ORDER REVIDX_RAWTEXT_CHANGING_FLAGS +# Keep this in sync with REVIDX_KNOWN_FLAGS in rust/hg-core/src/revlog/revlog.rs REVIDX_KNOWN_FLAGS = util.bitsfrom(REVIDX_FLAGS_ORDER) # Store flag processors (cf. 'addflagprocessor()' to register) diff --git a/mercurial/revlogutils/nodemap.py b/mercurial/revlogutils/nodemap.py --- a/mercurial/revlogutils/nodemap.py +++ b/mercurial/revlogutils/nodemap.py @@ -16,6 +16,7 @@ from ..node import hex from .. import ( error, + requirements, util, ) from . import docket as docket_mod @@ -34,6 +35,19 @@ def test_race_hook_1(): pass +def post_stream_cleanup(repo): + """The stream clone might needs to remove some file if persisten nodemap + was dropped while stream cloning + """ + if requirements.REVLOGV1_REQUIREMENT not in repo.requirements: + return + if requirements.NODEMAP_REQUIREMENT in repo.requirements: + return + unfi = repo.unfiltered() + delete_nodemap(None, unfi, unfi.changelog) + delete_nodemap(None, repo, unfi.manifestlog._rootstore._revlog) + + def persisted_data(revlog): """read the nodemap for a revlog from disk""" if revlog._nodemap_file is None: @@ -144,10 +158,12 @@ def update_persistent_nodemap(revlog): 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.tryunlink(revlog._nodemap_file) + prefix = revlog.radix + pattern = re.compile(br"(^|/)%s(-[0-9a-f]+\.nd|\.n(\.a)?)$" % prefix) + dirpath = revlog.opener.dirname(revlog._indexfile) + for f in revlog.opener.listdir(dirpath): + if pattern.match(f): + repo.svfs.tryunlink(f) def persist_nodemap(tr, revlog, pending=False, force=False): diff --git a/mercurial/revlogutils/revlogv0.py b/mercurial/revlogutils/revlogv0.py --- a/mercurial/revlogutils/revlogv0.py +++ b/mercurial/revlogutils/revlogv0.py @@ -47,12 +47,6 @@ class revlogoldindex(list): node_id=sha1nodeconstants.nullid, ) - @property - def nodemap(self): - msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]" - util.nouideprecwarn(msg, b'5.3', stacklevel=2) - return self._nodemap - @util.propertycache def _nodemap(self): nodemap = nodemaputil.NodeMap({sha1nodeconstants.nullid: node.nullrev}) diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py --- a/mercurial/scmutil.py +++ b/mercurial/scmutil.py @@ -180,6 +180,8 @@ def callcatch(ui, func): ) ) except error.RepoError as inst: + if isinstance(inst, error.RepoLookupError): + detailed_exit_code = 10 ui.error(_(b"abort: %s\n") % inst) if inst.hint: ui.error(_(b"(%s)\n") % inst.hint) @@ -341,13 +343,13 @@ class casecollisionauditor(object): if fl in self._loweredfiles and f not in self._dirstate: msg = _(b'possible case-folding collision for %s') % f if self._abort: - raise error.Abort(msg) + raise error.StateError(msg) self._ui.warn(_(b"warning: %s\n") % msg) self._loweredfiles.add(fl) self._newfiles.add(f) -def filteredhash(repo, maxrev): +def filteredhash(repo, maxrev, needobsolete=False): """build hash of filtered revisions in the current repoview. Multiple caches perform up-to-date validation by checking that the @@ -356,22 +358,31 @@ def filteredhash(repo, maxrev): of revisions in the view may change without the repository tiprev and tipnode changing. - This function hashes all the revs filtered from the view and returns - that SHA-1 digest. + This function hashes all the revs filtered from the view (and, optionally, + all obsolete revs) up to maxrev and returns that SHA-1 digest. """ cl = repo.changelog - if not cl.filteredrevs: - return None - key = cl._filteredrevs_hashcache.get(maxrev) - if not key: - revs = sorted(r for r in cl.filteredrevs if r <= maxrev) + if needobsolete: + obsrevs = obsolete.getrevs(repo, b'obsolete') + if not cl.filteredrevs and not obsrevs: + return None + key = (maxrev, hash(cl.filteredrevs), hash(obsrevs)) + else: + if not cl.filteredrevs: + return None + key = maxrev + obsrevs = frozenset() + + result = cl._filteredrevs_hashcache.get(key) + if not result: + revs = sorted(r for r in cl.filteredrevs | obsrevs if r <= maxrev) if revs: s = hashutil.sha1() for rev in revs: s.update(b'%d;' % rev) - key = s.digest() - cl._filteredrevs_hashcache[maxrev] = key - return key + result = s.digest() + cl._filteredrevs_hashcache[key] = result + return result def walkrepos(path, followsym=False, seen_dirs=None, recurse=False): @@ -2195,6 +2206,9 @@ def unhidehashlikerevs(repo, specs, hidd returns a repo object with the required changesets unhidden """ + if not specs: + return repo + if not repo.filtername or not repo.ui.configbool( b'experimental', b'directaccess' ): diff --git a/mercurial/shelve.py b/mercurial/shelve.py --- a/mercurial/shelve.py +++ b/mercurial/shelve.py @@ -1000,7 +1000,11 @@ def _rebaserestoredcommit( stats = merge.graft( repo, shelvectx, - labels=[b'working-copy', b'shelve'], + labels=[ + b'working-copy', + b'shelved change', + b'parent of shelved change', + ], keepconflictparent=True, ) if stats.unresolvedcount: diff --git a/mercurial/simplemerge.py b/mercurial/simplemerge.py --- a/mercurial/simplemerge.py +++ b/mercurial/simplemerge.py @@ -19,20 +19,14 @@ from __future__ import absolute_import from .i18n import _ -from .node import nullrev from . import ( error, mdiff, pycompat, - util, ) from .utils import stringutil -class CantReprocessAndShowBase(Exception): - pass - - def intersect(ra, rb): """Given two ranges return the range where they intersect or None. @@ -89,72 +83,6 @@ class Merge3Text(object): self.a = a self.b = b - def merge_lines( - self, - name_a=None, - name_b=None, - name_base=None, - start_marker=b'<<<<<<<', - mid_marker=b'=======', - end_marker=b'>>>>>>>', - base_marker=None, - localorother=None, - minimize=False, - ): - """Return merge in cvs-like form.""" - self.conflicts = False - newline = b'\n' - if len(self.a) > 0: - if self.a[0].endswith(b'\r\n'): - newline = b'\r\n' - elif self.a[0].endswith(b'\r'): - newline = b'\r' - if name_a and start_marker: - start_marker = start_marker + b' ' + name_a - if name_b and end_marker: - end_marker = end_marker + b' ' + name_b - if name_base and base_marker: - base_marker = base_marker + b' ' + name_base - merge_regions = self.merge_regions() - if minimize: - merge_regions = self.minimize(merge_regions) - for t in merge_regions: - what = t[0] - if what == b'unchanged': - for i in range(t[1], t[2]): - yield self.base[i] - elif what == b'a' or what == b'same': - for i in range(t[1], t[2]): - yield self.a[i] - elif what == b'b': - for i in range(t[1], t[2]): - yield self.b[i] - elif what == b'conflict': - if localorother == b'local': - for i in range(t[3], t[4]): - yield self.a[i] - elif localorother == b'other': - for i in range(t[5], t[6]): - yield self.b[i] - else: - self.conflicts = True - if start_marker is not None: - yield start_marker + newline - for i in range(t[3], t[4]): - yield self.a[i] - if base_marker is not None: - yield base_marker + newline - for i in range(t[1], t[2]): - yield self.base[i] - if mid_marker is not None: - yield mid_marker + newline - for i in range(t[5], t[6]): - yield self.b[i] - if end_marker is not None: - yield end_marker + newline - else: - raise ValueError(what) - def merge_groups(self): """Yield sequence of line groups. Each one is a tuple: @@ -170,7 +98,7 @@ class Merge3Text(object): 'b', lines Lines taken from b - 'conflict', base_lines, a_lines, b_lines + 'conflict', (base_lines, a_lines, b_lines) Lines from base were changed to either a or b and conflict. """ for t in self.merge_regions(): @@ -184,9 +112,11 @@ class Merge3Text(object): elif what == b'conflict': yield ( what, - self.base[t[1] : t[2]], - self.a[t[3] : t[4]], - self.b[t[5] : t[6]], + ( + self.base[t[1] : t[2]], + self.a[t[3] : t[4]], + self.b[t[5] : t[6]], + ), ) else: raise ValueError(what) @@ -280,67 +210,6 @@ class Merge3Text(object): ia = aend ib = bend - def minimize(self, merge_regions): - """Trim conflict regions of lines where A and B sides match. - - Lines where both A and B have made the same changes at the beginning - or the end of each merge region are eliminated from the conflict - region and are instead considered the same. - """ - for region in merge_regions: - if region[0] != b"conflict": - yield region - continue - # pytype thinks this tuple contains only 3 things, but - # that's clearly not true because this code successfully - # executes. It might be wise to rework merge_regions to be - # some kind of attrs type. - ( - issue, - z1, - z2, - a1, - a2, - b1, - b2, - ) = region # pytype: disable=bad-unpacking - alen = a2 - a1 - blen = b2 - b1 - - # find matches at the front - ii = 0 - while ( - ii < alen and ii < blen and self.a[a1 + ii] == self.b[b1 + ii] - ): - ii += 1 - startmatches = ii - - # find matches at the end - ii = 0 - while ( - ii < alen - and ii < blen - and self.a[a2 - ii - 1] == self.b[b2 - ii - 1] - ): - ii += 1 - endmatches = ii - - if startmatches > 0: - yield b'same', a1, a1 + startmatches - - yield ( - b'conflict', - z1, - z2, - a1 + startmatches, - a2 - endmatches, - b1 + startmatches, - b2 - endmatches, - ) - - if endmatches > 0: - yield b'same', a2 - endmatches, a2 - def find_sync_regions(self): """Return a list of sync regions, where both descendants match the base. @@ -403,39 +272,136 @@ class Merge3Text(object): return sl -def _verifytext(text, path, ui, opts): +def _verifytext(input): """verifies that text is non-binary (unless opts[text] is passed, then we just warn)""" - if stringutil.binary(text): - msg = _(b"%s looks like a binary file.") % path - if not opts.get('quiet'): - ui.warn(_(b'warning: %s\n') % msg) - if not opts.get('text'): - raise error.Abort(msg) - return text + if stringutil.binary(input.text()): + msg = _(b"%s looks like a binary file.") % input.fctx.path() + raise error.Abort(msg) + + +def _format_labels(*inputs): + pad = max(len(input.label) if input.label else 0 for input in inputs) + labels = [] + for input in inputs: + if input.label: + if input.label_detail: + label = ( + (input.label + b':').ljust(pad + 1) + + b' ' + + input.label_detail + ) + else: + label = input.label + # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ') + labels.append(stringutil.ellipsis(label, 80 - 8)) + else: + labels.append(None) + return labels + + +def _detect_newline(m3): + if len(m3.a) > 0: + if m3.a[0].endswith(b'\r\n'): + return b'\r\n' + elif m3.a[0].endswith(b'\r'): + return b'\r' + return b'\n' -def _picklabels(defaults, overrides): - if len(overrides) > 3: - raise error.Abort(_(b"can only specify three labels.")) - result = defaults[:] - for i, override in enumerate(overrides): - result[i] = override - return result +def _minimize(a_lines, b_lines): + """Trim conflict regions of lines where A and B sides match. + + Lines where both A and B have made the same changes at the beginning + or the end of each merge region are eliminated from the conflict + region and are instead considered the same. + """ + alen = len(a_lines) + blen = len(b_lines) + + # find matches at the front + ii = 0 + while ii < alen and ii < blen and a_lines[ii] == b_lines[ii]: + ii += 1 + startmatches = ii + + # find matches at the end + ii = 0 + while ii < alen and ii < blen and a_lines[-ii - 1] == b_lines[-ii - 1]: + ii += 1 + endmatches = ii + + lines_before = a_lines[:startmatches] + new_a_lines = a_lines[startmatches : alen - endmatches] + new_b_lines = b_lines[startmatches : blen - endmatches] + lines_after = a_lines[alen - endmatches :] + return lines_before, new_a_lines, new_b_lines, lines_after -def is_not_null(ctx): - if not util.safehasattr(ctx, "node"): - return False - return ctx.rev() != nullrev +def render_minimized( + m3, + name_a=None, + name_b=None, + start_marker=b'<<<<<<<', + mid_marker=b'=======', + end_marker=b'>>>>>>>', +): + """Return merge in cvs-like form.""" + newline = _detect_newline(m3) + conflicts = False + if name_a: + start_marker = start_marker + b' ' + name_a + if name_b: + end_marker = end_marker + b' ' + name_b + merge_groups = m3.merge_groups() + lines = [] + for what, group_lines in merge_groups: + if what == b'conflict': + conflicts = True + base_lines, a_lines, b_lines = group_lines + minimized = _minimize(a_lines, b_lines) + lines_before, a_lines, b_lines, lines_after = minimized + lines.extend(lines_before) + lines.append(start_marker + newline) + lines.extend(a_lines) + lines.append(mid_marker + newline) + lines.extend(b_lines) + lines.append(end_marker + newline) + lines.extend(lines_after) + else: + lines.extend(group_lines) + return lines, conflicts -def _mergediff(m3, name_a, name_b, name_base): +def render_merge3(m3, name_a, name_b, name_base): + """Render conflicts as 3-way conflict markers.""" + newline = _detect_newline(m3) + conflicts = False + lines = [] + for what, group_lines in m3.merge_groups(): + if what == b'conflict': + base_lines, a_lines, b_lines = group_lines + conflicts = True + lines.append(b'<<<<<<< ' + name_a + newline) + lines.extend(a_lines) + lines.append(b'||||||| ' + name_base + newline) + lines.extend(base_lines) + lines.append(b'=======' + newline) + lines.extend(b_lines) + lines.append(b'>>>>>>> ' + name_b + newline) + else: + lines.extend(group_lines) + return lines, conflicts + + +def render_mergediff(m3, name_a, name_b, name_base): + """Render conflicts as conflict markers with one snapshot and one diff.""" + newline = _detect_newline(m3) lines = [] conflicts = False - for group in m3.merge_groups(): - if group[0] == b'conflict': - base_lines, a_lines, b_lines = group[1:] + for what, group_lines in m3.merge_groups(): + if what == b'conflict': + base_lines, a_lines, b_lines = group_lines base_text = b''.join(base_lines) b_blocks = list( mdiff.allblocks( @@ -472,95 +438,95 @@ def _mergediff(m3, name_a, name_b, name_ for line in lines2[block[2] : block[3]]: yield b'+' + line - lines.append(b"<<<<<<<\n") + lines.append(b"<<<<<<<" + newline) if matching_lines(a_blocks) < matching_lines(b_blocks): - lines.append(b"======= %s\n" % name_a) + lines.append(b"======= " + name_a + newline) lines.extend(a_lines) - lines.append(b"------- %s\n" % name_base) - lines.append(b"+++++++ %s\n" % name_b) + lines.append(b"------- " + name_base + newline) + lines.append(b"+++++++ " + name_b + newline) lines.extend(diff_lines(b_blocks, base_lines, b_lines)) else: - lines.append(b"------- %s\n" % name_base) - lines.append(b"+++++++ %s\n" % name_a) + lines.append(b"------- " + name_base + newline) + lines.append(b"+++++++ " + name_a + newline) lines.extend(diff_lines(a_blocks, base_lines, a_lines)) - lines.append(b"======= %s\n" % name_b) + lines.append(b"======= " + name_b + newline) lines.extend(b_lines) - lines.append(b">>>>>>>\n") + lines.append(b">>>>>>>" + newline) conflicts = True else: - lines.extend(group[1]) + lines.extend(group_lines) return lines, conflicts -def simplemerge(ui, localctx, basectx, otherctx, **opts): +def _resolve(m3, sides): + lines = [] + for what, group_lines in m3.merge_groups(): + if what == b'conflict': + for side in sides: + lines.extend(group_lines[side]) + else: + lines.extend(group_lines) + return lines + + +class MergeInput(object): + def __init__(self, fctx, label=None, label_detail=None): + self.fctx = fctx + self.label = label + # If the "detail" part is set, then that is rendered after the label and + # separated by a ':'. The label is padded to make the ':' aligned among + # all merge inputs. + self.label_detail = label_detail + self._text = None + + def text(self): + if self._text is None: + # Merges were always run in the working copy before, which means + # they used decoded data, if the user defined any repository + # filters. + # + # Maintain that behavior today for BC, though perhaps in the future + # it'd be worth considering whether merging encoded data (what the + # repository usually sees) might be more useful. + self._text = self.fctx.decodeddata() + return self._text + + +def simplemerge( + local, + base, + other, + mode=b'merge', + allow_binary=False, +): """Performs the simplemerge algorithm. The merged result is written into `localctx`. """ - def readctx(ctx): - # Merges were always run in the working copy before, which means - # they used decoded data, if the user defined any repository - # filters. - # - # Maintain that behavior today for BC, though perhaps in the future - # it'd be worth considering whether merging encoded data (what the - # repository usually sees) might be more useful. - return _verifytext(ctx.decodeddata(), ctx.path(), ui, opts) - - mode = opts.get('mode', b'merge') - name_a, name_b, name_base = None, None, None - if mode != b'union': - name_a, name_b, name_base = _picklabels( - [localctx.path(), otherctx.path(), None], opts.get('label', []) - ) - - try: - localtext = readctx(localctx) - basetext = readctx(basectx) - othertext = readctx(otherctx) - except error.Abort: - return 1 + if not allow_binary: + _verifytext(local) + _verifytext(base) + _verifytext(other) - m3 = Merge3Text(basetext, localtext, othertext) - extrakwargs = { - b"localorother": opts.get("localorother", None), - b'minimize': True, - } + m3 = Merge3Text(base.text(), local.text(), other.text()) + conflicts = False if mode == b'union': - extrakwargs[b'start_marker'] = None - extrakwargs[b'mid_marker'] = None - extrakwargs[b'end_marker'] = None - elif name_base is not None: - extrakwargs[b'base_marker'] = b'|||||||' - extrakwargs[b'name_base'] = name_base - extrakwargs[b'minimize'] = False - - if mode == b'mergediff': - lines, conflicts = _mergediff(m3, name_a, name_b, name_base) + lines = _resolve(m3, (1, 2)) + elif mode == b'local': + lines = _resolve(m3, (1,)) + elif mode == b'other': + lines = _resolve(m3, (2,)) else: - lines = list( - m3.merge_lines( - name_a=name_a, name_b=name_b, **pycompat.strkwargs(extrakwargs) - ) - ) - conflicts = m3.conflicts - - # merge flags if necessary - flags = localctx.flags() - localflags = set(pycompat.iterbytestr(flags)) - otherflags = set(pycompat.iterbytestr(otherctx.flags())) - if is_not_null(basectx) and localflags != otherflags: - baseflags = set(pycompat.iterbytestr(basectx.flags())) - commonflags = localflags & otherflags - addedflags = (localflags ^ otherflags) - baseflags - flags = b''.join(sorted(commonflags | addedflags)) + if mode == b'mergediff': + labels = _format_labels(local, other, base) + lines, conflicts = render_mergediff(m3, *labels) + elif mode == b'merge3': + labels = _format_labels(local, other, base) + lines, conflicts = render_merge3(m3, *labels) + else: + labels = _format_labels(local, other) + lines, conflicts = render_minimized(m3, *labels) mergedtext = b''.join(lines) - if opts.get('print'): - ui.fout.write(mergedtext) - else: - localctx.write(mergedtext, flags) - - if conflicts and not mode == b'union': - return 1 + return mergedtext, conflicts diff --git a/mercurial/sparse.py b/mercurial/sparse.py --- a/mercurial/sparse.py +++ b/mercurial/sparse.py @@ -38,63 +38,66 @@ def parseconfig(ui, raw, action): Returns a tuple of includes, excludes, and profiles. """ - includes = set() - excludes = set() - profiles = set() - current = None - havesection = False + with util.timedcm( + 'sparse.parseconfig(ui, %d bytes, action=%s)', len(raw), action + ): + includes = set() + excludes = set() + profiles = set() + current = None + havesection = False - for line in raw.split(b'\n'): - line = line.strip() - if not line or line.startswith(b'#'): - # empty or comment line, skip - continue - elif line.startswith(b'%include '): - line = line[9:].strip() - if line: - profiles.add(line) - elif line == b'[include]': - if havesection and current != includes: - # TODO pass filename into this API so we can report it. - raise error.Abort( - _( - b'%(action)s config cannot have includes ' - b'after excludes' + for line in raw.split(b'\n'): + line = line.strip() + if not line or line.startswith(b'#'): + # empty or comment line, skip + continue + elif line.startswith(b'%include '): + line = line[9:].strip() + if line: + profiles.add(line) + elif line == b'[include]': + if havesection and current != includes: + # TODO pass filename into this API so we can report it. + raise error.Abort( + _( + b'%(action)s config cannot have includes ' + b'after excludes' + ) + % {b'action': action} ) - % {b'action': action} - ) - havesection = True - current = includes - continue - elif line == b'[exclude]': - havesection = True - current = excludes - elif line: - if current is None: - raise error.Abort( - _( - b'%(action)s config entry outside of ' - b'section: %(line)s' + havesection = True + current = includes + continue + elif line == b'[exclude]': + havesection = True + current = excludes + elif line: + if current is None: + raise error.Abort( + _( + b'%(action)s config entry outside of ' + b'section: %(line)s' + ) + % {b'action': action, b'line': line}, + hint=_( + b'add an [include] or [exclude] line ' + b'to declare the entry type' + ), ) - % {b'action': action, b'line': line}, - hint=_( - b'add an [include] or [exclude] line ' - b'to declare the entry type' - ), - ) - if line.strip().startswith(b'/'): - ui.warn( - _( - b'warning: %(action)s profile cannot use' - b' paths starting with /, ignoring %(line)s\n' + if line.strip().startswith(b'/'): + ui.warn( + _( + b'warning: %(action)s profile cannot use' + b' paths starting with /, ignoring %(line)s\n' + ) + % {b'action': action, b'line': line} ) - % {b'action': action, b'line': line} - ) - continue - current.add(line) + continue + current.add(line) - return includes, excludes, profiles + return includes, excludes, profiles # Exists as separate function to facilitate monkeypatching. @@ -396,7 +399,7 @@ def filterupdatesactions(repo, wctx, mct temporaryfiles.append(file) prunedactions[file] = action elif branchmerge: - if type not in mergestatemod.NO_OP_ACTIONS: + if not type.no_op: temporaryfiles.append(file) prunedactions[file] = action elif type == mergestatemod.ACTION_FORGET: @@ -600,38 +603,41 @@ def _updateconfigandrefreshwdir( repo, includes, excludes, profiles, force=False, removing=False ): """Update the sparse config and working directory state.""" - raw = repo.vfs.tryread(b'sparse') - oldincludes, oldexcludes, oldprofiles = parseconfig(repo.ui, raw, b'sparse') - - oldstatus = repo.status() - oldmatch = matcher(repo) - oldrequires = set(repo.requirements) + with repo.lock(): + raw = repo.vfs.tryread(b'sparse') + oldincludes, oldexcludes, oldprofiles = parseconfig( + repo.ui, raw, b'sparse' + ) - # TODO remove this try..except once the matcher integrates better - # with dirstate. We currently have to write the updated config - # because that will invalidate the matcher cache and force a - # re-read. We ideally want to update the cached matcher on the - # repo instance then flush the new config to disk once wdir is - # updated. But this requires massive rework to matcher() and its - # consumers. + oldstatus = repo.status() + oldmatch = matcher(repo) + oldrequires = set(repo.requirements) + + # TODO remove this try..except once the matcher integrates better + # with dirstate. We currently have to write the updated config + # because that will invalidate the matcher cache and force a + # re-read. We ideally want to update the cached matcher on the + # repo instance then flush the new config to disk once wdir is + # updated. But this requires massive rework to matcher() and its + # consumers. - if requirements.SPARSE_REQUIREMENT in oldrequires and removing: - repo.requirements.discard(requirements.SPARSE_REQUIREMENT) - scmutil.writereporequirements(repo) - elif requirements.SPARSE_REQUIREMENT not in oldrequires: - repo.requirements.add(requirements.SPARSE_REQUIREMENT) - scmutil.writereporequirements(repo) + if requirements.SPARSE_REQUIREMENT in oldrequires and removing: + repo.requirements.discard(requirements.SPARSE_REQUIREMENT) + scmutil.writereporequirements(repo) + elif requirements.SPARSE_REQUIREMENT not in oldrequires: + repo.requirements.add(requirements.SPARSE_REQUIREMENT) + scmutil.writereporequirements(repo) - try: - writeconfig(repo, includes, excludes, profiles) - return refreshwdir(repo, oldstatus, oldmatch, force=force) - except Exception: - if repo.requirements != oldrequires: - repo.requirements.clear() - repo.requirements |= oldrequires - scmutil.writereporequirements(repo) - writeconfig(repo, oldincludes, oldexcludes, oldprofiles) - raise + try: + writeconfig(repo, includes, excludes, profiles) + return refreshwdir(repo, oldstatus, oldmatch, force=force) + except Exception: + if repo.requirements != oldrequires: + repo.requirements.clear() + repo.requirements |= oldrequires + scmutil.writereporequirements(repo) + writeconfig(repo, oldincludes, oldexcludes, oldprofiles) + raise def clearrules(repo, force=False): @@ -701,21 +707,18 @@ def importfromfiles(repo, opts, paths, f def updateconfig( repo, - pats, opts, - include=False, - exclude=False, + include=(), + exclude=(), reset=False, - delete=False, - enableprofile=False, - disableprofile=False, + delete=(), + enableprofile=(), + disableprofile=(), force=False, usereporootpaths=False, ): """Perform a sparse config update. - Only one of the actions may be performed. - The new config is written out and a working directory refresh is performed. """ with repo.wlock(), repo.lock(), repo.dirstate.parentchange(): @@ -733,10 +736,13 @@ def updateconfig( newexclude = set(oldexclude) newprofiles = set(oldprofiles) - if any(os.path.isabs(pat) for pat in pats): - raise error.Abort(_(b'paths cannot be absolute')) + def normalize_pats(pats): + if any(os.path.isabs(pat) for pat in pats): + raise error.Abort(_(b'paths cannot be absolute')) - if not usereporootpaths: + if usereporootpaths: + return pats + # let's treat paths as relative to cwd root, cwd = repo.root, repo.getcwd() abspats = [] @@ -749,19 +755,20 @@ def updateconfig( abspats.append(ap) else: abspats.append(kindpat) - pats = abspats + return abspats - if include: - newinclude.update(pats) - elif exclude: - newexclude.update(pats) - elif enableprofile: - newprofiles.update(pats) - elif disableprofile: - newprofiles.difference_update(pats) - elif delete: - newinclude.difference_update(pats) - newexclude.difference_update(pats) + include = normalize_pats(include) + exclude = normalize_pats(exclude) + delete = normalize_pats(delete) + disableprofile = normalize_pats(disableprofile) + enableprofile = normalize_pats(enableprofile) + + newinclude.difference_update(delete) + newexclude.difference_update(delete) + newprofiles.difference_update(disableprofile) + newinclude.update(include) + newprofiles.update(enableprofile) + newexclude.update(exclude) profilecount = len(newprofiles - oldprofiles) - len( oldprofiles - newprofiles diff --git a/mercurial/sshpeer.py b/mercurial/sshpeer.py --- a/mercurial/sshpeer.py +++ b/mercurial/sshpeer.py @@ -16,7 +16,6 @@ from . import ( error, pycompat, util, - wireprotoserver, wireprototypes, wireprotov1peer, wireprotov1server, @@ -288,10 +287,6 @@ def _performhandshake(ui, stdin, stdout, # Generate a random token to help identify responses to version 2 # upgrade request. token = pycompat.sysbytes(str(uuid.uuid4())) - upgradecaps = [ - (b'proto', wireprotoserver.SSHV2), - ] - upgradecaps = util.urlreq.urlencode(upgradecaps) try: pairsarg = b'%s-%s' % (b'0' * 40, b'0' * 40) @@ -302,11 +297,6 @@ def _performhandshake(ui, stdin, stdout, pairsarg, ] - # Request upgrade to version 2 if configured. - if ui.configbool(b'experimental', b'sshpeer.advertise-v2'): - ui.debug(b'sending upgrade request: %s %s\n' % (token, upgradecaps)) - handshake.insert(0, b'upgrade %s %s\n' % (token, upgradecaps)) - if requestlog: ui.debug(b'devel-peer-request: hello+between\n') ui.debug(b'devel-peer-request: pairs: %d bytes\n' % len(pairsarg)) @@ -365,24 +355,6 @@ def _performhandshake(ui, stdin, stdout, if l.startswith(b'capabilities:'): caps.update(l[:-1].split(b':')[1].split()) break - elif protoname == wireprotoserver.SSHV2: - # We see a line with number of bytes to follow and then a value - # looking like ``capabilities: *``. - line = stdout.readline() - try: - valuelen = int(line) - except ValueError: - badresponse() - - capsline = stdout.read(valuelen) - if not capsline.startswith(b'capabilities: '): - badresponse() - - ui.debug(b'remote: %s\n' % capsline) - - caps.update(capsline.split(b':')[1].split()) - # Trailing newline. - stdout.read(1) # Error if we couldn't find capabilities, this means: # @@ -601,14 +573,6 @@ class sshv1peer(wireprotov1peer.wirepeer self._readerr() -class sshv2peer(sshv1peer): - """A peer that speakers version 2 of the transport protocol.""" - - # Currently version 2 is identical to version 1 post handshake. - # And handshake is performed before the peer is instantiated. So - # we need no custom code. - - def makepeer(ui, path, proc, stdin, stdout, stderr, autoreadstderr=True): """Make a peer instance from existing pipes. @@ -640,17 +604,6 @@ def makepeer(ui, path, proc, stdin, stdo caps, autoreadstderr=autoreadstderr, ) - elif protoname == wireprototypes.SSHV2: - return sshv2peer( - ui, - path, - proc, - stdin, - stdout, - stderr, - caps, - autoreadstderr=autoreadstderr, - ) else: _cleanuppipes(ui, stdout, stdin, stderr, warn=None) raise error.RepoError( diff --git a/mercurial/sslutil.py b/mercurial/sslutil.py --- a/mercurial/sslutil.py +++ b/mercurial/sslutil.py @@ -139,12 +139,18 @@ def _hostsettings(ui, hostname): alg, fingerprint = fingerprint.split(b':', 1) fingerprint = fingerprint.replace(b':', b'').lower() + # pytype: disable=attribute-error + # `s` is heterogeneous, but this entry is always a list of tuples s[b'certfingerprints'].append((alg, fingerprint)) + # pytype: enable=attribute-error # Fingerprints from [hostfingerprints] are always SHA-1. for fingerprint in ui.configlist(b'hostfingerprints', bhostname): fingerprint = fingerprint.replace(b':', b'').lower() + # pytype: disable=attribute-error + # `s` is heterogeneous, but this entry is always a list of tuples s[b'certfingerprints'].append((b'sha1', fingerprint)) + # pytype: enable=attribute-error s[b'legacyfingerprint'] = True # If a host cert fingerprint is defined, it is the only thing that diff --git a/mercurial/statichttprepo.py b/mercurial/statichttprepo.py --- a/mercurial/statichttprepo.py +++ b/mercurial/statichttprepo.py @@ -22,6 +22,7 @@ from . import ( namespaces, pathutil, pycompat, + requirements as requirementsmod, url, util, vfs as vfsmod, @@ -197,6 +198,9 @@ class statichttprepository( # we do not care about empty old-style repositories here msg = _(b"'%s' does not appear to be an hg repository") % path raise error.RepoError(msg) + if requirementsmod.SHARESAFE_REQUIREMENT in requirements: + storevfs = vfsclass(self.vfs.join(b'store')) + requirements |= set(storevfs.read(b'requires').splitlines()) supportedrequirements = localrepo.gathersupportedrequirements(ui) localrepo.ensurerequirementsrecognized( diff --git a/mercurial/statprof.py b/mercurial/statprof.py --- a/mercurial/statprof.py +++ b/mercurial/statprof.py @@ -494,9 +494,9 @@ def display(fp=None, format=3, data=None data = state if fp is None: - import sys + from .utils import procutil - fp = sys.stdout + fp = procutil.stdout if len(data.samples) == 0: fp.write(b'No samples recorded.\n') return @@ -516,7 +516,7 @@ def display(fp=None, format=3, data=None elif format == DisplayFormats.Chrome: write_to_chrome(data, fp, **kwargs) else: - raise Exception(b"Invalid display format") + raise Exception("Invalid display format") if format not in (DisplayFormats.Json, DisplayFormats.Chrome): fp.write(b'---\n') @@ -625,7 +625,7 @@ def display_by_method(data, fp): def display_about_method(data, fp, function=None, **kwargs): if function is None: - raise Exception(b"Invalid function") + raise Exception("Invalid function") filename = None if b':' in function: @@ -1080,7 +1080,7 @@ def main(argv=None): printusage() return 0 else: - assert False, b"unhandled option %s" % o + assert False, "unhandled option %s" % o if not path: print('must specify --file to load') diff --git a/mercurial/streamclone.py b/mercurial/streamclone.py --- a/mercurial/streamclone.py +++ b/mercurial/streamclone.py @@ -27,11 +27,38 @@ from . import ( store, util, ) +from .revlogutils import ( + nodemap, +) from .utils import ( stringutil, ) +def new_stream_clone_requirements(default_requirements, streamed_requirements): + """determine the final set of requirement for a new stream clone + + this method combine the "default" requirements that a new repository would + use with the constaint we get from the stream clone content. We keep local + configuration choice when possible. + """ + requirements = set(default_requirements) + requirements -= requirementsmod.STREAM_FIXED_REQUIREMENTS + requirements.update(streamed_requirements) + return requirements + + +def streamed_requirements(repo): + """the set of requirement the new clone will have to support + + This is used for advertising the stream options and to generate the actual + stream content.""" + requiredformats = ( + repo.requirements & requirementsmod.STREAM_FIXED_REQUIREMENTS + ) + return requiredformats + + def canperformstreamclone(pullop, bundle2=False): """Whether it is possible to perform a streaming clone as part of pull. @@ -184,17 +211,15 @@ def maybeperformlegacystreamclone(pullop with repo.lock(): consumev1(repo, fp, filecount, bytecount) - - # new requirements = old non-format requirements + - # new format-related remote requirements - # requirements from the streamed-in repository - repo.requirements = requirements | ( - repo.requirements - repo.supportedformats + repo.requirements = new_stream_clone_requirements( + repo.requirements, + requirements, ) repo.svfs.options = localrepo.resolvestorevfsoptions( repo.ui, repo.requirements, repo.features ) scmutil.writereporequirements(repo) + nodemap.post_stream_cleanup(repo) if rbranchmap: repo._branchcaches.replace(repo, rbranchmap) @@ -333,7 +358,7 @@ def generatebundlev1(repo, compression=b if compression != b'UN': raise ValueError(b'we do not support the compression argument yet') - requirements = repo.requirements & repo.supportedformats + requirements = streamed_requirements(repo) requires = b','.join(sorted(requirements)) def gen(): @@ -489,6 +514,7 @@ def applybundlev1(repo, fp): ) consumev1(repo, fp, filecount, bytecount) + nodemap.post_stream_cleanup(repo) class streamcloneapplier(object): @@ -797,16 +823,15 @@ def applybundlev2(repo, fp, filecount, f consumev2(repo, fp, filecount, filesize) - # new requirements = old non-format requirements + - # new format-related remote requirements - # requirements from the streamed-in repository - repo.requirements = set(requirements) | ( - repo.requirements - repo.supportedformats + repo.requirements = new_stream_clone_requirements( + repo.requirements, + requirements, ) repo.svfs.options = localrepo.resolvestorevfsoptions( repo.ui, repo.requirements, repo.features ) scmutil.writereporequirements(repo) + nodemap.post_stream_cleanup(repo) def _copy_files(src_vfs_map, dst_vfs_map, entries, progress): diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py --- a/mercurial/templatekw.py +++ b/mercurial/templatekw.py @@ -304,6 +304,21 @@ def showextras(context, mapping): ) +@templatekeyword(b'_fast_rank', requires={b'ctx'}) +def fast_rank(context, mapping): + """the rank of a changeset if cached + + The rank of a revision is the size of the sub-graph it defines as a head. + Equivalently, the rank of a revision `r` is the size of the set + `ancestors(r)`, `r` included. + """ + ctx = context.resource(mapping, b'ctx') + rank = ctx.fast_rank() + if rank is None: + return None + return b"%d" % rank + + def _getfilestatus(context, mapping, listall=False): ctx = context.resource(mapping, b'ctx') revcache = context.resource(mapping, b'revcache') diff --git a/mercurial/transaction.py b/mercurial/transaction.py --- a/mercurial/transaction.py +++ b/mercurial/transaction.py @@ -25,11 +25,6 @@ from .utils import stringutil version = 2 -# These are the file generators that should only be executed after the -# finalizers are done, since they rely on the output of the finalizers (like -# the changelog having been written). -postfinalizegenerators = {b'bookmarks', b'dirstate'} - GEN_GROUP_ALL = b'all' GEN_GROUP_PRE_FINALIZE = b'prefinalize' GEN_GROUP_POST_FINALIZE = b'postfinalize' @@ -334,7 +329,13 @@ class transaction(util.transactional): @active def addfilegenerator( - self, genid, filenames, genfunc, order=0, location=b'' + self, + genid, + filenames, + genfunc, + order=0, + location=b'', + post_finalize=False, ): """add a function to generates some files at transaction commit @@ -357,10 +358,14 @@ class transaction(util.transactional): The `location` arguments may be used to indicate the files are located outside of the the standard directory for transaction. It should match one of the key of the `transaction.vfsmap` dictionary. + + The `post_finalize` argument can be set to `True` for file generation + that must be run after the transaction has been finalized. """ # For now, we are unable to do proper backup and restore of custom vfs # but for bookmarks that are handled outside this mechanism. - self._filegenerators[genid] = (order, filenames, genfunc, location) + entry = (order, filenames, genfunc, location, post_finalize) + self._filegenerators[genid] = entry @active def removefilegenerator(self, genid): @@ -380,13 +385,12 @@ class transaction(util.transactional): for id, entry in sorted(pycompat.iteritems(self._filegenerators)): any = True - order, filenames, genfunc, location = entry + order, filenames, genfunc, location, post_finalize = entry # for generation at closing, check if it's before or after finalize - is_post = id in postfinalizegenerators - if skip_post and is_post: + if skip_post and post_finalize: continue - elif skip_pre and not is_post: + elif skip_pre and not post_finalize: continue vfs = self._vfsmap[location] diff --git a/mercurial/unionrepo.py b/mercurial/unionrepo.py --- a/mercurial/unionrepo.py +++ b/mercurial/unionrepo.py @@ -71,6 +71,7 @@ class unionrevlog(revlog.revlog): _sds, _dcm, _sdcm, + rank, ) = rev flags = _start & 0xFFFF @@ -107,6 +108,7 @@ class unionrevlog(revlog.revlog): 0, # sidedata size revlog_constants.COMP_MODE_INLINE, revlog_constants.COMP_MODE_INLINE, + rank, ) self.index.append(e) self.bundlerevs.add(n) diff --git a/mercurial/upgrade.py b/mercurial/upgrade.py --- a/mercurial/upgrade.py +++ b/mercurial/upgrade.py @@ -42,27 +42,16 @@ def upgraderepo( ): """Upgrade a repository in place.""" if optimize is None: - optimize = {} + optimize = set() repo = repo.unfiltered() - revlogs = set(upgrade_engine.UPGRADE_ALL_REVLOGS) - specentries = ( - (upgrade_engine.UPGRADE_CHANGELOG, changelog), - (upgrade_engine.UPGRADE_MANIFEST, manifest), - (upgrade_engine.UPGRADE_FILELOGS, filelogs), - ) - specified = [(y, x) for (y, x) in specentries if x is not None] - if specified: - # we have some limitation on revlogs to be recloned - if any(x for y, x in specified): - revlogs = set() - for upgrade, enabled in specified: - if enabled: - revlogs.add(upgrade) - else: - # none are enabled - for upgrade, __ in specified: - revlogs.discard(upgrade) + specified_revlogs = {} + if changelog is not None: + specified_revlogs[upgrade_engine.UPGRADE_CHANGELOG] = changelog + if manifest is not None: + specified_revlogs[upgrade_engine.UPGRADE_MANIFEST] = manifest + if filelogs is not None: + specified_revlogs[upgrade_engine.UPGRADE_FILELOGS] = filelogs # Ensure the repository can be upgraded. upgrade_actions.check_source_requirements(repo) @@ -96,20 +85,92 @@ def upgraderepo( ) removed_actions = upgrade_actions.find_format_downgrades(repo) - removedreqs = repo.requirements - newreqs - addedreqs = newreqs - repo.requirements + # check if we need to touch revlog and if so, which ones + + touched_revlogs = set() + overwrite_msg = _(b'warning: ignoring %14s, as upgrade is changing: %s\n') + select_msg = _(b'note: selecting %s for processing to change: %s\n') + msg_issued = 0 + + FL = upgrade_engine.UPGRADE_FILELOGS + MN = upgrade_engine.UPGRADE_MANIFEST + CL = upgrade_engine.UPGRADE_CHANGELOG + + if optimizations: + if any(specified_revlogs.values()): + # we have some limitation on revlogs to be recloned + for rl, enabled in specified_revlogs.items(): + if enabled: + touched_revlogs.add(rl) + else: + touched_revlogs = set(upgrade_engine.UPGRADE_ALL_REVLOGS) + for rl, enabled in specified_revlogs.items(): + if not enabled: + touched_revlogs.discard(rl) + + if repo.shared(): + unsafe_actions = set() + unsafe_actions.update(up_actions) + unsafe_actions.update(removed_actions) + unsafe_actions.update(optimizations) + unsafe_actions = [ + a for a in unsafe_actions if not a.compatible_with_share + ] + unsafe_actions.sort(key=lambda a: a.name) + if unsafe_actions: + m = _(b'cannot use these actions on a share repository: %s') + h = _(b'upgrade the main repository directly') + actions = b', '.join(a.name for a in unsafe_actions) + m %= actions + raise error.Abort(m, hint=h) - if revlogs != upgrade_engine.UPGRADE_ALL_REVLOGS: - incompatible = upgrade_actions.RECLONES_REQUIREMENTS & ( - removedreqs | addedreqs - ) - if incompatible: - msg = _( - b'ignoring revlogs selection flags, format requirements ' - b'change: %s\n' - ) - ui.warn(msg % b', '.join(sorted(incompatible))) - revlogs = upgrade_engine.UPGRADE_ALL_REVLOGS + for action in sorted(up_actions + removed_actions, key=lambda a: a.name): + # optimisation does not "requires anything, they just needs it. + if action.type != upgrade_actions.FORMAT_VARIANT: + continue + + if action.touches_filelogs and FL not in touched_revlogs: + if FL in specified_revlogs: + if not specified_revlogs[FL]: + msg = overwrite_msg % (b'--no-filelogs', action.name) + ui.warn(msg) + msg_issued = 2 + else: + msg = select_msg % (b'all-filelogs', action.name) + ui.status(msg) + if not ui.quiet: + msg_issued = 1 + touched_revlogs.add(FL) + + if action.touches_manifests and MN not in touched_revlogs: + if MN in specified_revlogs: + if not specified_revlogs[MN]: + msg = overwrite_msg % (b'--no-manifest', action.name) + ui.warn(msg) + msg_issued = 2 + else: + msg = select_msg % (b'all-manifestlogs', action.name) + ui.status(msg) + if not ui.quiet: + msg_issued = 1 + touched_revlogs.add(MN) + + if action.touches_changelog and CL not in touched_revlogs: + if CL in specified_revlogs: + if not specified_revlogs[CL]: + msg = overwrite_msg % (b'--no-changelog', action.name) + ui.warn(msg) + msg_issued = True + else: + msg = select_msg % (b'changelog', action.name) + ui.status(msg) + if not ui.quiet: + msg_issued = 1 + touched_revlogs.add(CL) + if msg_issued >= 2: + ui.warn((b"\n")) + elif msg_issued >= 1: + ui.status((b"\n")) upgrade_op = upgrade_actions.UpgradeOperation( ui, @@ -117,7 +178,7 @@ def upgraderepo( repo.requirements, up_actions, removed_actions, - revlogs, + touched_revlogs, backup, ) diff --git a/mercurial/upgrade_utils/actions.py b/mercurial/upgrade_utils/actions.py --- a/mercurial/upgrade_utils/actions.py +++ b/mercurial/upgrade_utils/actions.py @@ -36,7 +36,10 @@ RECLONES_REQUIREMENTS = { def preservedrequirements(repo): - return set() + preserved = { + requirements.SHARED_REQUIREMENT, + } + return preserved & repo.requirements FORMAT_VARIANT = b'deficiency' @@ -97,6 +100,9 @@ class improvement(object): # Whether this improvement touches the dirstate touches_dirstate = False + # Can this action be run on a share instead of its mains repository + compatible_with_share = False + allformatvariant = [] # type: List[Type['formatvariant']] @@ -190,6 +196,30 @@ class dirstatev2(requirementformatvarian touches_changelog = False touches_requirements = True touches_dirstate = True + compatible_with_share = True + + +@registerformatvariant +class dirstatetrackedkey(requirementformatvariant): + name = b'tracked-hint' + _requirement = requirements.DIRSTATE_TRACKED_HINT_V1 + + default = False + + description = _( + b'Add a small file to help external tooling that watch the tracked set' + ) + + upgrademessage = _( + b'external tools will be informated of potential change in the tracked set' + ) + + touches_filelogs = False + touches_manifests = False + touches_changelog = False + touches_requirements = True + touches_dirstate = True + compatible_with_share = True @registerformatvariant @@ -243,7 +273,7 @@ class sharesafe(requirementformatvariant name = b'share-safe' _requirement = requirements.SHARESAFE_REQUIREMENT - default = False + default = True description = _( b'old shared repositories do not share source repository ' @@ -899,8 +929,6 @@ def blocksourcerequirements(repo): # This was a precursor to generaldelta and was never enabled by default. # It should (hopefully) not exist in the wild. b'parentdelta', - # Upgrade should operate on the actual store, not the shared link. - requirements.SHARED_REQUIREMENT, } @@ -932,6 +960,16 @@ def check_source_requirements(repo): m = _(b'cannot upgrade repository; unsupported source requirement: %s') blockingreqs = b', '.join(sorted(blockingreqs)) raise error.Abort(m % blockingreqs) + # Upgrade should operate on the actual store, not the shared link. + + bad_share = ( + requirements.SHARED_REQUIREMENT in repo.requirements + and requirements.SHARESAFE_REQUIREMENT not in repo.requirements + ) + if bad_share: + m = _(b'cannot upgrade repository; share repository without share-safe') + h = _(b'check :hg:`help config.format.use-share-safe`') + raise error.Abort(m, hint=h) ### Verify the validity of the planned requirement changes #################### @@ -952,6 +990,7 @@ def supportremovedrequirements(repo): requirements.REVLOGV2_REQUIREMENT, requirements.CHANGELOGV2_REQUIREMENT, requirements.REVLOGV1_REQUIREMENT, + requirements.DIRSTATE_TRACKED_HINT_V1, requirements.DIRSTATE_V2_REQUIREMENT, } for name in compression.compengines: @@ -972,18 +1011,20 @@ def supporteddestrequirements(repo): Extensions should monkeypatch this to add their custom requirements. """ supported = { + requirements.CHANGELOGV2_REQUIREMENT, + requirements.COPIESSDC_REQUIREMENT, + requirements.DIRSTATE_TRACKED_HINT_V1, + requirements.DIRSTATE_V2_REQUIREMENT, requirements.DOTENCODE_REQUIREMENT, requirements.FNCACHE_REQUIREMENT, requirements.GENERALDELTA_REQUIREMENT, + requirements.NODEMAP_REQUIREMENT, requirements.REVLOGV1_REQUIREMENT, # allowed in case of downgrade - requirements.STORE_REQUIREMENT, + requirements.REVLOGV2_REQUIREMENT, + requirements.SHARED_REQUIREMENT, + requirements.SHARESAFE_REQUIREMENT, requirements.SPARSEREVLOG_REQUIREMENT, - requirements.COPIESSDC_REQUIREMENT, - requirements.NODEMAP_REQUIREMENT, - requirements.SHARESAFE_REQUIREMENT, - requirements.REVLOGV2_REQUIREMENT, - requirements.CHANGELOGV2_REQUIREMENT, - requirements.DIRSTATE_V2_REQUIREMENT, + requirements.STORE_REQUIREMENT, } for name in compression.compengines: engine = compression.compengines[name] @@ -1015,6 +1056,7 @@ def allowednewrequirements(repo): requirements.REVLOGV1_REQUIREMENT, requirements.REVLOGV2_REQUIREMENT, requirements.CHANGELOGV2_REQUIREMENT, + requirements.DIRSTATE_TRACKED_HINT_V1, requirements.DIRSTATE_V2_REQUIREMENT, } for name in compression.compengines: diff --git a/mercurial/upgrade_utils/engine.py b/mercurial/upgrade_utils/engine.py --- a/mercurial/upgrade_utils/engine.py +++ b/mercurial/upgrade_utils/engine.py @@ -486,6 +486,15 @@ def upgrade(ui, srcrepo, dstrepo, upgrad upgrade_dirstate(ui, srcrepo, upgrade_op, b'v2', b'v1') upgrade_op.removed_actions.remove(upgrade_actions.dirstatev2) + if upgrade_actions.dirstatetrackedkey in upgrade_op.upgrade_actions: + ui.status(_(b'create dirstate-tracked-hint file\n')) + upgrade_tracked_hint(ui, srcrepo, upgrade_op, add=True) + upgrade_op.upgrade_actions.remove(upgrade_actions.dirstatetrackedkey) + elif upgrade_actions.dirstatetrackedkey in upgrade_op.removed_actions: + ui.status(_(b'remove dirstate-tracked-hint file\n')) + upgrade_tracked_hint(ui, srcrepo, upgrade_op, add=False) + upgrade_op.removed_actions.remove(upgrade_actions.dirstatetrackedkey) + if not (upgrade_op.upgrade_actions or upgrade_op.removed_actions): return @@ -660,3 +669,15 @@ def upgrade_dirstate(ui, srcrepo, upgrad srcrepo.dirstate.write(None) scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements) + + +def upgrade_tracked_hint(ui, srcrepo, upgrade_op, add): + if add: + srcrepo.dirstate._use_tracked_hint = True + srcrepo.dirstate._dirty = True + srcrepo.dirstate._dirty_tracked_set = True + srcrepo.dirstate.write(None) + if not add: + srcrepo.dirstate.delete_tracked_hint() + + scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements) diff --git a/mercurial/util.py b/mercurial/util.py --- a/mercurial/util.py +++ b/mercurial/util.py @@ -57,7 +57,6 @@ from .utils import ( hashutil, procutil, stringutil, - urlutil, ) if pycompat.TYPE_CHECKING: @@ -2991,54 +2990,6 @@ def interpolate(prefix, mapping, s, fn=N return r.sub(lambda x: fn(mapping[x.group()[1:]]), s) -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( (1, 1e3, _(b'%.0f s')), (100, 1, _(b'%.1f s')), diff --git a/mercurial/utils/procutil.py b/mercurial/utils/procutil.py --- a/mercurial/utils/procutil.py +++ b/mercurial/utils/procutil.py @@ -75,7 +75,9 @@ class LineBufferedWrapper(object): return res +# pytype: disable=attribute-error io.BufferedIOBase.register(LineBufferedWrapper) +# pytype: enable=attribute-error def make_line_buffered(stream): @@ -114,7 +116,9 @@ class WriteAllWrapper(object): return total_written +# pytype: disable=attribute-error io.IOBase.register(WriteAllWrapper) +# pytype: enable=attribute-error def _make_write_all(stream): @@ -738,6 +742,8 @@ else: start_new_session = False ensurestart = True + stdin = None + try: if stdin_bytes is None: stdin = subprocess.DEVNULL @@ -766,7 +772,8 @@ else: record_wait(255) raise finally: - if stdin_bytes is not None: + if stdin_bytes is not None and stdin is not None: + assert not isinstance(stdin, int) stdin.close() if not ensurestart: # Even though we're not waiting on the child process, @@ -847,6 +854,8 @@ else: return returncode = 255 + stdin = None + try: if record_wait is None: # Start a new session @@ -889,7 +898,8 @@ else: finally: # mission accomplished, this child needs to exit and not # continue the hg process here. - stdin.close() + if stdin is not None: + stdin.close() if record_wait is None: os._exit(returncode) diff --git a/mercurial/utils/storageutil.py b/mercurial/utils/storageutil.py --- a/mercurial/utils/storageutil.py +++ b/mercurial/utils/storageutil.py @@ -112,6 +112,13 @@ def filerevisioncopied(store, node): 2-tuple of the source filename and node. """ if store.parents(node)[0] != sha1nodeconstants.nullid: + # When creating a copy or move we set filelog parents to null, + # because contents are probably unrelated and making a delta + # would not be useful. + # Conversely, if filelog p1 is non-null we know + # there is no copy metadata. + # In the presence of merges, this reasoning becomes invalid + # if we reorder parents. See tests/test-issue6528.t. return False meta = parsemeta(store.revision(node))[0] diff --git a/mercurial/utils/stringutil.py b/mercurial/utils/stringutil.py --- a/mercurial/utils/stringutil.py +++ b/mercurial/utils/stringutil.py @@ -264,7 +264,11 @@ def prettyrepr(o): q1 = rs.find(b'<', p1 + 1) if q1 < 0: q1 = len(rs) + # pytype: disable=wrong-arg-count + # TODO: figure out why pytype doesn't recognize the optional start + # arg elif q1 > p1 + 1 and rs.startswith(b'=', q1 - 1): + # pytype: enable=wrong-arg-count # backtrack for ' field=<' q0 = rs.rfind(b' ', p1 + 1, q1 - 1) if q0 < 0: @@ -692,11 +696,11 @@ def escapestr(s): s = bytes(s) # call underlying function of s.encode('string_escape') directly for # Python 3 compatibility - return codecs.escape_encode(s)[0] + return codecs.escape_encode(s)[0] # pytype: disable=module-attr def unescapestr(s): - return codecs.escape_decode(s)[0] + return codecs.escape_decode(s)[0] # pytype: disable=module-attr def forcebytestr(obj): diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -18,11 +18,9 @@ from . import ( util, wireprototypes, wireprotov1server, - wireprotov2server, ) from .interfaces import util as interfaceutil from .utils import ( - cborutil, compression, stringutil, ) @@ -39,7 +37,6 @@ HGTYPE2 = b'application/mercurial-0.2' HGERRTYPE = b'application/hg-error' SSHV1 = wireprototypes.SSHV1 -SSHV2 = wireprototypes.SSHV2 def decodevaluefromheaders(req, headerprefix): @@ -244,97 +241,6 @@ def handlewsgirequest(rctx, req, res, ch return True -def _availableapis(repo): - apis = set() - - # Registered APIs are made available via config options of the name of - # the protocol. - for k, v in API_HANDLERS.items(): - section, option = v[b'config'] - if repo.ui.configbool(section, option): - apis.add(k) - - return apis - - -def handlewsgiapirequest(rctx, req, res, checkperm): - """Handle requests to /api/*.""" - assert req.dispatchparts[0] == b'api' - - repo = rctx.repo - - # This whole URL space is experimental for now. But we want to - # reserve the URL space. So, 404 all URLs if the feature isn't enabled. - if not repo.ui.configbool(b'experimental', b'web.apiserver'): - res.status = b'404 Not Found' - res.headers[b'Content-Type'] = b'text/plain' - res.setbodybytes(_(b'Experimental API server endpoint not enabled')) - return - - # The URL space is /api//*. The structure of URLs under varies - # by . - - availableapis = _availableapis(repo) - - # Requests to /api/ list available APIs. - if req.dispatchparts == [b'api']: - res.status = b'200 OK' - res.headers[b'Content-Type'] = b'text/plain' - lines = [ - _( - b'APIs can be accessed at /api/, where can be ' - b'one of the following:\n' - ) - ] - if availableapis: - lines.extend(sorted(availableapis)) - else: - lines.append(_(b'(no available APIs)\n')) - res.setbodybytes(b'\n'.join(lines)) - return - - proto = req.dispatchparts[1] - - if proto not in API_HANDLERS: - res.status = b'404 Not Found' - res.headers[b'Content-Type'] = b'text/plain' - res.setbodybytes( - _(b'Unknown API: %s\nKnown APIs: %s') - % (proto, b', '.join(sorted(availableapis))) - ) - return - - if proto not in availableapis: - res.status = b'404 Not Found' - res.headers[b'Content-Type'] = b'text/plain' - res.setbodybytes(_(b'API %s not enabled\n') % proto) - return - - API_HANDLERS[proto][b'handler']( - rctx, req, res, checkperm, req.dispatchparts[2:] - ) - - -# Maps API name to metadata so custom API can be registered. -# Keys are: -# -# config -# Config option that controls whether service is enabled. -# handler -# Callable receiving (rctx, req, res, checkperm, urlparts) that is called -# when a request to this API is received. -# apidescriptor -# Callable receiving (req, repo) that is called to obtain an API -# descriptor for this service. The response must be serializable to CBOR. -API_HANDLERS = { - wireprotov2server.HTTP_WIREPROTO_V2: { - b'config': (b'experimental', b'web.api.http-v2'), - b'handler': wireprotov2server.handlehttpv2request, - b'apidescriptor': wireprotov2server.httpv2apidescriptor, - }, -} - - def _httpresponsetype(ui, proto, prefer_uncompressed): """Determine the appropriate response type and compression settings. @@ -371,55 +277,6 @@ def _httpresponsetype(ui, proto, prefer_ return HGTYPE, util.compengines[b'zlib'], opts -def processcapabilitieshandshake(repo, req, res, proto): - """Called during a ?cmd=capabilities request. - - If the client is advertising support for a newer protocol, we send - a CBOR response with information about available services. If no - advertised services are available, we don't handle the request. - """ - # Fall back to old behavior unless the API server is enabled. - if not repo.ui.configbool(b'experimental', b'web.apiserver'): - return False - - clientapis = decodevaluefromheaders(req, b'X-HgUpgrade') - protocaps = decodevaluefromheaders(req, b'X-HgProto') - if not clientapis or not protocaps: - return False - - # We currently only support CBOR responses. - protocaps = set(protocaps.split(b' ')) - if b'cbor' not in protocaps: - return False - - descriptors = {} - - for api in sorted(set(clientapis.split()) & _availableapis(repo)): - handler = API_HANDLERS[api] - - descriptorfn = handler.get(b'apidescriptor') - if not descriptorfn: - continue - - descriptors[api] = descriptorfn(req, repo) - - v1caps = wireprotov1server.dispatch(repo, proto, b'capabilities') - assert isinstance(v1caps, wireprototypes.bytesresponse) - - m = { - # TODO allow this to be configurable. - b'apibase': b'api/', - b'apis': descriptors, - b'v1capabilities': v1caps.data, - } - - res.status = b'200 OK' - res.headers[b'Content-Type'] = b'application/mercurial-cbor' - res.setbodybytes(b''.join(cborutil.streamencode(m))) - - return True - - def _callhttp(repo, req, res, proto, cmd): # Avoid cycle involving hg module. from .hgweb import common as hgwebcommon @@ -461,13 +318,6 @@ def _callhttp(repo, req, res, proto, cmd proto.checkperm(wireprotov1server.commands[cmd].permission) - # Possibly handle a modern client wanting to switch protocols. - if cmd == b'capabilities' and processcapabilitieshandshake( - repo, req, res, proto - ): - - return - rsp = wireprotov1server.dispatch(repo, proto, cmd) if isinstance(rsp, bytes): @@ -596,17 +446,6 @@ class sshv1protocolhandler(object): pass -class sshv2protocolhandler(sshv1protocolhandler): - """Protocol handler for version 2 of the SSH protocol.""" - - @property - def name(self): - return wireprototypes.SSHV2 - - def addcapabilities(self, repo, caps): - return caps - - def _runsshserver(ui, repo, fin, fout, ev): # This function operates like a state machine of sorts. The following # states are defined: @@ -616,19 +455,6 @@ def _runsshserver(ui, repo, fin, fout, e # new lines. These commands are processed in this state, one command # after the other. # - # protov2-serving - # Server is in protocol version 2 serving mode. - # - # upgrade-initial - # The server is going to process an upgrade request. - # - # upgrade-v2-filter-legacy-handshake - # The protocol is being upgraded to version 2. The server is expecting - # the legacy handshake from version 1. - # - # upgrade-v2-finish - # The upgrade to version 2 of the protocol is imminent. - # # shutdown # The server is shutting down, possibly in reaction to a client event. # @@ -637,32 +463,9 @@ def _runsshserver(ui, repo, fin, fout, e # protov1-serving -> shutdown # When server receives an empty request or encounters another # error. - # - # protov1-serving -> upgrade-initial - # An upgrade request line was seen. - # - # upgrade-initial -> upgrade-v2-filter-legacy-handshake - # Upgrade to version 2 in progress. Server is expecting to - # process a legacy handshake. - # - # upgrade-v2-filter-legacy-handshake -> shutdown - # Client did not fulfill upgrade handshake requirements. - # - # upgrade-v2-filter-legacy-handshake -> upgrade-v2-finish - # Client fulfilled version 2 upgrade requirements. Finishing that - # upgrade. - # - # upgrade-v2-finish -> protov2-serving - # Protocol upgrade to version 2 complete. Server can now speak protocol - # version 2. - # - # protov2-serving -> protov1-serving - # Ths happens by default since protocol version 2 is the same as - # version 1 except for the handshake. state = b'protov1-serving' proto = sshv1protocolhandler(ui, fin, fout) - protoswitched = False while not ev.is_set(): if state == b'protov1-serving': @@ -674,21 +477,6 @@ def _runsshserver(ui, repo, fin, fout, e state = b'shutdown' continue - # It looks like a protocol upgrade request. Transition state to - # handle it. - if request.startswith(b'upgrade '): - if protoswitched: - _sshv1respondooberror( - fout, - ui.ferr, - b'cannot upgrade protocols multiple times', - ) - state = b'shutdown' - continue - - state = b'upgrade-initial' - continue - available = wireprotov1server.commands.commandavailable( request, proto ) @@ -724,108 +512,6 @@ def _runsshserver(ui, repo, fin, fout, e b'wire protocol command: %s' % rsp ) - # For now, protocol version 2 serving just goes back to version 1. - elif state == b'protov2-serving': - state = b'protov1-serving' - continue - - elif state == b'upgrade-initial': - # We should never transition into this state if we've switched - # protocols. - assert not protoswitched - assert proto.name == wireprototypes.SSHV1 - - # Expected: upgrade - # If we get something else, the request is malformed. It could be - # from a future client that has altered the upgrade line content. - # We treat this as an unknown command. - try: - token, caps = request.split(b' ')[1:] - except ValueError: - _sshv1respondbytes(fout, b'') - state = b'protov1-serving' - continue - - # Send empty response if we don't support upgrading protocols. - if not ui.configbool(b'experimental', b'sshserver.support-v2'): - _sshv1respondbytes(fout, b'') - state = b'protov1-serving' - continue - - try: - caps = urlreq.parseqs(caps) - except ValueError: - _sshv1respondbytes(fout, b'') - state = b'protov1-serving' - continue - - # We don't see an upgrade request to protocol version 2. Ignore - # the upgrade request. - wantedprotos = caps.get(b'proto', [b''])[0] - if SSHV2 not in wantedprotos: - _sshv1respondbytes(fout, b'') - state = b'protov1-serving' - continue - - # It looks like we can honor this upgrade request to protocol 2. - # Filter the rest of the handshake protocol request lines. - state = b'upgrade-v2-filter-legacy-handshake' - continue - - elif state == b'upgrade-v2-filter-legacy-handshake': - # Client should have sent legacy handshake after an ``upgrade`` - # request. Expected lines: - # - # hello - # between - # pairs 81 - # 0000...-0000... - - ok = True - for line in (b'hello', b'between', b'pairs 81'): - request = fin.readline()[:-1] - - if request != line: - _sshv1respondooberror( - fout, - ui.ferr, - b'malformed handshake protocol: missing %s' % line, - ) - ok = False - state = b'shutdown' - break - - if not ok: - continue - - request = fin.read(81) - if request != b'%s-%s' % (b'0' * 40, b'0' * 40): - _sshv1respondooberror( - fout, - ui.ferr, - b'malformed handshake protocol: ' - b'missing between argument value', - ) - state = b'shutdown' - continue - - state = b'upgrade-v2-finish' - continue - - elif state == b'upgrade-v2-finish': - # Send the upgrade response. - fout.write(b'upgraded %s %s\n' % (token, SSHV2)) - servercaps = wireprotov1server.capabilities(repo, proto) - rsp = b'capabilities: %s' % servercaps.data - fout.write(b'%d\n%s\n' % (len(rsp), rsp)) - fout.flush() - - proto = sshv2protocolhandler(ui, fin, fout) - protoswitched = True - - state = b'protov2-serving' - continue - elif state == b'shutdown': break diff --git a/mercurial/wireprototypes.py b/mercurial/wireprototypes.py --- a/mercurial/wireprototypes.py +++ b/mercurial/wireprototypes.py @@ -21,10 +21,6 @@ from .utils import compression # Names of the SSH protocol implementations. SSHV1 = b'ssh-v1' -# These are advertised over the wire. Increment the counters at the end -# to reflect BC breakages. -SSHV2 = b'exp-ssh-v2-0003' -HTTP_WIREPROTO_V2 = b'exp-http-v2-0003' NARROWCAP = b'exp-narrow-1' ELLIPSESCAP1 = b'exp-ellipses-1' @@ -37,19 +33,10 @@ TRANSPORTS = { b'transport': b'ssh', b'version': 1, }, - SSHV2: { - b'transport': b'ssh', - # TODO mark as version 2 once all commands are implemented. - b'version': 1, - }, b'http-v1': { b'transport': b'http', b'version': 1, }, - HTTP_WIREPROTO_V2: { - b'transport': b'http', - b'version': 2, - }, } diff --git a/mercurial/wireprotov1server.py b/mercurial/wireprotov1server.py --- a/mercurial/wireprotov1server.py +++ b/mercurial/wireprotov1server.py @@ -147,12 +147,6 @@ def wireprotocommand(name, args=None, pe k for k, v in wireprototypes.TRANSPORTS.items() if v[b'version'] == 1 } - # Because SSHv2 is a mirror of SSHv1, we allow "batch" commands through to - # SSHv2. - # TODO undo this hack when SSH is using the unified frame protocol. - if name == b'batch': - transports.add(wireprototypes.SSHV2) - if permission not in (b'push', b'pull'): raise error.ProgrammingError( b'invalid wire protocol permission; ' @@ -306,7 +300,7 @@ def _capabilities(repo, proto): if streamclone.allowservergeneration(repo): if repo.ui.configbool(b'server', b'preferuncompressed'): caps.append(b'stream-preferred') - requiredformats = repo.requirements & repo.supportedformats + requiredformats = streamclone.streamed_requirements(repo) # if our local revlogs are just revlogv1, add 'stream' cap if not requiredformats - {requirementsmod.REVLOGV1_REQUIREMENT}: caps.append(b'stream') diff --git a/mercurial/wireprotov2peer.py b/mercurial/wireprotov2peer.py deleted file mode 100644 --- a/mercurial/wireprotov2peer.py +++ /dev/null @@ -1,576 +0,0 @@ -# wireprotov2peer.py - client side code for wire protocol version 2 -# -# Copyright 2018 Gregory Szorc -# -# 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 threading - -from .i18n import _ -from . import ( - encoding, - error, - pycompat, - sslutil, - url as urlmod, - util, - wireprotoframing, - wireprototypes, -) -from .utils import cborutil - - -def formatrichmessage(atoms): - """Format an encoded message from the framing protocol.""" - - chunks = [] - - for atom in atoms: - msg = _(atom[b'msg']) - - if b'args' in atom: - msg = msg % tuple(atom[b'args']) - - chunks.append(msg) - - return b''.join(chunks) - - -SUPPORTED_REDIRECT_PROTOCOLS = { - b'http', - b'https', -} - -SUPPORTED_CONTENT_HASHES = { - b'sha1', - b'sha256', -} - - -def redirecttargetsupported(ui, target): - """Determine whether a redirect target entry is supported. - - ``target`` should come from the capabilities data structure emitted by - the server. - """ - if target.get(b'protocol') not in SUPPORTED_REDIRECT_PROTOCOLS: - ui.note( - _(b'(remote redirect target %s uses unsupported protocol: %s)\n') - % (target[b'name'], target.get(b'protocol', b'')) - ) - return False - - if target.get(b'snirequired') and not sslutil.hassni: - ui.note( - _(b'(redirect target %s requires SNI, which is unsupported)\n') - % target[b'name'] - ) - return False - - if b'tlsversions' in target: - tlsversions = set(target[b'tlsversions']) - supported = set() - - for v in sslutil.supportedprotocols: - assert v.startswith(b'tls') - supported.add(v[3:]) - - if not tlsversions & supported: - ui.note( - _( - b'(remote redirect target %s requires unsupported TLS ' - b'versions: %s)\n' - ) - % (target[b'name'], b', '.join(sorted(tlsversions))) - ) - return False - - ui.note(_(b'(remote redirect target %s is compatible)\n') % target[b'name']) - - return True - - -def supportedredirects(ui, apidescriptor): - """Resolve the "redirect" command request key given an API descriptor. - - Given an API descriptor returned by the server, returns a data structure - that can be used in hte "redirect" field of command requests to advertise - support for compatible redirect targets. - - Returns None if no redirect targets are remotely advertised or if none are - supported. - """ - if not apidescriptor or b'redirect' not in apidescriptor: - return None - - targets = [ - t[b'name'] - for t in apidescriptor[b'redirect'][b'targets'] - if redirecttargetsupported(ui, t) - ] - - hashes = [ - h - for h in apidescriptor[b'redirect'][b'hashes'] - if h in SUPPORTED_CONTENT_HASHES - ] - - return { - b'targets': targets, - b'hashes': hashes, - } - - -class commandresponse(object): - """Represents the response to a command request. - - Instances track the state of the command and hold its results. - - An external entity is required to update the state of the object when - events occur. - """ - - def __init__(self, requestid, command, fromredirect=False): - self.requestid = requestid - self.command = command - self.fromredirect = fromredirect - - # Whether all remote input related to this command has been - # received. - self._inputcomplete = False - - # We have a lock that is acquired when important object state is - # mutated. This is to prevent race conditions between 1 thread - # sending us new data and another consuming it. - self._lock = threading.RLock() - - # An event is set when state of the object changes. This event - # is waited on by the generator emitting objects. - self._serviceable = threading.Event() - - self._pendingevents = [] - self._pendingerror = None - self._decoder = cborutil.bufferingdecoder() - self._seeninitial = False - self._redirect = None - - def _oninputcomplete(self): - with self._lock: - self._inputcomplete = True - self._serviceable.set() - - def _onresponsedata(self, data): - available, readcount, wanted = self._decoder.decode(data) - - if not available: - return - - with self._lock: - for o in self._decoder.getavailable(): - if not self._seeninitial and not self.fromredirect: - self._handleinitial(o) - continue - - # We should never see an object after a content redirect, - # as the spec says the main status object containing the - # content redirect is the only object in the stream. Fail - # if we see a misbehaving server. - if self._redirect: - raise error.Abort( - _( - b'received unexpected response data ' - b'after content redirect; the remote is ' - b'buggy' - ) - ) - - self._pendingevents.append(o) - - self._serviceable.set() - - def _onerror(self, e): - self._pendingerror = e - - with self._lock: - self._serviceable.set() - - def _handleinitial(self, o): - self._seeninitial = True - if o[b'status'] == b'ok': - return - - elif o[b'status'] == b'redirect': - l = o[b'location'] - self._redirect = wireprototypes.alternatelocationresponse( - url=l[b'url'], - mediatype=l[b'mediatype'], - size=l.get(b'size'), - fullhashes=l.get(b'fullhashes'), - fullhashseed=l.get(b'fullhashseed'), - serverdercerts=l.get(b'serverdercerts'), - servercadercerts=l.get(b'servercadercerts'), - ) - return - - atoms = [{b'msg': o[b'error'][b'message']}] - if b'args' in o[b'error']: - atoms[0][b'args'] = o[b'error'][b'args'] - - raise error.RepoError(formatrichmessage(atoms)) - - def objects(self): - """Obtained decoded objects from this response. - - This is a generator of data structures that were decoded from the - command response. - - Obtaining the next member of the generator may block due to waiting - on external data to become available. - - If the server encountered an error in the middle of serving the data - or if another error occurred, an exception may be raised when - advancing the generator. - """ - while True: - # TODO this can infinite loop if self._inputcomplete is never - # set. We likely want to tie the lifetime of this object/state - # to that of the background thread receiving frames and updating - # our state. - self._serviceable.wait(1.0) - - if self._pendingerror: - raise self._pendingerror - - with self._lock: - self._serviceable.clear() - - # Make copies because objects could be mutated during - # iteration. - stop = self._inputcomplete - pending = list(self._pendingevents) - self._pendingevents[:] = [] - - for o in pending: - yield o - - if stop: - break - - -class clienthandler(object): - """Object to handle higher-level client activities. - - The ``clientreactor`` is used to hold low-level state about the frame-based - protocol, such as which requests and streams are active. This type is used - for higher-level operations, such as reading frames from a socket, exposing - and managing a higher-level primitive for representing command responses, - etc. This class is what peers should probably use to bridge wire activity - with the higher-level peer API. - """ - - def __init__( - self, ui, clientreactor, opener=None, requestbuilder=util.urlreq.request - ): - self._ui = ui - self._reactor = clientreactor - self._requests = {} - self._futures = {} - self._responses = {} - self._redirects = [] - self._frameseof = False - self._opener = opener or urlmod.opener(ui) - self._requestbuilder = requestbuilder - - def callcommand(self, command, args, f, redirect=None): - """Register a request to call a command. - - Returns an iterable of frames that should be sent over the wire. - """ - request, action, meta = self._reactor.callcommand( - command, args, redirect=redirect - ) - - if action != b'noop': - raise error.ProgrammingError(b'%s not yet supported' % action) - - rid = request.requestid - self._requests[rid] = request - self._futures[rid] = f - # TODO we need some kind of lifetime on response instances otherwise - # objects() may deadlock. - self._responses[rid] = commandresponse(rid, command) - - return iter(()) - - def flushcommands(self): - """Flush all queued commands. - - Returns an iterable of frames that should be sent over the wire. - """ - action, meta = self._reactor.flushcommands() - - if action != b'sendframes': - raise error.ProgrammingError(b'%s not yet supported' % action) - - return meta[b'framegen'] - - def readdata(self, framefh): - """Attempt to read data and do work. - - Returns None if no data was read. Presumably this means we're - done with all read I/O. - """ - if not self._frameseof: - frame = wireprotoframing.readframe(framefh) - if frame is None: - # TODO tell reactor? - self._frameseof = True - else: - self._ui.debug(b'received %r\n' % frame) - self._processframe(frame) - - # Also try to read the first redirect. - if self._redirects: - if not self._processredirect(*self._redirects[0]): - self._redirects.pop(0) - - if self._frameseof and not self._redirects: - return None - - return True - - def _processframe(self, frame): - """Process a single read frame.""" - - action, meta = self._reactor.onframerecv(frame) - - if action == b'error': - e = error.RepoError(meta[b'message']) - - if frame.requestid in self._responses: - self._responses[frame.requestid]._oninputcomplete() - - if frame.requestid in self._futures: - self._futures[frame.requestid].set_exception(e) - del self._futures[frame.requestid] - else: - raise e - - return - elif action == b'noop': - return - elif action == b'responsedata': - # Handled below. - pass - else: - raise error.ProgrammingError(b'action not handled: %s' % action) - - if frame.requestid not in self._requests: - raise error.ProgrammingError( - b'received frame for unknown request; this is either a bug in ' - b'the clientreactor not screening for this or this instance was ' - b'never told about this request: %r' % frame - ) - - response = self._responses[frame.requestid] - - if action == b'responsedata': - # Any failures processing this frame should bubble up to the - # future tracking the request. - try: - self._processresponsedata(frame, meta, response) - except BaseException as e: - # If an exception occurs before the future is resolved, - # fail the future. Otherwise, we stuff the exception on - # the response object so it can be raised during objects() - # iteration. If nothing is consuming objects(), we could - # silently swallow this exception. That's a risk we'll have to - # take. - if frame.requestid in self._futures: - self._futures[frame.requestid].set_exception(e) - del self._futures[frame.requestid] - response._oninputcomplete() - else: - response._onerror(e) - else: - raise error.ProgrammingError( - b'unhandled action from clientreactor: %s' % action - ) - - def _processresponsedata(self, frame, meta, response): - # This can raise. The caller can handle it. - response._onresponsedata(meta[b'data']) - - # We need to be careful about resolving futures prematurely. If a - # response is a redirect response, resolving the future before the - # redirect is processed would result in the consumer seeing an - # empty stream of objects, since they'd be consuming our - # response.objects() instead of the redirect's response.objects(). - # - # Our strategy is to not resolve/finish the request until either - # EOS occurs or until the initial response object is fully received. - - # Always react to eos. - if meta[b'eos']: - response._oninputcomplete() - del self._requests[frame.requestid] - - # Not EOS but we haven't decoded the initial response object yet. - # Return and wait for more data. - elif not response._seeninitial: - return - - # The specification says no objects should follow the initial/redirect - # object. So it should be safe to handle the redirect object if one is - # decoded, without having to wait for EOS. - if response._redirect: - self._followredirect(frame.requestid, response._redirect) - return - - # If the command has a decoder, we wait until all input has been - # received before resolving the future. Otherwise we resolve the - # future immediately. - if frame.requestid not in self._futures: - return - - if response.command not in COMMAND_DECODERS: - self._futures[frame.requestid].set_result(response.objects()) - del self._futures[frame.requestid] - elif response._inputcomplete: - decoded = COMMAND_DECODERS[response.command](response.objects()) - self._futures[frame.requestid].set_result(decoded) - del self._futures[frame.requestid] - - def _followredirect(self, requestid, redirect): - """Called to initiate redirect following for a request.""" - self._ui.note(_(b'(following redirect to %s)\n') % redirect.url) - - # TODO handle framed responses. - if redirect.mediatype != b'application/mercurial-cbor': - raise error.Abort( - _(b'cannot handle redirects for the %s media type') - % redirect.mediatype - ) - - if redirect.fullhashes: - self._ui.warn( - _( - b'(support for validating hashes on content ' - b'redirects not supported)\n' - ) - ) - - if redirect.serverdercerts or redirect.servercadercerts: - self._ui.warn( - _( - b'(support for pinning server certificates on ' - b'content redirects not supported)\n' - ) - ) - - headers = { - 'Accept': redirect.mediatype, - } - - req = self._requestbuilder(pycompat.strurl(redirect.url), None, headers) - - try: - res = self._opener.open(req) - except util.urlerr.httperror as e: - if e.code == 401: - raise error.Abort(_(b'authorization failed')) - raise - except util.httplib.HTTPException as e: - self._ui.debug(b'http error requesting %s\n' % req.get_full_url()) - self._ui.traceback() - raise IOError(None, e) - - urlmod.wrapresponse(res) - - # The existing response object is associated with frame data. Rather - # than try to normalize its state, just create a new object. - oldresponse = self._responses[requestid] - self._responses[requestid] = commandresponse( - requestid, oldresponse.command, fromredirect=True - ) - - self._redirects.append((requestid, res)) - - def _processredirect(self, rid, res): - """Called to continue processing a response from a redirect. - - Returns a bool indicating if the redirect is still serviceable. - """ - response = self._responses[rid] - - try: - data = res.read(32768) - response._onresponsedata(data) - - # We're at end of stream. - if not data: - response._oninputcomplete() - - if rid not in self._futures: - return bool(data) - - if response.command not in COMMAND_DECODERS: - self._futures[rid].set_result(response.objects()) - del self._futures[rid] - elif response._inputcomplete: - decoded = COMMAND_DECODERS[response.command](response.objects()) - self._futures[rid].set_result(decoded) - del self._futures[rid] - - return bool(data) - - except BaseException as e: - self._futures[rid].set_exception(e) - del self._futures[rid] - response._oninputcomplete() - return False - - -def decodebranchmap(objs): - # Response should be a single CBOR map of branch name to array of nodes. - bm = next(objs) - - return {encoding.tolocal(k): v for k, v in bm.items()} - - -def decodeheads(objs): - # Array of node bytestrings. - return next(objs) - - -def decodeknown(objs): - # Bytestring where each byte is a 0 or 1. - raw = next(objs) - - return [True if raw[i : i + 1] == b'1' else False for i in range(len(raw))] - - -def decodelistkeys(objs): - # Map with bytestring keys and values. - return next(objs) - - -def decodelookup(objs): - return next(objs) - - -def decodepushkey(objs): - return next(objs) - - -COMMAND_DECODERS = { - b'branchmap': decodebranchmap, - b'heads': decodeheads, - b'known': decodeknown, - b'listkeys': decodelistkeys, - b'lookup': decodelookup, - b'pushkey': decodepushkey, -} diff --git a/mercurial/wireprotov2server.py b/mercurial/wireprotov2server.py deleted file mode 100644 --- a/mercurial/wireprotov2server.py +++ /dev/null @@ -1,1613 +0,0 @@ -# Copyright 21 May 2005 - (c) 2005 Jake Edge -# 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 - -import collections -import contextlib - -from .i18n import _ -from .node import hex -from . import ( - discovery, - encoding, - error, - match as matchmod, - narrowspec, - pycompat, - streamclone, - templatefilters, - util, - wireprotoframing, - wireprototypes, -) -from .interfaces import util as interfaceutil -from .utils import ( - cborutil, - hashutil, - stringutil, -) - -FRAMINGTYPE = b'application/mercurial-exp-framing-0006' - -HTTP_WIREPROTO_V2 = wireprototypes.HTTP_WIREPROTO_V2 - -COMMANDS = wireprototypes.commanddict() - -# Value inserted into cache key computation function. Change the value to -# force new cache keys for every command request. This should be done when -# there is a change to how caching works, etc. -GLOBAL_CACHE_VERSION = 1 - - -def handlehttpv2request(rctx, req, res, checkperm, urlparts): - from .hgweb import common as hgwebcommon - - # URL space looks like: /, where can - # be ``ro`` or ``rw`` to signal read-only or read-write, respectively. - - # Root URL does nothing meaningful... yet. - if not urlparts: - res.status = b'200 OK' - res.headers[b'Content-Type'] = b'text/plain' - res.setbodybytes(_(b'HTTP version 2 API handler')) - return - - if len(urlparts) == 1: - res.status = b'404 Not Found' - res.headers[b'Content-Type'] = b'text/plain' - res.setbodybytes( - _(b'do not know how to process %s\n') % req.dispatchpath - ) - return - - permission, command = urlparts[0:2] - - if permission not in (b'ro', b'rw'): - res.status = b'404 Not Found' - res.headers[b'Content-Type'] = b'text/plain' - res.setbodybytes(_(b'unknown permission: %s') % permission) - return - - if req.method != b'POST': - res.status = b'405 Method Not Allowed' - res.headers[b'Allow'] = b'POST' - res.setbodybytes(_(b'commands require POST requests')) - return - - # At some point we'll want to use our own API instead of recycling the - # behavior of version 1 of the wire protocol... - # TODO return reasonable responses - not responses that overload the - # HTTP status line message for error reporting. - try: - checkperm(rctx, req, b'pull' if permission == b'ro' else b'push') - except hgwebcommon.ErrorResponse as e: - res.status = hgwebcommon.statusmessage( - e.code, stringutil.forcebytestr(e) - ) - for k, v in e.headers: - res.headers[k] = v - res.setbodybytes(b'permission denied') - return - - # We have a special endpoint to reflect the request back at the client. - if command == b'debugreflect': - _processhttpv2reflectrequest(rctx.repo.ui, rctx.repo, req, res) - return - - # Extra commands that we handle that aren't really wire protocol - # commands. Think extra hard before making this hackery available to - # extension. - extracommands = {b'multirequest'} - - if command not in COMMANDS and command not in extracommands: - res.status = b'404 Not Found' - res.headers[b'Content-Type'] = b'text/plain' - res.setbodybytes(_(b'unknown wire protocol command: %s\n') % command) - return - - repo = rctx.repo - ui = repo.ui - - proto = httpv2protocolhandler(req, ui) - - if ( - not COMMANDS.commandavailable(command, proto) - and command not in extracommands - ): - res.status = b'404 Not Found' - res.headers[b'Content-Type'] = b'text/plain' - res.setbodybytes(_(b'invalid wire protocol command: %s') % command) - return - - # TODO consider cases where proxies may add additional Accept headers. - if req.headers.get(b'Accept') != FRAMINGTYPE: - res.status = b'406 Not Acceptable' - res.headers[b'Content-Type'] = b'text/plain' - res.setbodybytes( - _(b'client MUST specify Accept header with value: %s\n') - % FRAMINGTYPE - ) - return - - if req.headers.get(b'Content-Type') != FRAMINGTYPE: - res.status = b'415 Unsupported Media Type' - # TODO we should send a response with appropriate media type, - # since client does Accept it. - res.headers[b'Content-Type'] = b'text/plain' - res.setbodybytes( - _(b'client MUST send Content-Type header with value: %s\n') - % FRAMINGTYPE - ) - return - - _processhttpv2request(ui, repo, req, res, permission, command, proto) - - -def _processhttpv2reflectrequest(ui, repo, req, res): - """Reads unified frame protocol request and dumps out state to client. - - This special endpoint can be used to help debug the wire protocol. - - Instead of routing the request through the normal dispatch mechanism, - we instead read all frames, decode them, and feed them into our state - tracker. We then dump the log of all that activity back out to the - client. - """ - # Reflection APIs have a history of being abused, accidentally disclosing - # sensitive data, etc. So we have a config knob. - if not ui.configbool(b'experimental', b'web.api.debugreflect'): - res.status = b'404 Not Found' - res.headers[b'Content-Type'] = b'text/plain' - res.setbodybytes(_(b'debugreflect service not available')) - return - - # We assume we have a unified framing protocol request body. - - reactor = wireprotoframing.serverreactor(ui) - states = [] - - while True: - frame = wireprotoframing.readframe(req.bodyfh) - - if not frame: - states.append(b'received: ') - break - - states.append( - b'received: %d %d %d %s' - % (frame.typeid, frame.flags, frame.requestid, frame.payload) - ) - - action, meta = reactor.onframerecv(frame) - states.append(templatefilters.json((action, meta))) - - action, meta = reactor.oninputeof() - meta[b'action'] = action - states.append(templatefilters.json(meta)) - - res.status = b'200 OK' - res.headers[b'Content-Type'] = b'text/plain' - res.setbodybytes(b'\n'.join(states)) - - -def _processhttpv2request(ui, repo, req, res, authedperm, reqcommand, proto): - """Post-validation handler for HTTPv2 requests. - - Called when the HTTP request contains unified frame-based protocol - frames for evaluation. - """ - # TODO Some HTTP clients are full duplex and can receive data before - # the entire request is transmitted. Figure out a way to indicate support - # for that so we can opt into full duplex mode. - reactor = wireprotoframing.serverreactor(ui, deferoutput=True) - seencommand = False - - outstream = None - - while True: - frame = wireprotoframing.readframe(req.bodyfh) - if not frame: - break - - action, meta = reactor.onframerecv(frame) - - if action == b'wantframe': - # Need more data before we can do anything. - continue - elif action == b'runcommand': - # Defer creating output stream because we need to wait for - # protocol settings frames so proper encoding can be applied. - if not outstream: - outstream = reactor.makeoutputstream() - - sentoutput = _httpv2runcommand( - ui, - repo, - req, - res, - authedperm, - reqcommand, - reactor, - outstream, - meta, - issubsequent=seencommand, - ) - - if sentoutput: - return - - seencommand = True - - elif action == b'error': - # TODO define proper error mechanism. - res.status = b'200 OK' - res.headers[b'Content-Type'] = b'text/plain' - res.setbodybytes(meta[b'message'] + b'\n') - return - else: - raise error.ProgrammingError( - b'unhandled action from frame processor: %s' % action - ) - - action, meta = reactor.oninputeof() - if action == b'sendframes': - # We assume we haven't started sending the response yet. If we're - # wrong, the response type will raise an exception. - res.status = b'200 OK' - res.headers[b'Content-Type'] = FRAMINGTYPE - res.setbodygen(meta[b'framegen']) - elif action == b'noop': - pass - else: - raise error.ProgrammingError( - b'unhandled action from frame processor: %s' % action - ) - - -def _httpv2runcommand( - ui, - repo, - req, - res, - authedperm, - reqcommand, - reactor, - outstream, - command, - issubsequent, -): - """Dispatch a wire protocol command made from HTTPv2 requests. - - The authenticated permission (``authedperm``) along with the original - command from the URL (``reqcommand``) are passed in. - """ - # We already validated that the session has permissions to perform the - # actions in ``authedperm``. In the unified frame protocol, the canonical - # command to run is expressed in a frame. However, the URL also requested - # to run a specific command. We need to be careful that the command we - # run doesn't have permissions requirements greater than what was granted - # by ``authedperm``. - # - # Our rule for this is we only allow one command per HTTP request and - # that command must match the command in the URL. However, we make - # an exception for the ``multirequest`` URL. This URL is allowed to - # execute multiple commands. We double check permissions of each command - # as it is invoked to ensure there is no privilege escalation. - # TODO consider allowing multiple commands to regular command URLs - # iff each command is the same. - - proto = httpv2protocolhandler(req, ui, args=command[b'args']) - - if reqcommand == b'multirequest': - if not COMMANDS.commandavailable(command[b'command'], proto): - # TODO proper error mechanism - res.status = b'200 OK' - res.headers[b'Content-Type'] = b'text/plain' - res.setbodybytes( - _(b'wire protocol command not available: %s') - % command[b'command'] - ) - return True - - # TODO don't use assert here, since it may be elided by -O. - assert authedperm in (b'ro', b'rw') - wirecommand = COMMANDS[command[b'command']] - assert wirecommand.permission in (b'push', b'pull') - - if authedperm == b'ro' and wirecommand.permission != b'pull': - # TODO proper error mechanism - res.status = b'403 Forbidden' - res.headers[b'Content-Type'] = b'text/plain' - res.setbodybytes( - _(b'insufficient permissions to execute command: %s') - % command[b'command'] - ) - return True - - # TODO should we also call checkperm() here? Maybe not if we're going - # to overhaul that API. The granted scope from the URL check should - # be good enough. - - else: - # Don't allow multiple commands outside of ``multirequest`` URL. - if issubsequent: - # TODO proper error mechanism - res.status = b'200 OK' - res.headers[b'Content-Type'] = b'text/plain' - res.setbodybytes( - _(b'multiple commands cannot be issued to this URL') - ) - return True - - if reqcommand != command[b'command']: - # TODO define proper error mechanism - res.status = b'200 OK' - res.headers[b'Content-Type'] = b'text/plain' - res.setbodybytes(_(b'command in frame must match command in URL')) - return True - - res.status = b'200 OK' - res.headers[b'Content-Type'] = FRAMINGTYPE - - try: - objs = dispatch(repo, proto, command[b'command'], command[b'redirect']) - - action, meta = reactor.oncommandresponsereadyobjects( - outstream, command[b'requestid'], objs - ) - - except error.WireprotoCommandError as e: - action, meta = reactor.oncommanderror( - outstream, command[b'requestid'], e.message, e.messageargs - ) - - except Exception as e: - action, meta = reactor.onservererror( - outstream, - command[b'requestid'], - _(b'exception when invoking command: %s') - % stringutil.forcebytestr(e), - ) - - if action == b'sendframes': - res.setbodygen(meta[b'framegen']) - return True - elif action == b'noop': - return False - else: - raise error.ProgrammingError( - b'unhandled event from reactor: %s' % action - ) - - -def getdispatchrepo(repo, proto, command): - viewconfig = repo.ui.config(b'server', b'view') - return repo.filtered(viewconfig) - - -def dispatch(repo, proto, command, redirect): - """Run a wire protocol command. - - Returns an iterable of objects that will be sent to the client. - """ - repo = getdispatchrepo(repo, proto, command) - - entry = COMMANDS[command] - func = entry.func - spec = entry.args - - args = proto.getargs(spec) - - # There is some duplicate boilerplate code here for calling the command and - # emitting objects. It is either that or a lot of indented code that looks - # like a pyramid (since there are a lot of code paths that result in not - # using the cacher). - callcommand = lambda: func(repo, proto, **pycompat.strkwargs(args)) - - # Request is not cacheable. Don't bother instantiating a cacher. - if not entry.cachekeyfn: - for o in callcommand(): - yield o - return - - if redirect: - redirecttargets = redirect[b'targets'] - redirecthashes = redirect[b'hashes'] - else: - redirecttargets = [] - redirecthashes = [] - - cacher = makeresponsecacher( - repo, - proto, - command, - args, - cborutil.streamencode, - redirecttargets=redirecttargets, - redirecthashes=redirecthashes, - ) - - # But we have no cacher. Do default handling. - if not cacher: - for o in callcommand(): - yield o - return - - with cacher: - cachekey = entry.cachekeyfn( - repo, proto, cacher, **pycompat.strkwargs(args) - ) - - # No cache key or the cacher doesn't like it. Do default handling. - if cachekey is None or not cacher.setcachekey(cachekey): - for o in callcommand(): - yield o - return - - # Serve it from the cache, if possible. - cached = cacher.lookup() - - if cached: - for o in cached[b'objs']: - yield o - return - - # Else call the command and feed its output into the cacher, allowing - # the cacher to buffer/mutate objects as it desires. - for o in callcommand(): - for o in cacher.onobject(o): - yield o - - for o in cacher.onfinished(): - yield o - - -@interfaceutil.implementer(wireprototypes.baseprotocolhandler) -class httpv2protocolhandler(object): - def __init__(self, req, ui, args=None): - self._req = req - self._ui = ui - self._args = args - - @property - def name(self): - return HTTP_WIREPROTO_V2 - - def getargs(self, args): - # First look for args that were passed but aren't registered on this - # command. - extra = set(self._args) - set(args) - if extra: - raise error.WireprotoCommandError( - b'unsupported argument to command: %s' - % b', '.join(sorted(extra)) - ) - - # And look for required arguments that are missing. - missing = {a for a in args if args[a][b'required']} - set(self._args) - - if missing: - raise error.WireprotoCommandError( - b'missing required arguments: %s' % b', '.join(sorted(missing)) - ) - - # Now derive the arguments to pass to the command, taking into - # account the arguments specified by the client. - data = {} - for k, meta in sorted(args.items()): - # This argument wasn't passed by the client. - if k not in self._args: - data[k] = meta[b'default']() - continue - - v = self._args[k] - - # Sets may be expressed as lists. Silently normalize. - if meta[b'type'] == b'set' and isinstance(v, list): - v = set(v) - - # TODO consider more/stronger type validation. - - data[k] = v - - return data - - def getprotocaps(self): - # Protocol capabilities are currently not implemented for HTTP V2. - return set() - - def getpayload(self): - raise NotImplementedError - - @contextlib.contextmanager - def mayberedirectstdio(self): - raise NotImplementedError - - def client(self): - raise NotImplementedError - - def addcapabilities(self, repo, caps): - return caps - - def checkperm(self, perm): - raise NotImplementedError - - -def httpv2apidescriptor(req, repo): - proto = httpv2protocolhandler(req, repo.ui) - - return _capabilitiesv2(repo, proto) - - -def _capabilitiesv2(repo, proto): - """Obtain the set of capabilities for version 2 transports. - - These capabilities are distinct from the capabilities for version 1 - transports. - """ - caps = { - b'commands': {}, - b'framingmediatypes': [FRAMINGTYPE], - b'pathfilterprefixes': set(narrowspec.VALID_PREFIXES), - } - - for command, entry in COMMANDS.items(): - args = {} - - for arg, meta in entry.args.items(): - args[arg] = { - # TODO should this be a normalized type using CBOR's - # terminology? - b'type': meta[b'type'], - b'required': meta[b'required'], - } - - if not meta[b'required']: - args[arg][b'default'] = meta[b'default']() - - if meta[b'validvalues']: - args[arg][b'validvalues'] = meta[b'validvalues'] - - # TODO this type of check should be defined in a per-command callback. - if ( - command == b'rawstorefiledata' - and not streamclone.allowservergeneration(repo) - ): - continue - - caps[b'commands'][command] = { - b'args': args, - b'permissions': [entry.permission], - } - - if entry.extracapabilitiesfn: - extracaps = entry.extracapabilitiesfn(repo, proto) - caps[b'commands'][command].update(extracaps) - - caps[b'rawrepoformats'] = sorted(repo.requirements & repo.supportedformats) - - targets = getadvertisedredirecttargets(repo, proto) - if targets: - caps[b'redirect'] = { - b'targets': [], - b'hashes': [b'sha256', b'sha1'], - } - - for target in targets: - entry = { - b'name': target[b'name'], - b'protocol': target[b'protocol'], - b'uris': target[b'uris'], - } - - for key in (b'snirequired', b'tlsversions'): - if key in target: - entry[key] = target[key] - - caps[b'redirect'][b'targets'].append(entry) - - return proto.addcapabilities(repo, caps) - - -def getadvertisedredirecttargets(repo, proto): - """Obtain a list of content redirect targets. - - Returns a list containing potential redirect targets that will be - advertised in capabilities data. Each dict MUST have the following - keys: - - name - The name of this redirect target. This is the identifier clients use - to refer to a target. It is transferred as part of every command - request. - - protocol - Network protocol used by this target. Typically this is the string - in front of the ``://`` in a URL. e.g. ``https``. - - uris - List of representative URIs for this target. Clients can use the - URIs to test parsing for compatibility or for ordering preference - for which target to use. - - The following optional keys are recognized: - - snirequired - Bool indicating if Server Name Indication (SNI) is required to - connect to this target. - - tlsversions - List of bytes indicating which TLS versions are supported by this - target. - - By default, clients reflect the target order advertised by servers - and servers will use the first client-advertised target when picking - a redirect target. So targets should be advertised in the order the - server prefers they be used. - """ - return [] - - -def wireprotocommand( - name, - args=None, - permission=b'push', - cachekeyfn=None, - extracapabilitiesfn=None, -): - """Decorator to declare a wire protocol command. - - ``name`` is the name of the wire protocol command being provided. - - ``args`` is a dict defining arguments accepted by the command. Keys are - the argument name. Values are dicts with the following keys: - - ``type`` - The argument data type. Must be one of the following string - literals: ``bytes``, ``int``, ``list``, ``dict``, ``set``, - or ``bool``. - - ``default`` - A callable returning the default value for this argument. If not - specified, ``None`` will be the default value. - - ``example`` - An example value for this argument. - - ``validvalues`` - Set of recognized values for this argument. - - ``permission`` defines the permission type needed to run this command. - Can be ``push`` or ``pull``. These roughly map to read-write and read-only, - respectively. Default is to assume command requires ``push`` permissions - because otherwise commands not declaring their permissions could modify - a repository that is supposed to be read-only. - - ``cachekeyfn`` defines an optional callable that can derive the - cache key for this request. - - ``extracapabilitiesfn`` defines an optional callable that defines extra - command capabilities/parameters that are advertised next to the command - in the capabilities data structure describing the server. The callable - receives as arguments the repository and protocol objects. It returns - a dict of extra fields to add to the command descriptor. - - Wire protocol commands are generators of objects to be serialized and - sent to the client. - - If a command raises an uncaught exception, this will be translated into - a command error. - - All commands can opt in to being cacheable by defining a function - (``cachekeyfn``) that is called to derive a cache key. This function - receives the same arguments as the command itself plus a ``cacher`` - argument containing the active cacher for the request and returns a bytes - containing the key in a cache the response to this command may be cached - under. - """ - transports = { - k for k, v in wireprototypes.TRANSPORTS.items() if v[b'version'] == 2 - } - - if permission not in (b'push', b'pull'): - raise error.ProgrammingError( - b'invalid wire protocol permission; ' - b'got %s; expected "push" or "pull"' % permission - ) - - if args is None: - args = {} - - if not isinstance(args, dict): - raise error.ProgrammingError( - b'arguments for version 2 commands must be declared as dicts' - ) - - for arg, meta in args.items(): - if arg == b'*': - raise error.ProgrammingError( - b'* argument name not allowed on version 2 commands' - ) - - if not isinstance(meta, dict): - raise error.ProgrammingError( - b'arguments for version 2 commands ' - b'must declare metadata as a dict' - ) - - if b'type' not in meta: - raise error.ProgrammingError( - b'%s argument for command %s does not ' - b'declare type field' % (arg, name) - ) - - if meta[b'type'] not in ( - b'bytes', - b'int', - b'list', - b'dict', - b'set', - b'bool', - ): - raise error.ProgrammingError( - b'%s argument for command %s has ' - b'illegal type: %s' % (arg, name, meta[b'type']) - ) - - if b'example' not in meta: - raise error.ProgrammingError( - b'%s argument for command %s does not ' - b'declare example field' % (arg, name) - ) - - meta[b'required'] = b'default' not in meta - - meta.setdefault(b'default', lambda: None) - meta.setdefault(b'validvalues', None) - - def register(func): - if name in COMMANDS: - raise error.ProgrammingError( - b'%s command already registered for version 2' % name - ) - - COMMANDS[name] = wireprototypes.commandentry( - func, - args=args, - transports=transports, - permission=permission, - cachekeyfn=cachekeyfn, - extracapabilitiesfn=extracapabilitiesfn, - ) - - return func - - return register - - -def makecommandcachekeyfn(command, localversion=None, allargs=False): - """Construct a cache key derivation function with common features. - - By default, the cache key is a hash of: - - * The command name. - * A global cache version number. - * A local cache version number (passed via ``localversion``). - * All the arguments passed to the command. - * The media type used. - * Wire protocol version string. - * The repository path. - """ - if not allargs: - raise error.ProgrammingError( - b'only allargs=True is currently supported' - ) - - if localversion is None: - raise error.ProgrammingError(b'must set localversion argument value') - - def cachekeyfn(repo, proto, cacher, **args): - spec = COMMANDS[command] - - # Commands that mutate the repo can not be cached. - if spec.permission == b'push': - return None - - # TODO config option to disable caching. - - # Our key derivation strategy is to construct a data structure - # holding everything that could influence cacheability and to hash - # the CBOR representation of that. Using CBOR seems like it might - # be overkill. However, simpler hashing mechanisms are prone to - # duplicate input issues. e.g. if you just concatenate two values, - # "foo"+"bar" is identical to "fo"+"obar". Using CBOR provides - # "padding" between values and prevents these problems. - - # Seed the hash with various data. - state = { - # To invalidate all cache keys. - b'globalversion': GLOBAL_CACHE_VERSION, - # More granular cache key invalidation. - b'localversion': localversion, - # Cache keys are segmented by command. - b'command': command, - # Throw in the media type and API version strings so changes - # to exchange semantics invalid cache. - b'mediatype': FRAMINGTYPE, - b'version': HTTP_WIREPROTO_V2, - # So same requests for different repos don't share cache keys. - b'repo': repo.root, - } - - # The arguments passed to us will have already been normalized. - # Default values will be set, etc. This is important because it - # means that it doesn't matter if clients send an explicit argument - # or rely on the default value: it will all normalize to the same - # set of arguments on the server and therefore the same cache key. - # - # Arguments by their very nature must support being encoded to CBOR. - # And the CBOR encoder is deterministic. So we hash the arguments - # by feeding the CBOR of their representation into the hasher. - if allargs: - state[b'args'] = pycompat.byteskwargs(args) - - cacher.adjustcachekeystate(state) - - hasher = hashutil.sha1() - for chunk in cborutil.streamencode(state): - hasher.update(chunk) - - return pycompat.sysbytes(hasher.hexdigest()) - - return cachekeyfn - - -def makeresponsecacher( - repo, proto, command, args, objencoderfn, redirecttargets, redirecthashes -): - """Construct a cacher for a cacheable command. - - Returns an ``iwireprotocolcommandcacher`` instance. - - Extensions can monkeypatch this function to provide custom caching - backends. - """ - return None - - -def resolvenodes(repo, revisions): - """Resolve nodes from a revisions specifier data structure.""" - cl = repo.changelog - clhasnode = cl.hasnode - - seen = set() - nodes = [] - - if not isinstance(revisions, list): - raise error.WireprotoCommandError( - b'revisions must be defined as an array' - ) - - for spec in revisions: - if b'type' not in spec: - raise error.WireprotoCommandError( - b'type key not present in revision specifier' - ) - - typ = spec[b'type'] - - if typ == b'changesetexplicit': - if b'nodes' not in spec: - raise error.WireprotoCommandError( - b'nodes key not present in changesetexplicit revision ' - b'specifier' - ) - - for node in spec[b'nodes']: - if node not in seen: - nodes.append(node) - seen.add(node) - - elif typ == b'changesetexplicitdepth': - for key in (b'nodes', b'depth'): - if key not in spec: - raise error.WireprotoCommandError( - b'%s key not present in changesetexplicitdepth revision ' - b'specifier', - (key,), - ) - - for rev in repo.revs( - b'ancestors(%ln, %s)', spec[b'nodes'], spec[b'depth'] - 1 - ): - node = cl.node(rev) - - if node not in seen: - nodes.append(node) - seen.add(node) - - elif typ == b'changesetdagrange': - for key in (b'roots', b'heads'): - if key not in spec: - raise error.WireprotoCommandError( - b'%s key not present in changesetdagrange revision ' - b'specifier', - (key,), - ) - - if not spec[b'heads']: - raise error.WireprotoCommandError( - b'heads key in changesetdagrange cannot be empty' - ) - - if spec[b'roots']: - common = [n for n in spec[b'roots'] if clhasnode(n)] - else: - common = [repo.nullid] - - for n in discovery.outgoing(repo, common, spec[b'heads']).missing: - if n not in seen: - nodes.append(n) - seen.add(n) - - else: - raise error.WireprotoCommandError( - b'unknown revision specifier type: %s', (typ,) - ) - - return nodes - - -@wireprotocommand(b'branchmap', permission=b'pull') -def branchmapv2(repo, proto): - yield { - encoding.fromlocal(k): v - for k, v in pycompat.iteritems(repo.branchmap()) - } - - -@wireprotocommand(b'capabilities', permission=b'pull') -def capabilitiesv2(repo, proto): - yield _capabilitiesv2(repo, proto) - - -@wireprotocommand( - b'changesetdata', - args={ - b'revisions': { - b'type': b'list', - b'example': [ - { - b'type': b'changesetexplicit', - b'nodes': [b'abcdef...'], - } - ], - }, - b'fields': { - b'type': b'set', - b'default': set, - b'example': {b'parents', b'revision'}, - b'validvalues': {b'bookmarks', b'parents', b'phase', b'revision'}, - }, - }, - permission=b'pull', -) -def changesetdata(repo, proto, revisions, fields): - # TODO look for unknown fields and abort when they can't be serviced. - # This could probably be validated by dispatcher using validvalues. - - cl = repo.changelog - outgoing = resolvenodes(repo, revisions) - publishing = repo.publishing() - - if outgoing: - repo.hook(b'preoutgoing', throw=True, source=b'serve') - - yield { - b'totalitems': len(outgoing), - } - - # The phases of nodes already transferred to the client may have changed - # since the client last requested data. We send phase-only records - # for these revisions, if requested. - # TODO actually do this. We'll probably want to emit phase heads - # in the ancestry set of the outgoing revisions. This will ensure - # that phase updates within that set are seen. - if b'phase' in fields: - pass - - nodebookmarks = {} - for mark, node in repo._bookmarks.items(): - nodebookmarks.setdefault(node, set()).add(mark) - - # It is already topologically sorted by revision number. - for node in outgoing: - d = { - b'node': node, - } - - if b'parents' in fields: - d[b'parents'] = cl.parents(node) - - if b'phase' in fields: - if publishing: - d[b'phase'] = b'public' - else: - ctx = repo[node] - d[b'phase'] = ctx.phasestr() - - if b'bookmarks' in fields and node in nodebookmarks: - d[b'bookmarks'] = sorted(nodebookmarks[node]) - del nodebookmarks[node] - - followingmeta = [] - followingdata = [] - - if b'revision' in fields: - revisiondata = cl.revision(node) - followingmeta.append((b'revision', len(revisiondata))) - followingdata.append(revisiondata) - - # TODO make it possible for extensions to wrap a function or register - # a handler to service custom fields. - - if followingmeta: - d[b'fieldsfollowing'] = followingmeta - - yield d - - for extra in followingdata: - yield extra - - # If requested, send bookmarks from nodes that didn't have revision - # data sent so receiver is aware of any bookmark updates. - if b'bookmarks' in fields: - for node, marks in sorted(pycompat.iteritems(nodebookmarks)): - yield { - b'node': node, - b'bookmarks': sorted(marks), - } - - -class FileAccessError(Exception): - """Represents an error accessing a specific file.""" - - def __init__(self, path, msg, args): - self.path = path - self.msg = msg - self.args = args - - -def getfilestore(repo, proto, path): - """Obtain a file storage object for use with wire protocol. - - Exists as a standalone function so extensions can monkeypatch to add - access control. - """ - # This seems to work even if the file doesn't exist. So catch - # "empty" files and return an error. - fl = repo.file(path) - - if not len(fl): - raise FileAccessError(path, b'unknown file: %s', (path,)) - - return fl - - -def emitfilerevisions(repo, path, revisions, linknodes, fields): - for revision in revisions: - d = { - b'node': revision.node, - } - - if b'parents' in fields: - d[b'parents'] = [revision.p1node, revision.p2node] - - if b'linknode' in fields: - d[b'linknode'] = linknodes[revision.node] - - followingmeta = [] - followingdata = [] - - if b'revision' in fields: - if revision.revision is not None: - followingmeta.append((b'revision', len(revision.revision))) - followingdata.append(revision.revision) - else: - d[b'deltabasenode'] = revision.basenode - followingmeta.append((b'delta', len(revision.delta))) - followingdata.append(revision.delta) - - if followingmeta: - d[b'fieldsfollowing'] = followingmeta - - yield d - - for extra in followingdata: - yield extra - - -def makefilematcher(repo, pathfilter): - """Construct a matcher from a path filter dict.""" - - # Validate values. - if pathfilter: - for key in (b'include', b'exclude'): - for pattern in pathfilter.get(key, []): - if not pattern.startswith((b'path:', b'rootfilesin:')): - raise error.WireprotoCommandError( - b'%s pattern must begin with `path:` or `rootfilesin:`; ' - b'got %s', - (key, pattern), - ) - - if pathfilter: - matcher = matchmod.match( - repo.root, - b'', - include=pathfilter.get(b'include', []), - exclude=pathfilter.get(b'exclude', []), - ) - else: - matcher = matchmod.match(repo.root, b'') - - # Requested patterns could include files not in the local store. So - # filter those out. - return repo.narrowmatch(matcher) - - -@wireprotocommand( - b'filedata', - args={ - b'haveparents': { - b'type': b'bool', - b'default': lambda: False, - b'example': True, - }, - b'nodes': { - b'type': b'list', - b'example': [b'0123456...'], - }, - b'fields': { - b'type': b'set', - b'default': set, - b'example': {b'parents', b'revision'}, - b'validvalues': {b'parents', b'revision', b'linknode'}, - }, - b'path': { - b'type': b'bytes', - b'example': b'foo.txt', - }, - }, - permission=b'pull', - # TODO censoring a file revision won't invalidate the cache. - # Figure out a way to take censoring into account when deriving - # the cache key. - cachekeyfn=makecommandcachekeyfn(b'filedata', 1, allargs=True), -) -def filedata(repo, proto, haveparents, nodes, fields, path): - # TODO this API allows access to file revisions that are attached to - # secret changesets. filesdata does not have this problem. Maybe this - # API should be deleted? - - try: - # Extensions may wish to access the protocol handler. - store = getfilestore(repo, proto, path) - except FileAccessError as e: - raise error.WireprotoCommandError(e.msg, e.args) - - clnode = repo.changelog.node - linknodes = {} - - # Validate requested nodes. - for node in nodes: - try: - store.rev(node) - except error.LookupError: - raise error.WireprotoCommandError( - b'unknown file node: %s', (hex(node),) - ) - - # TODO by creating the filectx against a specific file revision - # instead of changeset, linkrev() is always used. This is wrong for - # cases where linkrev() may refer to a hidden changeset. But since this - # API doesn't know anything about changesets, we're not sure how to - # disambiguate the linknode. Perhaps we should delete this API? - fctx = repo.filectx(path, fileid=node) - linknodes[node] = clnode(fctx.introrev()) - - revisions = store.emitrevisions( - nodes, - revisiondata=b'revision' in fields, - assumehaveparentrevisions=haveparents, - ) - - yield { - b'totalitems': len(nodes), - } - - for o in emitfilerevisions(repo, path, revisions, linknodes, fields): - yield o - - -def filesdatacapabilities(repo, proto): - batchsize = repo.ui.configint( - b'experimental', b'server.filesdata.recommended-batch-size' - ) - return { - b'recommendedbatchsize': batchsize, - } - - -@wireprotocommand( - b'filesdata', - args={ - b'haveparents': { - b'type': b'bool', - b'default': lambda: False, - b'example': True, - }, - b'fields': { - b'type': b'set', - b'default': set, - b'example': {b'parents', b'revision'}, - b'validvalues': { - b'firstchangeset', - b'linknode', - b'parents', - b'revision', - }, - }, - b'pathfilter': { - b'type': b'dict', - b'default': lambda: None, - b'example': {b'include': [b'path:tests']}, - }, - b'revisions': { - b'type': b'list', - b'example': [ - { - b'type': b'changesetexplicit', - b'nodes': [b'abcdef...'], - } - ], - }, - }, - permission=b'pull', - # TODO censoring a file revision won't invalidate the cache. - # Figure out a way to take censoring into account when deriving - # the cache key. - cachekeyfn=makecommandcachekeyfn(b'filesdata', 1, allargs=True), - extracapabilitiesfn=filesdatacapabilities, -) -def filesdata(repo, proto, haveparents, fields, pathfilter, revisions): - # TODO This should operate on a repo that exposes obsolete changesets. There - # is a race between a client making a push that obsoletes a changeset and - # another client fetching files data for that changeset. If a client has a - # changeset, it should probably be allowed to access files data for that - # changeset. - - outgoing = resolvenodes(repo, revisions) - filematcher = makefilematcher(repo, pathfilter) - - # path -> {fnode: linknode} - fnodes = collections.defaultdict(dict) - - # We collect the set of relevant file revisions by iterating the changeset - # revisions and either walking the set of files recorded in the changeset - # or by walking the manifest at that revision. There is probably room for a - # storage-level API to request this data, as it can be expensive to compute - # and would benefit from caching or alternate storage from what revlogs - # provide. - for node in outgoing: - ctx = repo[node] - mctx = ctx.manifestctx() - md = mctx.read() - - if haveparents: - checkpaths = ctx.files() - else: - checkpaths = md.keys() - - for path in checkpaths: - fnode = md[path] - - if path in fnodes and fnode in fnodes[path]: - continue - - if not filematcher(path): - continue - - fnodes[path].setdefault(fnode, node) - - yield { - b'totalpaths': len(fnodes), - b'totalitems': sum(len(v) for v in fnodes.values()), - } - - for path, filenodes in sorted(fnodes.items()): - try: - store = getfilestore(repo, proto, path) - except FileAccessError as e: - raise error.WireprotoCommandError(e.msg, e.args) - - yield { - b'path': path, - b'totalitems': len(filenodes), - } - - revisions = store.emitrevisions( - filenodes.keys(), - revisiondata=b'revision' in fields, - assumehaveparentrevisions=haveparents, - ) - - for o in emitfilerevisions(repo, path, revisions, filenodes, fields): - yield o - - -@wireprotocommand( - b'heads', - args={ - b'publiconly': { - b'type': b'bool', - b'default': lambda: False, - b'example': False, - }, - }, - permission=b'pull', -) -def headsv2(repo, proto, publiconly): - if publiconly: - repo = repo.filtered(b'immutable') - - yield repo.heads() - - -@wireprotocommand( - b'known', - args={ - b'nodes': { - b'type': b'list', - b'default': list, - b'example': [b'deadbeef'], - }, - }, - permission=b'pull', -) -def knownv2(repo, proto, nodes): - result = b''.join(b'1' if n else b'0' for n in repo.known(nodes)) - yield result - - -@wireprotocommand( - b'listkeys', - args={ - b'namespace': { - b'type': b'bytes', - b'example': b'ns', - }, - }, - permission=b'pull', -) -def listkeysv2(repo, proto, namespace): - keys = repo.listkeys(encoding.tolocal(namespace)) - keys = { - encoding.fromlocal(k): encoding.fromlocal(v) - for k, v in pycompat.iteritems(keys) - } - - yield keys - - -@wireprotocommand( - b'lookup', - args={ - b'key': { - b'type': b'bytes', - b'example': b'foo', - }, - }, - permission=b'pull', -) -def lookupv2(repo, proto, key): - key = encoding.tolocal(key) - - # TODO handle exception. - node = repo.lookup(key) - - yield node - - -def manifestdatacapabilities(repo, proto): - batchsize = repo.ui.configint( - b'experimental', b'server.manifestdata.recommended-batch-size' - ) - - return { - b'recommendedbatchsize': batchsize, - } - - -@wireprotocommand( - b'manifestdata', - args={ - b'nodes': { - b'type': b'list', - b'example': [b'0123456...'], - }, - b'haveparents': { - b'type': b'bool', - b'default': lambda: False, - b'example': True, - }, - b'fields': { - b'type': b'set', - b'default': set, - b'example': {b'parents', b'revision'}, - b'validvalues': {b'parents', b'revision'}, - }, - b'tree': { - b'type': b'bytes', - b'example': b'', - }, - }, - permission=b'pull', - cachekeyfn=makecommandcachekeyfn(b'manifestdata', 1, allargs=True), - extracapabilitiesfn=manifestdatacapabilities, -) -def manifestdata(repo, proto, haveparents, nodes, fields, tree): - store = repo.manifestlog.getstorage(tree) - - # Validate the node is known and abort on unknown revisions. - for node in nodes: - try: - store.rev(node) - except error.LookupError: - raise error.WireprotoCommandError(b'unknown node: %s', (node,)) - - revisions = store.emitrevisions( - nodes, - revisiondata=b'revision' in fields, - assumehaveparentrevisions=haveparents, - ) - - yield { - b'totalitems': len(nodes), - } - - for revision in revisions: - d = { - b'node': revision.node, - } - - if b'parents' in fields: - d[b'parents'] = [revision.p1node, revision.p2node] - - followingmeta = [] - followingdata = [] - - if b'revision' in fields: - if revision.revision is not None: - followingmeta.append((b'revision', len(revision.revision))) - followingdata.append(revision.revision) - else: - d[b'deltabasenode'] = revision.basenode - followingmeta.append((b'delta', len(revision.delta))) - followingdata.append(revision.delta) - - if followingmeta: - d[b'fieldsfollowing'] = followingmeta - - yield d - - for extra in followingdata: - yield extra - - -@wireprotocommand( - b'pushkey', - args={ - b'namespace': { - b'type': b'bytes', - b'example': b'ns', - }, - b'key': { - b'type': b'bytes', - b'example': b'key', - }, - b'old': { - b'type': b'bytes', - b'example': b'old', - }, - b'new': { - b'type': b'bytes', - b'example': b'new', - }, - }, - permission=b'push', -) -def pushkeyv2(repo, proto, namespace, key, old, new): - # TODO handle ui output redirection - yield repo.pushkey( - encoding.tolocal(namespace), - encoding.tolocal(key), - encoding.tolocal(old), - encoding.tolocal(new), - ) - - -@wireprotocommand( - b'rawstorefiledata', - args={ - b'files': { - b'type': b'list', - b'example': [b'changelog', b'manifestlog'], - }, - b'pathfilter': { - b'type': b'list', - b'default': lambda: None, - b'example': {b'include': [b'path:tests']}, - }, - }, - permission=b'pull', -) -def rawstorefiledata(repo, proto, files, pathfilter): - if not streamclone.allowservergeneration(repo): - raise error.WireprotoCommandError(b'stream clone is disabled') - - # TODO support dynamically advertising what store files "sets" are - # available. For now, we support changelog, manifestlog, and files. - files = set(files) - allowedfiles = {b'changelog', b'manifestlog'} - - unsupported = files - allowedfiles - if unsupported: - raise error.WireprotoCommandError( - b'unknown file type: %s', (b', '.join(sorted(unsupported)),) - ) - - with repo.lock(): - topfiles = list(repo.store.topfiles()) - - sendfiles = [] - totalsize = 0 - - # TODO this is a bunch of storage layer interface abstractions because - # it assumes revlogs. - for rl_type, name, 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'): - pass - else: - continue - - sendfiles.append((b'store', name, size)) - totalsize += size - - yield { - b'filecount': len(sendfiles), - b'totalsize': totalsize, - } - - for location, name, size in sendfiles: - yield { - b'location': location, - b'path': name, - b'size': size, - } - - # We have to use a closure for this to ensure the context manager is - # closed only after sending the final chunk. - def getfiledata(): - with repo.svfs(name, b'rb', auditpath=False) as fh: - for chunk in util.filechunkiter(fh, limit=size): - yield chunk - - yield wireprototypes.indefinitebytestringresponse(getfiledata()) diff --git a/relnotes/next b/relnotes/next --- a/relnotes/next +++ b/relnotes/next @@ -4,16 +4,42 @@ == Default Format Change == These changes affects newly created repositories (or new clone) done with -Mercurial XXX. +Mercurial 6.1. + +The `share-safe` format variant is now enabled by default. It makes +configuration and requirements more consistent across repository and their +shares. This introduces a behavior change as shares from a repository using the +new format will also use their main repository's configuration. + +See `hg help config.format.use-share-safe` for details about the feature and +the available options for auto-upgrading existing shares. == New Experimental Features == == Bug Fixes == +The `--no-check` and `--no-merge` now properly overwrite the behavior from `commands.update.check`. == Backwards Compatibility Changes == +The remotefilelog extension now requires an appropiate excludepattern +for subrepositories. + +The labels passed to merge tools have changed slightly. Merge tools can get +labels passed to them if you include `$labellocal`, `$labelbase`, and/or +`$labelother` in the `merge-tool..args` configuration. These labels +used to have some space-padding, and truncation to fit within 72 columns. Both +the padding and the truncation has been removed. + +Some of the text in labels passed to merge tools has changed. For example, +in conflicts while running `hg histedit`, the labels used to be "local", +"base", and "histedit". They are now "already edited", +"parent of current change", and "current change", respectively. + +The use of `share-safe`, means shares (of new repositories) will also use their +main repository's configuration see the `Default Format Change` section +for details. == Internal API Changes == diff --git a/rust/Cargo.lock b/rust/Cargo.lock --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "adler" version = "0.2.3" @@ -314,21 +316,19 @@ dependencies = [ [[package]] name = "format-bytes" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c4e89040c7fd7b4e6ba2820ac705a45def8a0c098ec78d170ae88f1ef1d5762" +checksum = "48942366ef93975da38e175ac9e10068c6fc08ca9e85930d4f098f4d5b14c2fd" dependencies = [ "format-bytes-macros", - "proc-macro-hack", ] [[package]] name = "format-bytes-macros" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05089e341a0460449e2210c3bf7b61597860b07f0deae58da38dbed0a4c6b6d" +checksum = "203aadebefcc73d12038296c228eabf830f99cba991b0032adf20e9fa6ce7e4f" dependencies = [ - "proc-macro-hack", "proc-macro2", "quote", "syn", @@ -356,6 +356,17 @@ dependencies = [ ] [[package]] +name = "getrandom" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", +] + +[[package]] name = "glob" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -371,6 +382,12 @@ dependencies = [ ] [[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] name = "hg-core" version = "0.1.0" dependencies = [ @@ -391,7 +408,7 @@ dependencies = [ "memmap2", "micro-timer", "pretty_assertions", - "rand", + "rand 0.8.4", "rand_distr", "rand_pcg", "rayon", @@ -415,6 +432,7 @@ dependencies = [ "libc", "log", "stable_deref_trait", + "vcsgraph", ] [[package]] @@ -442,7 +460,7 @@ source = "registry+https://github.com/ru checksum = "3ca8957e71f04a205cb162508f9326aea04676c8dfd0711220190d6b83664f3f" dependencies = [ "bitmaps", - "rand_core", + "rand_core 0.5.1", "rand_xoshiro", "sized-chunks", "typenum", @@ -480,6 +498,12 @@ source = "registry+https://github.com/ru checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" [[package]] +name = "libm" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" + +[[package]] name = "libz-sys" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -579,6 +603,7 @@ source = "registry+https://github.com/ru checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -637,12 +662,6 @@ dependencies = [ ] [[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" @@ -692,11 +711,23 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ - "getrandom", + "getrandom 0.1.15", "libc", - "rand_chacha", - "rand_core", - "rand_hc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc 0.2.0", +] + +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.3", + "rand_hc 0.3.1", ] [[package]] @@ -706,7 +737,17 @@ source = "registry+https://github.com/ru checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.3", ] [[package]] @@ -715,16 +756,26 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ - "getrandom", + "getrandom 0.1.15", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom 0.2.4", ] [[package]] name = "rand_distr" -version = "0.2.2" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96977acbdd3a6576fb1d27391900035bf3863d4a16422973a409b488cf29ffb2" +checksum = "964d548f8e7d12e102ef183a0de7e98180c9f8729f555897a857b96e48122d2f" dependencies = [ - "rand", + "num-traits", + "rand 0.8.4", ] [[package]] @@ -733,16 +784,25 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ - "rand_core", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core 0.6.3", ] [[package]] name = "rand_pcg" -version = "0.2.1" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" dependencies = [ - "rand_core", + "rand_core 0.6.3", ] [[package]] @@ -751,7 +811,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9fcdd2e881d02f1d9390ae47ad8e5696a9e4be7b547a1da2afbc61973217004" dependencies = [ - "rand_core", + "rand_core 0.5.1", ] [[package]] @@ -816,6 +876,7 @@ dependencies = [ name = "rhg" version = "0.1.0" dependencies = [ + "atty", "chrono", "clap", "derive_more", @@ -905,7 +966,7 @@ checksum = "7a6e24d9338a0a5be79593e2fa15 dependencies = [ "cfg-if 0.1.10", "libc", - "rand", + "rand 0.7.3", "redox_syscall", "remove_dir_all", "winapi", @@ -956,7 +1017,7 @@ source = "registry+https://github.com/ru checksum = "04f8ab788026715fa63b31960869617cba39117e520eb415b0139543e325ab59" dependencies = [ "cfg-if 0.1.10", - "rand", + "rand 0.7.3", "static_assertions", ] @@ -995,6 +1056,17 @@ source = "registry+https://github.com/ru checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb" [[package]] +name = "vcsgraph" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cb68c231e2575f7503a7c19213875f9d4ec2e84e963a56ce3de4b6bee351ef7" +dependencies = [ + "hex", + "rand 0.7.3", + "sha-1", +] + +[[package]] name = "vec_map" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/rust/hg-core/Cargo.toml b/rust/hg-core/Cargo.toml --- a/rust/hg-core/Cargo.toml +++ b/rust/hg-core/Cargo.toml @@ -18,9 +18,9 @@ im-rc = "15.0.*" itertools = "0.9" lazy_static = "1.4.0" libc = "0.2" -rand = "0.7.3" -rand_pcg = "0.2.1" -rand_distr = "0.2.2" +rand = "0.8.4" +rand_pcg = "0.3.1" +rand_distr = "0.4.2" rayon = "1.3.0" regex = "1.3.9" sha-1 = "0.9.6" @@ -33,7 +33,7 @@ micro-timer = "0.3.0" log = "0.4.8" memmap2 = {version = "0.4", features = ["stable_deref_trait"]} zstd = "0.5.3" -format-bytes = "0.2.2" +format-bytes = "0.3.0" # 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. diff --git a/rust/hg-core/src/ancestors.rs b/rust/hg-core/src/ancestors.rs --- a/rust/hg-core/src/ancestors.rs +++ b/rust/hg-core/src/ancestors.rs @@ -26,15 +26,6 @@ pub struct AncestorsIterator { stoprev: Revision, } -/// Lazy ancestors set, backed by AncestorsIterator -pub struct LazyAncestors { - graph: G, - containsiter: AncestorsIterator, - initrevs: Vec, - stoprev: Revision, - inclusive: bool, -} - pub struct MissingAncestors { graph: G, bases: HashSet, @@ -165,49 +156,6 @@ impl Iterator for AncestorsIte } } -impl LazyAncestors { - pub fn new( - graph: G, - initrevs: impl IntoIterator, - stoprev: Revision, - inclusive: bool, - ) -> Result { - let v: Vec = initrevs.into_iter().collect(); - Ok(LazyAncestors { - graph: graph.clone(), - containsiter: AncestorsIterator::new( - graph, - v.iter().cloned(), - stoprev, - inclusive, - )?, - initrevs: v, - stoprev, - inclusive, - }) - } - - pub fn contains(&mut self, rev: Revision) -> Result { - self.containsiter.contains(rev) - } - - pub fn is_empty(&self) -> bool { - self.containsiter.is_empty() - } - - pub fn iter(&self) -> AncestorsIterator { - // the arguments being the same as for self.containsiter, we know - // for sure that AncestorsIterator constructor can't fail - AncestorsIterator::new( - self.graph.clone(), - self.initrevs.iter().cloned(), - self.stoprev, - self.inclusive, - ) - .unwrap() - } -} - impl MissingAncestors { pub fn new(graph: G, bases: impl IntoIterator) -> Self { let mut created = MissingAncestors { @@ -550,39 +498,6 @@ mod tests { } #[test] - fn test_lazy_iter_contains() { - let mut lazy = - LazyAncestors::new(SampleGraph, vec![11, 13], 0, false).unwrap(); - - let revs: Vec = lazy.iter().map(|r| r.unwrap()).collect(); - // compare with iterator tests on the same initial revisions - assert_eq!(revs, vec![8, 7, 4, 3, 2, 1, 0]); - - // contains() results are correct, unaffected by the fact that - // we consumed entirely an iterator out of lazy - assert_eq!(lazy.contains(2), Ok(true)); - assert_eq!(lazy.contains(9), Ok(false)); - } - - #[test] - fn test_lazy_contains_iter() { - let mut lazy = - LazyAncestors::new(SampleGraph, vec![11, 13], 0, false).unwrap(); // reminder: [8, 7, 4, 3, 2, 1, 0] - - assert_eq!(lazy.contains(2), Ok(true)); - assert_eq!(lazy.contains(6), Ok(false)); - - // after consumption of 2 by the inner iterator, results stay - // consistent - assert_eq!(lazy.contains(2), Ok(true)); - assert_eq!(lazy.contains(5), Ok(false)); - - // iter() still gives us a fresh iterator - let revs: Vec = lazy.iter().map(|r| r.unwrap()).collect(); - assert_eq!(revs, vec![8, 7, 4, 3, 2, 1, 0]); - } - - #[test] /// Test constructor, add/get bases and heads fn test_missing_bases() -> Result<(), GraphError> { let mut missing_ancestors = diff --git a/rust/hg-core/src/config.rs b/rust/hg-core/src/config.rs --- a/rust/hg-core/src/config.rs +++ b/rust/hg-core/src/config.rs @@ -13,4 +13,4 @@ mod config; mod layer; mod values; pub use config::{Config, ConfigSource, ConfigValueParseError}; -pub use layer::{ConfigError, ConfigParseError}; +pub use layer::{ConfigError, ConfigOrigin, ConfigParseError}; diff --git a/rust/hg-core/src/config/config.rs b/rust/hg-core/src/config/config.rs --- a/rust/hg-core/src/config/config.rs +++ b/rust/hg-core/src/config/config.rs @@ -84,6 +84,11 @@ impl fmt::Display for ConfigValueParseEr } impl Config { + /// The configuration to use when printing configuration-loading errors + pub fn empty() -> Self { + Self { layers: Vec::new() } + } + /// Load system and user configuration from various files. /// /// This is also affected by some environment variables. @@ -133,13 +138,19 @@ impl Config { Ok(config) } - pub fn load_cli_args_config( + pub fn load_cli_args( &mut self, cli_config_args: impl IntoIterator>, + color_arg: Option>, ) -> Result<(), ConfigError> { if let Some(layer) = ConfigLayer::parse_cli_args(cli_config_args)? { self.layers.push(layer) } + if let Some(arg) = color_arg { + let mut layer = ConfigLayer::new(ConfigOrigin::CommandLineColor); + layer.add(b"ui"[..].into(), b"color"[..].into(), arg, None); + self.layers.push(layer) + } Ok(()) } @@ -361,6 +372,15 @@ impl Config { Ok(self.get_option(section, item)?.unwrap_or(false)) } + /// Returns `true` if the extension is enabled, `false` otherwise + pub fn is_extension_enabled(&self, extension: &[u8]) -> bool { + let value = self.get(b"extensions", extension); + match value { + Some(c) => !c.starts_with(b"!"), + None => false, + } + } + /// If there is an `item` value in `section`, parse and return a list of /// byte strings. pub fn get_list( @@ -377,6 +397,16 @@ impl Config { .map(|(_, value)| value.bytes.as_ref()) } + /// Returns the raw value bytes of the first one found, or `None`. + pub fn get_with_origin( + &self, + section: &[u8], + item: &[u8], + ) -> Option<(&[u8], &ConfigOrigin)> { + self.get_inner(section, item) + .map(|(layer, value)| (value.bytes.as_ref(), &layer.origin)) + } + /// Returns the layer and the value of the first one found, or `None`. fn get_inner( &self, @@ -402,6 +432,66 @@ impl Config { .collect() } + /// Returns whether any key is defined in the given section + pub fn has_non_empty_section(&self, section: &[u8]) -> bool { + self.layers + .iter() + .any(|layer| layer.has_non_empty_section(section)) + } + + /// Yields (key, value) pairs for everything in the given section + pub fn iter_section<'a>( + &'a self, + section: &'a [u8], + ) -> impl Iterator + 'a { + // TODO: Use `Iterator`’s `.peekable()` when its `peek_mut` is + // available: + // https://doc.rust-lang.org/nightly/std/iter/struct.Peekable.html#method.peek_mut + struct Peekable { + iter: I, + /// Remember a peeked value, even if it was None. + peeked: Option>, + } + + impl Peekable { + fn new(iter: I) -> Self { + Self { iter, peeked: None } + } + + fn next(&mut self) { + self.peeked = None + } + + fn peek_mut(&mut self) -> Option<&mut I::Item> { + let iter = &mut self.iter; + self.peeked.get_or_insert_with(|| iter.next()).as_mut() + } + } + + // Deduplicate keys redefined in multiple layers + let mut keys_already_seen = HashSet::new(); + let mut key_is_new = + move |&(key, _value): &(&'a [u8], &'a [u8])| -> bool { + keys_already_seen.insert(key) + }; + // This is similar to `flat_map` + `filter_map`, except with a single + // closure that owns `key_is_new` (and therefore the + // `keys_already_seen` set): + let mut layer_iters = Peekable::new( + self.layers + .iter() + .rev() + .map(move |layer| layer.iter_section(section)), + ); + std::iter::from_fn(move || loop { + if let Some(pair) = layer_iters.peek_mut()?.find(&mut key_is_new) { + return Some(pair); + } else { + layer_iters.next(); + } + }) + } + /// Get raw values bytes from all layers (even untrusted ones) in order /// of precedence. #[cfg(test)] diff --git a/rust/hg-core/src/config/layer.rs b/rust/hg-core/src/config/layer.rs --- a/rust/hg-core/src/config/layer.rs +++ b/rust/hg-core/src/config/layer.rs @@ -127,6 +127,24 @@ impl ConfigLayer { .flat_map(|section| section.keys().map(|vec| &**vec)) } + /// Returns the (key, value) pairs defined in the given section + pub fn iter_section<'layer>( + &'layer self, + section: &[u8], + ) -> impl Iterator { + self.sections + .get(section) + .into_iter() + .flat_map(|section| section.iter().map(|(k, v)| (&**k, &*v.bytes))) + } + + /// Returns whether any key is defined in the given section + pub fn has_non_empty_section(&self, section: &[u8]) -> bool { + self.sections + .get(section) + .map_or(false, |section| !section.is_empty()) + } + pub fn is_empty(&self) -> bool { self.sections.is_empty() } @@ -277,16 +295,17 @@ pub struct ConfigValue { pub line: Option, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum ConfigOrigin { /// From a configuration file File(PathBuf), /// From a `--config` CLI argument CommandLine, + /// From a `--color` CLI argument + CommandLineColor, /// From environment variables like `$PAGER` or `$EDITOR` Environment(Vec), - /* TODO cli - * TODO defaults (configitems.py) + /* TODO defaults (configitems.py) * TODO extensions * TODO Python resources? * Others? */ @@ -300,6 +319,7 @@ impl DisplayBytes for ConfigOrigin { match self { ConfigOrigin::File(p) => out.write_all(&get_bytes_from_path(p)), ConfigOrigin::CommandLine => out.write_all(b"--config"), + ConfigOrigin::CommandLineColor => out.write_all(b"--color"), ConfigOrigin::Environment(e) => write_bytes!(out, b"${}", e), } } diff --git a/rust/hg-core/src/dirstate/entry.rs b/rust/hg-core/src/dirstate/entry.rs --- a/rust/hg-core/src/dirstate/entry.rs +++ b/rust/hg-core/src/dirstate/entry.rs @@ -43,6 +43,10 @@ pub struct TruncatedTimestamp { truncated_seconds: u32, /// Always in the `0 .. 1_000_000_000` range. nanoseconds: u32, + /// TODO this should be in DirstateEntry, but the current code needs + /// refactoring to use DirstateEntry instead of TruncatedTimestamp for + /// comparison. + pub second_ambiguous: bool, } impl TruncatedTimestamp { @@ -50,11 +54,16 @@ impl TruncatedTimestamp { /// and truncate the seconds components to its lower 31 bits. /// /// Panics if the nanoseconds components is not in the expected range. - pub fn new_truncate(seconds: i64, nanoseconds: u32) -> Self { + pub fn new_truncate( + seconds: i64, + nanoseconds: u32, + second_ambiguous: bool, + ) -> Self { assert!(nanoseconds < NSEC_PER_SEC); Self { truncated_seconds: seconds as u32 & RANGE_MASK_31BIT, nanoseconds, + second_ambiguous, } } @@ -63,6 +72,7 @@ impl TruncatedTimestamp { pub fn from_already_truncated( truncated_seconds: u32, nanoseconds: u32, + second_ambiguous: bool, ) -> Result { if truncated_seconds & !RANGE_MASK_31BIT == 0 && nanoseconds < NSEC_PER_SEC @@ -70,12 +80,17 @@ impl TruncatedTimestamp { Ok(Self { truncated_seconds, nanoseconds, + second_ambiguous, }) } else { Err(DirstateV2ParseError) } } + /// Returns a `TruncatedTimestamp` for the modification time of `metadata`. + /// + /// Propagates errors from `std` on platforms where modification time + /// is not available at all. pub fn for_mtime_of(metadata: &fs::Metadata) -> io::Result { #[cfg(unix)] { @@ -83,7 +98,7 @@ impl TruncatedTimestamp { let seconds = metadata.mtime(); // i64 -> u32 with value always in the `0 .. NSEC_PER_SEC` range let nanoseconds = metadata.mtime_nsec().try_into().unwrap(); - Ok(Self::new_truncate(seconds, nanoseconds)) + Ok(Self::new_truncate(seconds, nanoseconds, false)) } #[cfg(not(unix))] { @@ -91,6 +106,47 @@ impl TruncatedTimestamp { } } + /// Like `for_mtime_of`, but may return `None` or a value with + /// `second_ambiguous` set if the mtime is not "reliable". + /// + /// A modification time is reliable if it is older than `boundary` (or + /// sufficiently in the future). + /// + /// Otherwise a concurrent modification might happens with the same mtime. + pub fn for_reliable_mtime_of( + metadata: &fs::Metadata, + boundary: &Self, + ) -> io::Result> { + let mut mtime = Self::for_mtime_of(metadata)?; + // If the mtime of the ambiguous file is younger (or equal) to the + // starting point of the `status` walk, we cannot garantee that + // another, racy, write will not happen right after with the same mtime + // and we cannot cache the information. + // + // However if the mtime is far away in the future, this is likely some + // mismatch between the current clock and previous file system + // operation. So mtime more than one days in the future are considered + // fine. + let reliable = if mtime.truncated_seconds == boundary.truncated_seconds + { + mtime.second_ambiguous = true; + mtime.nanoseconds != 0 + && boundary.nanoseconds != 0 + && mtime.nanoseconds < boundary.nanoseconds + } else { + // `truncated_seconds` is less than 2**31, + // so this does not overflow `u32`: + let one_day_later = boundary.truncated_seconds + 24 * 3600; + mtime.truncated_seconds < boundary.truncated_seconds + || mtime.truncated_seconds > one_day_later + }; + if reliable { + Ok(Some(mtime)) + } else { + Ok(None) + } + } + /// The lower 31 bits of the number of seconds since the epoch. pub fn truncated_seconds(&self) -> u32 { self.truncated_seconds @@ -122,10 +178,17 @@ impl TruncatedTimestamp { /// in that way, doing a simple comparison would cause many false /// negatives. pub fn likely_equal(self, other: Self) -> bool { - self.truncated_seconds == other.truncated_seconds - && (self.nanoseconds == other.nanoseconds - || self.nanoseconds == 0 - || other.nanoseconds == 0) + if self.truncated_seconds != other.truncated_seconds { + false + } else if self.nanoseconds == 0 || other.nanoseconds == 0 { + if self.second_ambiguous { + false + } else { + true + } + } else { + self.nanoseconds == other.nanoseconds + } } pub fn likely_equal_to_mtime_of( @@ -168,12 +231,12 @@ impl From for TruncatedTimes } } }; - Self::new_truncate(seconds, nanoseconds) + Self::new_truncate(seconds, nanoseconds, false) } } const NSEC_PER_SEC: u32 = 1_000_000_000; -const RANGE_MASK_31BIT: u32 = 0x7FFF_FFFF; +pub const RANGE_MASK_31BIT: u32 = 0x7FFF_FFFF; pub const MTIME_UNSET: i32 = -1; @@ -258,9 +321,10 @@ impl DirstateEntry { let mode = u32::try_from(mode).unwrap(); let size = u32::try_from(size).unwrap(); let mtime = u32::try_from(mtime).unwrap(); - let mtime = - TruncatedTimestamp::from_already_truncated(mtime, 0) - .unwrap(); + let mtime = TruncatedTimestamp::from_already_truncated( + mtime, 0, false, + ) + .unwrap(); Self { flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED, mode_size: Some((mode, size)), @@ -438,7 +502,11 @@ impl DirstateEntry { } else if !self.flags.contains(Flags::P1_TRACKED) { MTIME_UNSET } else if let Some(mtime) = self.mtime { - i32::try_from(mtime.truncated_seconds()).unwrap() + if mtime.second_ambiguous { + MTIME_UNSET + } else { + i32::try_from(mtime.truncated_seconds()).unwrap() + } } else { MTIME_UNSET } @@ -580,10 +648,8 @@ impl DirstateEntry { &self, filesystem_metadata: &std::fs::Metadata, ) -> bool { - use std::os::unix::fs::MetadataExt; - const EXEC_BIT_MASK: u32 = 0o100; - let dirstate_exec_bit = (self.mode() as u32) & EXEC_BIT_MASK; - let fs_exec_bit = filesystem_metadata.mode() & EXEC_BIT_MASK; + let dirstate_exec_bit = (self.mode() as u32 & EXEC_BIT_MASK) != 0; + let fs_exec_bit = has_exec_bit(filesystem_metadata); dirstate_exec_bit != fs_exec_bit } @@ -592,16 +658,6 @@ impl DirstateEntry { pub fn debug_tuple(&self) -> (u8, i32, i32, i32) { (self.state().into(), self.mode(), self.size(), self.mtime()) } - - /// True if the stored mtime would be ambiguous with the current time - pub fn need_delay(&self, now: TruncatedTimestamp) -> bool { - if let Some(mtime) = self.mtime { - self.state() == EntryState::Normal - && mtime.truncated_seconds() == now.truncated_seconds() - } else { - false - } - } } impl EntryState { @@ -641,3 +697,11 @@ impl Into for EntryState { } } } + +const EXEC_BIT_MASK: u32 = 0o100; + +pub fn has_exec_bit(metadata: &std::fs::Metadata) -> bool { + // TODO: How to handle executable permissions on Windows? + use std::os::unix::fs::MetadataExt; + (metadata.mode() & EXEC_BIT_MASK) != 0 +} diff --git a/rust/hg-core/src/dirstate/status.rs b/rust/hg-core/src/dirstate/status.rs --- a/rust/hg-core/src/dirstate/status.rs +++ b/rust/hg-core/src/dirstate/status.rs @@ -9,10 +9,9 @@ //! It is currently missing a lot of functionality compared to the Python one //! and will only be triggered in narrow cases. +use crate::dirstate::entry::TruncatedTimestamp; use crate::dirstate_tree::on_disk::DirstateV2ParseError; - use crate::{ - dirstate::TruncatedTimestamp, utils::hg_path::{HgPath, HgPathError}, PatternError, }; @@ -62,46 +61,48 @@ pub type HgPathCow<'a> = Cow<'a, HgPath> #[derive(Debug, Copy, Clone)] pub struct StatusOptions { - /// Remember the most recent modification timeslot for status, to make - /// sure we won't miss future size-preserving file content modifications - /// that happen within the same timeslot. - pub last_normal_time: TruncatedTimestamp, /// Whether we are on a filesystem with UNIX-like exec flags pub check_exec: bool, pub list_clean: bool, pub list_unknown: bool, pub list_ignored: bool, + /// Whether to populate `StatusPath::copy_source` + pub list_copies: bool, /// Whether to collect traversed dirs for applying a callback later. /// Used by `hg purge` for example. pub collect_traversed_dirs: bool, } -#[derive(Debug, Default)] +#[derive(Default)] pub struct DirstateStatus<'a> { + /// The current time at the start of the `status()` algorithm, as measured + /// and possibly truncated by the filesystem. + pub filesystem_time_at_status_start: Option, + /// Tracked files whose contents have changed since the parent revision - pub modified: Vec>, + pub modified: Vec>, /// Newly-tracked files that were not present in the parent - pub added: Vec>, + pub added: Vec>, /// Previously-tracked files that have been (re)moved with an hg command - pub removed: Vec>, + pub removed: Vec>, /// (Still) tracked files that are missing, (re)moved with an non-hg /// command - pub deleted: Vec>, + pub deleted: Vec>, /// Tracked files that are up to date with the parent. /// Only pupulated if `StatusOptions::list_clean` is true. - pub clean: Vec>, + pub clean: Vec>, /// Files in the working directory that are ignored with `.hgignore`. /// Only pupulated if `StatusOptions::list_ignored` is true. - pub ignored: Vec>, + pub ignored: Vec>, /// Files in the working directory that are neither tracked nor ignored. /// Only pupulated if `StatusOptions::list_unknown` is true. - pub unknown: Vec>, + pub unknown: Vec>, /// Was explicitly matched but cannot be found/accessed pub bad: Vec<(HgPathCow<'a>, BadMatch)>, @@ -109,7 +110,7 @@ pub struct DirstateStatus<'a> { /// Either clean or modified, but we can’t tell from filesystem metadata /// alone. The file contents need to be read and compared with that in /// the parent. - pub unsure: Vec>, + pub unsure: Vec>, /// Only filled if `collect_traversed_dirs` is `true` pub traversed: Vec>, @@ -119,10 +120,14 @@ pub struct DirstateStatus<'a> { pub dirty: bool, } +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct StatusPath<'a> { + pub path: HgPathCow<'a>, + pub copy_source: Option>, +} + #[derive(Debug, derive_more::From)] pub enum StatusError { - /// Generic IO error - IO(std::io::Error), /// An invalid path that cannot be represented in Mercurial was found Path(HgPathError), /// An invalid "ignore" pattern was found @@ -136,7 +141,6 @@ pub type StatusResult = Result fmt::Result { match self { - StatusError::IO(error) => error.fmt(f), StatusError::Path(error) => error.fmt(f), StatusError::Pattern(error) => error.fmt(f), StatusError::DirstateV2ParseError(_) => { diff --git a/rust/hg-core/src/dirstate_tree/dirstate_map.rs b/rust/hg-core/src/dirstate_tree/dirstate_map.rs --- a/rust/hg-core/src/dirstate_tree/dirstate_map.rs +++ b/rust/hg-core/src/dirstate_tree/dirstate_map.rs @@ -309,6 +309,25 @@ impl<'tree, 'on_disk> NodeRef<'tree, 'on NodeRef::OnDisk(node) => node.copy_source(on_disk), } } + /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk, + /// HgPath>` detached from `'tree` + pub(super) fn copy_source_borrowed( + &self, + on_disk: &'on_disk [u8], + ) -> Result>, DirstateV2ParseError> + { + Ok(match self { + NodeRef::InMemory(_path, node) => { + node.copy_source.as_ref().map(|source| match source { + Cow::Borrowed(on_disk) => BorrowedPath::OnDisk(on_disk), + Cow::Owned(in_memory) => BorrowedPath::InMemory(in_memory), + }) + } + NodeRef::OnDisk(node) => node + .copy_source(on_disk)? + .map(|source| BorrowedPath::OnDisk(source)), + }) + } pub(super) fn entry( &self, @@ -677,25 +696,6 @@ impl<'on_disk> DirstateMap<'on_disk> { }) } - fn clear_known_ambiguous_mtimes( - &mut self, - paths: &[impl AsRef], - ) -> Result<(), DirstateV2ParseError> { - for path in paths { - if let Some(node) = Self::get_node_mut( - self.on_disk, - &mut self.unreachable_bytes, - &mut self.root, - path.as_ref(), - )? { - if let NodeData::Entry(entry) = &mut node.data { - entry.set_possibly_dirty(); - } - } - } - Ok(()) - } - fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow) { if let Cow::Borrowed(path) = path { *unreachable_bytes += path.len() as u32 @@ -928,31 +928,22 @@ impl OwningDirstateMap { #[timed] pub fn pack_v1( - &mut self, + &self, parents: DirstateParents, - now: TruncatedTimestamp, ) -> Result, DirstateError> { - let map = self.get_map_mut(); - let mut ambiguous_mtimes = Vec::new(); + let map = self.get_map(); // Optizimation (to be measured?): pre-compute size to avoid `Vec` // reallocations let mut size = parents.as_bytes().len(); for node in map.iter_nodes() { let node = node?; - if let Some(entry) = node.entry()? { + if node.entry()?.is_some() { size += packed_entry_size( node.full_path(map.on_disk)?, node.copy_source(map.on_disk)?, ); - if entry.need_delay(now) { - ambiguous_mtimes.push( - node.full_path_borrowed(map.on_disk)? - .detach_from_tree(), - ) - } } } - map.clear_known_ambiguous_mtimes(&ambiguous_mtimes)?; let mut packed = Vec::with_capacity(size); packed.extend(parents.as_bytes()); @@ -977,27 +968,10 @@ impl OwningDirstateMap { /// (false). #[timed] pub fn pack_v2( - &mut self, - now: TruncatedTimestamp, + &self, can_append: bool, - ) -> Result<(Vec, Vec, bool), DirstateError> { - let map = self.get_map_mut(); - let mut paths = Vec::new(); - for node in map.iter_nodes() { - let node = node?; - if let Some(entry) = node.entry()? { - if entry.need_delay(now) { - paths.push( - node.full_path_borrowed(map.on_disk)? - .detach_from_tree(), - ) - } - } - } - // Borrow of `self` ends here since we collect cloned paths - - map.clear_known_ambiguous_mtimes(&paths)?; - + ) -> Result<(Vec, on_disk::TreeMetadata, bool), DirstateError> { + let map = self.get_map(); on_disk::write(map, can_append) } diff --git a/rust/hg-core/src/dirstate_tree/on_disk.rs b/rust/hg-core/src/dirstate_tree/on_disk.rs --- a/rust/hg-core/src/dirstate_tree/on_disk.rs +++ b/rust/hg-core/src/dirstate_tree/on_disk.rs @@ -14,8 +14,10 @@ use bitflags::bitflags; use bytes_cast::unaligned::{U16Be, U32Be}; use bytes_cast::BytesCast; use format_bytes::format_bytes; +use rand::Rng; use std::borrow::Cow; use std::convert::{TryFrom, TryInto}; +use std::fmt::Write; /// Added at the start of `.hg/dirstate` when the "v2" format is used. /// This a redundant sanity check more than an actual "magic number" since @@ -61,14 +63,14 @@ struct DocketHeader { pub struct Docket<'on_disk> { header: &'on_disk DocketHeader, - uuid: &'on_disk [u8], + pub uuid: &'on_disk [u8], } /// Fields are documented in the *Tree metadata in the docket file* /// section of `mercurial/helptext/internals/dirstate-v2.txt` #[derive(BytesCast)] #[repr(C)] -struct TreeMetadata { +pub struct TreeMetadata { root_nodes: ChildNodes, nodes_with_entry_count: Size, nodes_with_copy_source_count: Size, @@ -186,7 +188,51 @@ impl From for crat } } +impl TreeMetadata { + pub fn as_bytes(&self) -> &[u8] { + BytesCast::as_bytes(self) + } +} + impl<'on_disk> Docket<'on_disk> { + /// Generate the identifier for a new data file + /// + /// TODO: support the `HGTEST_UUIDFILE` environment variable. + /// See `mercurial/revlogutils/docket.py` + pub fn new_uid() -> String { + const ID_LENGTH: usize = 8; + let mut id = String::with_capacity(ID_LENGTH); + let mut rng = rand::thread_rng(); + for _ in 0..ID_LENGTH { + // One random hexadecimal digit. + // `unwrap` never panics because `impl Write for String` + // never returns an error. + write!(&mut id, "{:x}", rng.gen_range(0..16)).unwrap(); + } + id + } + + pub fn serialize( + parents: DirstateParents, + tree_metadata: TreeMetadata, + data_size: u64, + uuid: &[u8], + ) -> Result, std::num::TryFromIntError> { + let header = DocketHeader { + marker: *V2_FORMAT_MARKER, + parent_1: parents.p1.pad_to_256_bits(), + parent_2: parents.p2.pad_to_256_bits(), + metadata: tree_metadata, + data_size: u32::try_from(data_size)?.into(), + uuid_size: uuid.len().try_into()?, + }; + let header = header.as_bytes(); + let mut docket = Vec::with_capacity(header.len() + uuid.len()); + docket.extend_from_slice(header); + docket.extend_from_slice(uuid); + Ok(docket) + } + pub fn parents(&self) -> DirstateParents { use crate::Node; let p1 = Node::try_from(&self.header.parent_1[..USED_NODE_ID_BYTES]) @@ -336,7 +382,7 @@ impl Node { && self.flags().contains(Flags::HAS_MTIME) && self.flags().contains(Flags::ALL_UNKNOWN_RECORDED) { - Ok(Some(self.mtime.try_into()?)) + Ok(Some(self.mtime()?)) } else { Ok(None) } @@ -356,6 +402,14 @@ impl Node { (file_type | permisions).into() } + fn mtime(&self) -> Result { + let mut m: TruncatedTimestamp = self.mtime.try_into()?; + if self.flags().contains(Flags::MTIME_SECOND_AMBIGUOUS) { + m.second_ambiguous = true; + } + Ok(m) + } + fn assume_entry(&self) -> Result { // TODO: convert through raw bits instead? let wdir_tracked = self.flags().contains(Flags::WDIR_TRACKED); @@ -371,11 +425,8 @@ impl Node { let mtime = if self.flags().contains(Flags::HAS_MTIME) && !self.flags().contains(Flags::DIRECTORY) && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED) - // The current code is not able to do the more subtle comparison that the - // MTIME_SECOND_AMBIGUOUS requires. So we ignore the mtime - && !self.flags().contains(Flags::MTIME_SECOND_AMBIGUOUS) { - Some(self.mtime.try_into()?) + Some(self.mtime()?) } else { None }; @@ -465,6 +516,9 @@ impl Node { }; let mtime = if let Some(m) = mtime_opt { flags.insert(Flags::HAS_MTIME); + if m.second_ambiguous { + flags.insert(Flags::MTIME_SECOND_AMBIGUOUS); + }; m.into() } else { PackedTruncatedTimestamp::null() @@ -549,9 +603,9 @@ pub(crate) fn for_each_tracked_path<'on_ /// `dirstate_map.on_disk` (true), instead of written to a new data file /// (false). pub(super) fn write( - dirstate_map: &mut DirstateMap, + dirstate_map: &DirstateMap, can_append: bool, -) -> Result<(Vec, Vec, bool), DirstateError> { +) -> Result<(Vec, TreeMetadata, bool), DirstateError> { let append = can_append && dirstate_map.write_should_append(); // This ignores the space for paths, and for nodes without an entry. @@ -577,7 +631,7 @@ pub(super) fn write( unused: [0; 4], ignore_patterns_hash: dirstate_map.ignore_patterns_hash, }; - Ok((writer.out, meta.as_bytes().to_vec(), append)) + Ok((writer.out, meta, append)) } struct Writer<'dmap, 'on_disk> { @@ -631,7 +685,7 @@ impl Writer<'_, '_> { dirstate_map::NodeData::Entry(entry) => { Node::from_dirstate_entry(entry) } - dirstate_map::NodeData::CachedDirectory { mtime } => ( + dirstate_map::NodeData::CachedDirectory { mtime } => { // we currently never set a mtime if unknown file // are present. // So if we have a mtime for a directory, we know @@ -642,12 +696,14 @@ impl Writer<'_, '_> { // We never set ALL_IGNORED_RECORDED since we // don't track that case // currently. - Flags::DIRECTORY + let mut flags = Flags::DIRECTORY | Flags::HAS_MTIME - | Flags::ALL_UNKNOWN_RECORDED, - 0.into(), - (*mtime).into(), - ), + | Flags::ALL_UNKNOWN_RECORDED; + if mtime.second_ambiguous { + flags.insert(Flags::MTIME_SECOND_AMBIGUOUS) + } + (flags, 0.into(), (*mtime).into()) + } dirstate_map::NodeData::None => ( Flags::DIRECTORY, 0.into(), @@ -773,6 +829,7 @@ impl TryFrom f Self::from_already_truncated( timestamp.truncated_seconds.get(), timestamp.nanoseconds.get(), + false, ) } } diff --git a/rust/hg-core/src/dirstate_tree/owning.rs b/rust/hg-core/src/dirstate_tree/owning.rs --- a/rust/hg-core/src/dirstate_tree/owning.rs +++ b/rust/hg-core/src/dirstate_tree/owning.rs @@ -21,7 +21,7 @@ pub struct OwningDirstateMap { /// language cannot represent a lifetime referencing a sibling field. /// This is not quite a self-referencial struct (moving this struct is not /// a problem as it doesn’t change the address of the bytes buffer owned - /// by `PyBytes`) but touches similar borrow-checker limitations. + /// by `on_disk`) but touches similar borrow-checker limitations. ptr: *mut (), } @@ -50,13 +50,13 @@ impl OwningDirstateMap { // SAFETY: We cast the type-erased pointer back to the same type it had // in `new`, except with a different lifetime parameter. This time we // connect the lifetime to that of `self`. This cast is valid because - // `self` owns the same `PyBytes` whose buffer `DirstateMap` - // references. That buffer has a stable memory address because the byte - // string value of a `PyBytes` is immutable. + // `self` owns the same `on_disk` whose buffer `DirstateMap` + // references. That buffer has a stable memory address because our + // `Self::new_empty` counstructor requires `StableDeref`. let ptr: *mut DirstateMap<'a> = self.ptr.cast(); // SAFETY: we dereference that pointer, connecting the lifetime of the - // new `&mut` to that of `self`. This is valid because the - // raw pointer is to a boxed value, and `self` owns that box. + // new `&mut` to that of `self`. This is valid because the + // raw pointer is to a boxed value, and `self` owns that box. (&self.on_disk, unsafe { &mut *ptr }) } @@ -65,7 +65,7 @@ impl OwningDirstateMap { } pub fn get_map<'a>(&'a self) -> &'a DirstateMap<'a> { - // SAFETY: same reasoning as in `get_mut` above. + // SAFETY: same reasoning as in `get_pair_mut` above. let ptr: *mut DirstateMap<'a> = self.ptr.cast(); unsafe { &*ptr } } @@ -79,13 +79,13 @@ impl Drop for OwningDirstateMap { fn drop(&mut self) { // Silence a "field is never read" warning, and demonstrate that this // value is still alive. - let _ = &self.on_disk; + let _: &Box + Send> = &self.on_disk; // SAFETY: this cast is the same as in `get_mut`, and is valid for the // same reason. `self.on_disk` still exists at this point, drop glue // will drop it implicitly after this `drop` method returns. let ptr: *mut DirstateMap<'_> = self.ptr.cast(); // SAFETY: `Box::from_raw` takes ownership of the box away from `self`. - // This is fine because drop glue does nothig for `*mut ()` and we’re + // This is fine because drop glue does nothing for `*mut ()` and we’re // in `drop`, so `get` and `get_mut` cannot be called again. unsafe { drop(Box::from_raw(ptr)) } } diff --git a/rust/hg-core/src/dirstate_tree/status.rs b/rust/hg-core/src/dirstate_tree/status.rs --- a/rust/hg-core/src/dirstate_tree/status.rs +++ b/rust/hg-core/src/dirstate_tree/status.rs @@ -1,5 +1,6 @@ use crate::dirstate::entry::TruncatedTimestamp; use crate::dirstate::status::IgnoreFnType; +use crate::dirstate::status::StatusPath; use crate::dirstate_tree::dirstate_map::BorrowedPath; use crate::dirstate_tree::dirstate_map::ChildNodesRef; use crate::dirstate_tree::dirstate_map::DirstateMap; @@ -15,6 +16,7 @@ use crate::BadMatch; use crate::DirstateStatus; use crate::EntryState; use crate::HgPathBuf; +use crate::HgPathCow; use crate::PatternFileWarning; use crate::StatusError; use crate::StatusOptions; @@ -61,16 +63,42 @@ pub fn status<'tree, 'on_disk: 'tree>( (Box::new(|&_| true), vec![], None) }; + let filesystem_time_at_status_start = + filesystem_now(&root_dir).ok().map(TruncatedTimestamp::from); + + // If the repository is under the current directory, prefer using a + // relative path, so the kernel needs to traverse fewer directory in every + // call to `read_dir` or `symlink_metadata`. + // This is effective in the common case where the current directory is the + // repository root. + + // TODO: Better yet would be to use libc functions like `openat` and + // `fstatat` to remove such repeated traversals entirely, but the standard + // library does not provide APIs based on those. + // Maybe with a crate like https://crates.io/crates/openat instead? + let root_dir = if let Some(relative) = std::env::current_dir() + .ok() + .and_then(|cwd| root_dir.strip_prefix(cwd).ok()) + { + relative + } else { + &root_dir + }; + + let outcome = DirstateStatus { + filesystem_time_at_status_start, + ..Default::default() + }; let common = StatusCommon { dmap, options, matcher, ignore_fn, - outcome: Default::default(), + outcome: Mutex::new(outcome), ignore_patterns_have_changed: patterns_changed, new_cachable_directories: Default::default(), outated_cached_directories: Default::default(), - filesystem_time_at_status_start: filesystem_now(&root_dir).ok(), + filesystem_time_at_status_start, }; let is_at_repo_root = true; let hg_path = &BorrowedPath::OnDisk(HgPath::new("")); @@ -138,10 +166,68 @@ struct StatusCommon<'a, 'tree, 'on_disk: /// The current time at the start of the `status()` algorithm, as measured /// and possibly truncated by the filesystem. - filesystem_time_at_status_start: Option, + filesystem_time_at_status_start: Option, +} + +enum Outcome { + Modified, + Added, + Removed, + Deleted, + Clean, + Ignored, + Unknown, + Unsure, } impl<'a, 'tree, 'on_disk> StatusCommon<'a, 'tree, 'on_disk> { + fn push_outcome( + &self, + which: Outcome, + dirstate_node: &NodeRef<'tree, 'on_disk>, + ) -> Result<(), DirstateV2ParseError> { + let path = dirstate_node + .full_path_borrowed(self.dmap.on_disk)? + .detach_from_tree(); + let copy_source = if self.options.list_copies { + dirstate_node + .copy_source_borrowed(self.dmap.on_disk)? + .map(|source| source.detach_from_tree()) + } else { + None + }; + self.push_outcome_common(which, path, copy_source); + Ok(()) + } + + fn push_outcome_without_copy_source( + &self, + which: Outcome, + path: &BorrowedPath<'_, 'on_disk>, + ) { + self.push_outcome_common(which, path.detach_from_tree(), None) + } + + fn push_outcome_common( + &self, + which: Outcome, + path: HgPathCow<'on_disk>, + copy_source: Option>, + ) { + let mut outcome = self.outcome.lock().unwrap(); + let vec = match which { + Outcome::Modified => &mut outcome.modified, + Outcome::Added => &mut outcome.added, + Outcome::Removed => &mut outcome.removed, + Outcome::Deleted => &mut outcome.deleted, + Outcome::Clean => &mut outcome.clean, + Outcome::Ignored => &mut outcome.ignored, + Outcome::Unknown => &mut outcome.unknown, + Outcome::Unsure => &mut outcome.unsure, + }; + vec.push(StatusPath { path, copy_source }); + } + fn read_dir( &self, hg_path: &HgPath, @@ -342,10 +428,7 @@ impl<'a, 'tree, 'on_disk> StatusCommon<' // If we previously had a file here, it was removed (with // `hg rm` or similar) or deleted before it could be // replaced by a directory or something else. - self.mark_removed_or_deleted_if_file( - &hg_path, - dirstate_node.state()?, - ); + self.mark_removed_or_deleted_if_file(&dirstate_node)?; } if file_type.is_dir() { if self.options.collect_traversed_dirs { @@ -376,24 +459,13 @@ impl<'a, 'tree, 'on_disk> StatusCommon<' if file_or_symlink && self.matcher.matches(hg_path) { if let Some(state) = dirstate_node.state()? { match state { - EntryState::Added => self - .outcome - .lock() - .unwrap() - .added - .push(hg_path.detach_from_tree()), + EntryState::Added => { + self.push_outcome(Outcome::Added, &dirstate_node)? + } EntryState::Removed => self - .outcome - .lock() - .unwrap() - .removed - .push(hg_path.detach_from_tree()), + .push_outcome(Outcome::Removed, &dirstate_node)?, EntryState::Merged => self - .outcome - .lock() - .unwrap() - .modified - .push(hg_path.detach_from_tree()), + .push_outcome(Outcome::Modified, &dirstate_node)?, EntryState::Normal => self .handle_normal_file(&dirstate_node, fs_metadata)?, } @@ -421,71 +493,86 @@ impl<'a, 'tree, 'on_disk> StatusCommon<' directory_metadata: &std::fs::Metadata, dirstate_node: NodeRef<'tree, 'on_disk>, ) -> Result<(), DirstateV2ParseError> { - if children_all_have_dirstate_node_or_are_ignored { - // All filesystem directory entries from `read_dir` have a - // corresponding node in the dirstate, so we can reconstitute the - // names of those entries without calling `read_dir` again. - if let (Some(status_start), Ok(directory_mtime)) = ( - &self.filesystem_time_at_status_start, - directory_metadata.modified(), + if !children_all_have_dirstate_node_or_are_ignored { + return Ok(()); + } + // All filesystem directory entries from `read_dir` have a + // corresponding node in the dirstate, so we can reconstitute the + // names of those entries without calling `read_dir` again. + + // TODO: use let-else here and below when available: + // https://github.com/rust-lang/rust/issues/87335 + let status_start = if let Some(status_start) = + &self.filesystem_time_at_status_start + { + status_start + } else { + return Ok(()); + }; + + // Although the Rust standard library’s `SystemTime` type + // has nanosecond precision, the times reported for a + // directory’s (or file’s) modified time may have lower + // resolution based on the filesystem (for example ext3 + // only stores integer seconds), kernel (see + // https://stackoverflow.com/a/14393315/1162888), etc. + let directory_mtime = if let Ok(option) = + TruncatedTimestamp::for_reliable_mtime_of( + directory_metadata, + status_start, ) { - // Although the Rust standard library’s `SystemTime` type - // has nanosecond precision, the times reported for a - // directory’s (or file’s) modified time may have lower - // resolution based on the filesystem (for example ext3 - // only stores integer seconds), kernel (see - // https://stackoverflow.com/a/14393315/1162888), etc. - if &directory_mtime >= status_start { - // The directory was modified too recently, don’t cache its - // `read_dir` results. - // - // A timeline like this is possible: - // - // 1. A change to this directory (direct child was - // added or removed) cause its mtime to be set - // (possibly truncated) to `directory_mtime` - // 2. This `status` algorithm calls `read_dir` - // 3. An other change is made to the same directory is - // made so that calling `read_dir` agin would give - // different results, but soon enough after 1. that - // the mtime stays the same - // - // On a system where the time resolution poor, this - // scenario is not unlikely if all three steps are caused - // by the same script. - } else { - // We’ve observed (through `status_start`) that time has - // “progressed” since `directory_mtime`, so any further - // change to this directory is extremely likely to cause a - // different mtime. - // - // Having the same mtime again is not entirely impossible - // since the system clock is not monotonous. It could jump - // backward to some point before `directory_mtime`, then a - // directory change could potentially happen during exactly - // the wrong tick. - // - // We deem this scenario (unlike the previous one) to be - // unlikely enough in practice. - let truncated = TruncatedTimestamp::from(directory_mtime); - let is_up_to_date = if let Some(cached) = - dirstate_node.cached_directory_mtime()? - { - cached.likely_equal(truncated) - } else { - false - }; - if !is_up_to_date { - let hg_path = dirstate_node - .full_path_borrowed(self.dmap.on_disk)? - .detach_from_tree(); - self.new_cachable_directories - .lock() - .unwrap() - .push((hg_path, truncated)) - } - } + if let Some(directory_mtime) = option { + directory_mtime + } else { + // The directory was modified too recently, + // don’t cache its `read_dir` results. + // + // 1. A change to this directory (direct child was + // added or removed) cause its mtime to be set + // (possibly truncated) to `directory_mtime` + // 2. This `status` algorithm calls `read_dir` + // 3. An other change is made to the same directory is + // made so that calling `read_dir` agin would give + // different results, but soon enough after 1. that + // the mtime stays the same + // + // On a system where the time resolution poor, this + // scenario is not unlikely if all three steps are caused + // by the same script. + return Ok(()); } + } else { + // OS/libc does not support mtime? + return Ok(()); + }; + // We’ve observed (through `status_start`) that time has + // “progressed” since `directory_mtime`, so any further + // change to this directory is extremely likely to cause a + // different mtime. + // + // Having the same mtime again is not entirely impossible + // since the system clock is not monotonous. It could jump + // backward to some point before `directory_mtime`, then a + // directory change could potentially happen during exactly + // the wrong tick. + // + // We deem this scenario (unlike the previous one) to be + // unlikely enough in practice. + + let is_up_to_date = + if let Some(cached) = dirstate_node.cached_directory_mtime()? { + cached.likely_equal(directory_mtime) + } else { + false + }; + if !is_up_to_date { + let hg_path = dirstate_node + .full_path_borrowed(self.dmap.on_disk)? + .detach_from_tree(); + self.new_cachable_directories + .lock() + .unwrap() + .push((hg_path, directory_mtime)) } Ok(()) } @@ -505,7 +592,6 @@ impl<'a, 'tree, 'on_disk> StatusCommon<' let entry = dirstate_node .entry()? .expect("handle_normal_file called with entry-less node"); - let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?; let mode_changed = || self.options.check_exec && entry.mode_changed(fs_metadata); let size = entry.size(); @@ -513,43 +599,31 @@ impl<'a, 'tree, 'on_disk> StatusCommon<' if size >= 0 && size_changed && fs_metadata.file_type().is_symlink() { // issue6456: Size returned may be longer due to encryption // on EXT-4 fscrypt. TODO maybe only do it on EXT4? - self.outcome - .lock() - .unwrap() - .unsure - .push(hg_path.detach_from_tree()) + self.push_outcome(Outcome::Unsure, dirstate_node)? } else if dirstate_node.has_copy_source() || entry.is_from_other_parent() || (size >= 0 && (size_changed || mode_changed())) { - self.outcome - .lock() - .unwrap() - .modified - .push(hg_path.detach_from_tree()) + self.push_outcome(Outcome::Modified, dirstate_node)? } else { let mtime_looks_clean; if let Some(dirstate_mtime) = entry.truncated_mtime() { let fs_mtime = TruncatedTimestamp::for_mtime_of(fs_metadata) .expect("OS/libc does not support mtime?"); + // There might be a change in the future if for example the + // internal clock become off while process run, but this is a + // case where the issues the user would face + // would be a lot worse and there is nothing we + // can really do. mtime_looks_clean = fs_mtime.likely_equal(dirstate_mtime) - && !fs_mtime.likely_equal(self.options.last_normal_time) } else { // No mtime in the dirstate entry mtime_looks_clean = false }; if !mtime_looks_clean { - self.outcome - .lock() - .unwrap() - .unsure - .push(hg_path.detach_from_tree()) + self.push_outcome(Outcome::Unsure, dirstate_node)? } else if self.options.list_clean { - self.outcome - .lock() - .unwrap() - .clean - .push(hg_path.detach_from_tree()) + self.push_outcome(Outcome::Clean, dirstate_node)? } } Ok(()) @@ -561,10 +635,7 @@ impl<'a, 'tree, 'on_disk> StatusCommon<' dirstate_node: NodeRef<'tree, 'on_disk>, ) -> Result<(), DirstateV2ParseError> { self.check_for_outdated_directory_cache(&dirstate_node)?; - self.mark_removed_or_deleted_if_file( - &dirstate_node.full_path_borrowed(self.dmap.on_disk)?, - dirstate_node.state()?, - ); + self.mark_removed_or_deleted_if_file(&dirstate_node)?; dirstate_node .children(self.dmap.on_disk)? .par_iter() @@ -578,26 +649,19 @@ impl<'a, 'tree, 'on_disk> StatusCommon<' /// Does nothing on a "directory" node fn mark_removed_or_deleted_if_file( &self, - hg_path: &BorrowedPath<'tree, 'on_disk>, - dirstate_node_state: Option, - ) { - if let Some(state) = dirstate_node_state { - if self.matcher.matches(hg_path) { + dirstate_node: &NodeRef<'tree, 'on_disk>, + ) -> Result<(), DirstateV2ParseError> { + if let Some(state) = dirstate_node.state()? { + let path = dirstate_node.full_path(self.dmap.on_disk)?; + if self.matcher.matches(path) { if let EntryState::Removed = state { - self.outcome - .lock() - .unwrap() - .removed - .push(hg_path.detach_from_tree()) + self.push_outcome(Outcome::Removed, dirstate_node)? } else { - self.outcome - .lock() - .unwrap() - .deleted - .push(hg_path.detach_from_tree()) + self.push_outcome(Outcome::Deleted, &dirstate_node)? } } } + Ok(()) } /// Something in the filesystem has no corresponding dirstate node @@ -675,19 +739,17 @@ impl<'a, 'tree, 'on_disk> StatusCommon<' let is_ignored = has_ignored_ancestor || (self.ignore_fn)(&hg_path); if is_ignored { if self.options.list_ignored { - self.outcome - .lock() - .unwrap() - .ignored - .push(hg_path.detach_from_tree()) + self.push_outcome_without_copy_source( + Outcome::Ignored, + hg_path, + ) } } else { if self.options.list_unknown { - self.outcome - .lock() - .unwrap() - .unknown - .push(hg_path.detach_from_tree()) + self.push_outcome_without_copy_source( + Outcome::Unknown, + hg_path, + ) } } is_ignored @@ -710,8 +772,11 @@ impl DirEntry { /// * Elsewhere, we’re listing the content of a sub-repo. Return an empty /// list instead. fn read_dir(path: &Path, is_at_repo_root: bool) -> io::Result> { + // `read_dir` returns a "not found" error for the empty path + let at_cwd = path == Path::new(""); + let read_dir_path = if at_cwd { Path::new(".") } else { path }; let mut results = Vec::new(); - for entry in path.read_dir()? { + for entry in read_dir_path.read_dir()? { let entry = entry?; let metadata = match entry.metadata() { Ok(v) => v, @@ -724,9 +789,9 @@ impl DirEntry { } } }; - let name = get_bytes_from_os_string(entry.file_name()); + let file_name = entry.file_name(); // FIXME don't do this when cached - if name == b".hg" { + if file_name == ".hg" { if is_at_repo_root { // Skip the repo’s own .hg (might be a symlink) continue; @@ -736,9 +801,15 @@ impl DirEntry { return Ok(Vec::new()); } } + let full_path = if at_cwd { + file_name.clone().into() + } else { + entry.path() + }; + let base_name = get_bytes_from_os_string(file_name).into(); results.push(DirEntry { - base_name: name.into(), - full_path: entry.path(), + base_name, + full_path, metadata, }) } diff --git a/rust/hg-core/src/errors.rs b/rust/hg-core/src/errors.rs --- a/rust/hg-core/src/errors.rs +++ b/rust/hg-core/src/errors.rs @@ -151,6 +151,8 @@ pub trait IoResultExt { /// Converts a `Result` with `std::io::Error` into one with `HgError`. fn when_reading_file(self, path: &std::path::Path) -> Result; + fn when_writing_file(self, path: &std::path::Path) -> Result; + fn with_context( self, context: impl FnOnce() -> IoErrorContext, @@ -162,6 +164,10 @@ impl IoResultExt for std::io::Resu self.with_context(|| IoErrorContext::ReadingFile(path.to_owned())) } + fn when_writing_file(self, path: &std::path::Path) -> Result { + self.with_context(|| IoErrorContext::WritingFile(path.to_owned())) + } + fn with_context( self, context: impl FnOnce() -> IoErrorContext, diff --git a/rust/hg-core/src/lib.rs b/rust/hg-core/src/lib.rs --- a/rust/hg-core/src/lib.rs +++ b/rust/hg-core/src/lib.rs @@ -7,7 +7,7 @@ mod ancestors; pub mod dagops; pub mod errors; -pub use ancestors::{AncestorsIterator, LazyAncestors, MissingAncestors}; +pub use ancestors::{AncestorsIterator, MissingAncestors}; pub mod dirstate; pub mod dirstate_tree; pub mod discovery; @@ -29,6 +29,7 @@ pub mod repo; pub mod revlog; pub use revlog::*; pub mod config; +pub mod lock; pub mod logging; pub mod operations; pub mod revset; diff --git a/rust/hg-core/src/lock.rs b/rust/hg-core/src/lock.rs new file mode 100644 --- /dev/null +++ b/rust/hg-core/src/lock.rs @@ -0,0 +1,187 @@ +//! Filesystem-based locks for local repositories + +use crate::errors::HgError; +use crate::errors::HgResultExt; +use crate::utils::StrExt; +use crate::vfs::Vfs; +use std::io; +use std::io::ErrorKind; + +#[derive(derive_more::From)] +pub enum LockError { + AlreadyHeld, + #[from] + Other(HgError), +} + +/// Try to call `f` with the lock acquired, without waiting. +/// +/// If the lock is aready held, `f` is not called and `LockError::AlreadyHeld` +/// is returned. `LockError::Io` is returned for any unexpected I/O error +/// accessing the lock file, including for removing it after `f` was called. +/// The return value of `f` is dropped in that case. If all is successful, the +/// return value of `f` is forwarded. +pub fn try_with_lock_no_wait( + hg_vfs: Vfs, + lock_filename: &str, + f: impl FnOnce() -> R, +) -> Result { + let our_lock_data = &*OUR_LOCK_DATA; + for _retry in 0..5 { + match make_lock(hg_vfs, lock_filename, our_lock_data) { + Ok(()) => { + let result = f(); + unlock(hg_vfs, lock_filename)?; + return Ok(result); + } + Err(HgError::IoError { error, .. }) + if error.kind() == ErrorKind::AlreadyExists => + { + let lock_data = read_lock(hg_vfs, lock_filename)?; + if lock_data.is_none() { + // Lock was apparently just released, retry acquiring it + continue; + } + if !lock_should_be_broken(&lock_data) { + return Err(LockError::AlreadyHeld); + } + // The lock file is left over from a process not running + // anymore. Break it, but with another lock to + // avoid a race. + break_lock(hg_vfs, lock_filename)?; + + // Retry acquiring + } + Err(error) => Err(error)?, + } + } + Err(LockError::AlreadyHeld) +} + +fn break_lock(hg_vfs: Vfs, lock_filename: &str) -> Result<(), LockError> { + try_with_lock_no_wait(hg_vfs, &format!("{}.break", lock_filename), || { + // Check again in case some other process broke and + // acquired the lock in the meantime + let lock_data = read_lock(hg_vfs, lock_filename)?; + if !lock_should_be_broken(&lock_data) { + return Err(LockError::AlreadyHeld); + } + Ok(hg_vfs.remove_file(lock_filename)?) + })? +} + +#[cfg(unix)] +fn make_lock( + hg_vfs: Vfs, + lock_filename: &str, + data: &str, +) -> Result<(), HgError> { + // Use a symbolic link because creating it is atomic. + // The link’s "target" contains data not representing any path. + let fake_symlink_target = data; + hg_vfs.create_symlink(lock_filename, fake_symlink_target) +} + +fn read_lock( + hg_vfs: Vfs, + lock_filename: &str, +) -> Result, HgError> { + let link_target = + hg_vfs.read_link(lock_filename).io_not_found_as_none()?; + if let Some(target) = link_target { + let data = target + .into_os_string() + .into_string() + .map_err(|_| HgError::corrupted("non-UTF-8 lock data"))?; + Ok(Some(data)) + } else { + Ok(None) + } +} + +fn unlock(hg_vfs: Vfs, lock_filename: &str) -> Result<(), HgError> { + hg_vfs.remove_file(lock_filename) +} + +/// Return whether the process that is/was holding the lock is known not to be +/// running anymore. +fn lock_should_be_broken(data: &Option) -> bool { + (|| -> Option { + let (prefix, pid) = data.as_ref()?.split_2(':')?; + if prefix != &*LOCK_PREFIX { + return Some(false); + } + let process_is_running; + + #[cfg(unix)] + { + let pid: libc::pid_t = pid.parse().ok()?; + unsafe { + let signal = 0; // Test if we could send a signal, without sending + let result = libc::kill(pid, signal); + if result == 0 { + process_is_running = true + } else { + let errno = + io::Error::last_os_error().raw_os_error().unwrap(); + process_is_running = errno != libc::ESRCH + } + } + } + + Some(!process_is_running) + })() + .unwrap_or(false) +} + +lazy_static::lazy_static! { + /// A string which is used to differentiate pid namespaces + /// + /// It's useful to detect "dead" processes and remove stale locks with + /// confidence. Typically it's just hostname. On modern linux, we include an + /// extra Linux-specific pid namespace identifier. + static ref LOCK_PREFIX: String = { + // Note: this must match the behavior of `_getlockprefix` in `mercurial/lock.py` + + /// Same as https://github.com/python/cpython/blob/v3.10.0/Modules/socketmodule.c#L5414 + const BUFFER_SIZE: usize = 1024; + let mut buffer = [0_i8; BUFFER_SIZE]; + let hostname_bytes = unsafe { + let result = libc::gethostname(buffer.as_mut_ptr(), BUFFER_SIZE); + if result != 0 { + panic!("gethostname: {}", io::Error::last_os_error()) + } + std::ffi::CStr::from_ptr(buffer.as_mut_ptr()).to_bytes() + }; + let hostname = + std::str::from_utf8(hostname_bytes).expect("non-UTF-8 hostname"); + + #[cfg(target_os = "linux")] + { + use std::os::linux::fs::MetadataExt; + match std::fs::metadata("/proc/self/ns/pid") { + Ok(meta) => { + return format!("{}/{:x}", hostname, meta.st_ino()) + } + Err(error) => { + // TODO: match on `error.kind()` when `NotADirectory` + // is available on all supported Rust versions: + // https://github.com/rust-lang/rust/issues/86442 + use libc::{ + ENOENT, // ErrorKind::NotFound + ENOTDIR, // ErrorKind::NotADirectory + EACCES, // ErrorKind::PermissionDenied + }; + match error.raw_os_error() { + Some(ENOENT) | Some(ENOTDIR) | Some(EACCES) => {} + _ => panic!("stat /proc/self/ns/pid: {}", error), + } + } + } + } + + hostname.to_owned() + }; + + static ref OUR_LOCK_DATA: String = format!("{}:{}", &*LOCK_PREFIX, std::process::id()); +} diff --git a/rust/hg-core/src/matchers.rs b/rust/hg-core/src/matchers.rs --- a/rust/hg-core/src/matchers.rs +++ b/rust/hg-core/src/matchers.rs @@ -22,6 +22,7 @@ use crate::{ PatternSyntax, }; +use crate::dirstate::status::IgnoreFnType; use crate::filepatterns::normalize_path_bytes; use std::borrow::ToOwned; use std::collections::HashSet; @@ -246,7 +247,7 @@ impl<'a> Matcher for FileMatcher<'a> { /// ``` pub struct IncludeMatcher<'a> { patterns: Vec, - match_fn: Box Fn(&'r HgPath) -> bool + 'a + Sync>, + match_fn: IgnoreFnType<'a>, /// Whether all the patterns match a prefix (i.e. recursively) prefix: bool, roots: HashSet, @@ -341,9 +342,9 @@ fn re_matcher( /// Returns the regex pattern and a function that matches an `HgPath` against /// said regex formed by the given ignore patterns. -fn build_regex_match( - ignore_patterns: &[IgnorePattern], -) -> PatternResult<(Vec, Box bool + Sync>)> { +fn build_regex_match<'a, 'b>( + ignore_patterns: &'a [IgnorePattern], +) -> PatternResult<(Vec, IgnoreFnType<'b>)> { let mut regexps = vec![]; let mut exact_set = HashSet::new(); @@ -365,10 +366,10 @@ fn build_regex_match( let func = move |filename: &HgPath| { exact_set.contains(filename) || matcher(filename) }; - Box::new(func) as Box bool + Sync> + Box::new(func) as IgnoreFnType } else { let func = move |filename: &HgPath| exact_set.contains(filename); - Box::new(func) as Box bool + Sync> + Box::new(func) as IgnoreFnType }; Ok((full_regex, func)) @@ -476,8 +477,8 @@ fn roots_dirs_and_parents( /// should be matched. fn build_match<'a, 'b>( ignore_patterns: Vec, -) -> PatternResult<(Vec, Box bool + 'b + Sync>)> { - let mut match_funcs: Vec bool + Sync>> = vec![]; +) -> PatternResult<(Vec, IgnoreFnType<'b>)> { + let mut match_funcs: Vec> = vec![]; // For debugging and printing let mut patterns = vec![]; @@ -560,14 +561,11 @@ fn build_match<'a, 'b>( /// Parses all "ignore" files with their recursive includes and returns a /// function that checks whether a given file (in the general sense) should be /// ignored. -pub fn get_ignore_function<'a>( +pub fn get_ignore_matcher<'a>( mut all_pattern_files: Vec, root_dir: &Path, inspect_pattern_bytes: &mut impl FnMut(&[u8]), -) -> PatternResult<( - Box Fn(&'r HgPath) -> bool + Sync + 'a>, - Vec, -)> { +) -> PatternResult<(IncludeMatcher<'a>, Vec)> { let mut all_patterns = vec![]; let mut all_warnings = vec![]; @@ -590,10 +588,25 @@ pub fn get_ignore_function<'a>( all_warnings.extend(warnings); } let matcher = IncludeMatcher::new(all_patterns)?; - Ok(( - Box::new(move |path: &HgPath| matcher.matches(path)), - all_warnings, - )) + Ok((matcher, all_warnings)) +} + +/// Parses all "ignore" files with their recursive includes and returns a +/// function that checks whether a given file (in the general sense) should be +/// ignored. +pub fn get_ignore_function<'a>( + all_pattern_files: Vec, + root_dir: &Path, + inspect_pattern_bytes: &mut impl FnMut(&[u8]), +) -> PatternResult<(IgnoreFnType<'a>, Vec)> { + let res = + get_ignore_matcher(all_pattern_files, root_dir, inspect_pattern_bytes); + res.map(|(matcher, all_warnings)| { + let res: IgnoreFnType<'a> = + Box::new(move |path: &HgPath| matcher.matches(path)); + + (res, all_warnings) + }) } impl<'a> IncludeMatcher<'a> { @@ -628,6 +641,10 @@ impl<'a> IncludeMatcher<'a> { .chain(self.parents.iter()); DirsChildrenMultiset::new(thing, Some(&self.parents)) } + + pub fn debug_get_patterns(&self) -> &[u8] { + self.patterns.as_ref() + } } impl<'a> Display for IncludeMatcher<'a> { diff --git a/rust/hg-core/src/operations/cat.rs b/rust/hg-core/src/operations/cat.rs --- a/rust/hg-core/src/operations/cat.rs +++ b/rust/hg-core/src/operations/cat.rs @@ -11,6 +11,9 @@ use crate::revlog::Node; use crate::utils::hg_path::HgPath; +use crate::errors::HgError; +use crate::manifest::Manifest; +use crate::manifest::ManifestEntry; use itertools::put_back; use itertools::PutBack; use std::cmp::Ordering; @@ -28,46 +31,43 @@ pub struct CatOutput<'a> { } // Find an item in an iterator over a sorted collection. -fn find_item<'a, 'b, 'c, D, I: Iterator>( - i: &mut PutBack, - needle: &'b HgPath, -) -> Option { +fn find_item<'a>( + i: &mut PutBack, HgError>>>, + needle: &HgPath, +) -> Result, HgError> { loop { match i.next() { - None => return None, - Some(val) => match needle.as_bytes().cmp(val.0.as_bytes()) { - Ordering::Less => { - i.put_back(val); - return None; + None => return Ok(None), + Some(result) => { + let entry = result?; + match needle.as_bytes().cmp(entry.path.as_bytes()) { + Ordering::Less => { + i.put_back(Ok(entry)); + return Ok(None); + } + Ordering::Greater => continue, + Ordering::Equal => return Ok(Some(entry.node_id()?)), } - Ordering::Greater => continue, - Ordering::Equal => return Some(val.1), - }, + } } } } -fn find_files_in_manifest< - 'manifest, - 'query, - Data, - Manifest: Iterator, - Query: Iterator, ->( - manifest: Manifest, - query: Query, -) -> (Vec<(&'query HgPath, Data)>, Vec<&'query HgPath>) { - let mut manifest = put_back(manifest); +fn find_files_in_manifest<'query>( + manifest: &Manifest, + query: impl Iterator, +) -> Result<(Vec<(&'query HgPath, Node)>, Vec<&'query HgPath>), HgError> { + let mut manifest = put_back(manifest.iter()); let mut res = vec![]; let mut missing = vec![]; for file in query { - match find_item(&mut manifest, file) { + match find_item(&mut manifest, file)? { None => missing.push(file), Some(item) => res.push((file, item)), } } - return (res, missing); + return Ok((res, missing)); } /// Output the given revision of files @@ -92,17 +92,16 @@ pub fn cat<'a>( files.sort_unstable(); let (found, missing) = find_files_in_manifest( - manifest.files_with_nodes(), + &manifest, files.into_iter().map(|f| f.as_ref()), - ); + )?; - for (file_path, node_bytes) in found { + for (file_path, file_node) in found { found_any = true; let file_log = repo.filelog(file_path)?; - let file_node = Node::from_hex_for_repo(node_bytes)?; results.push(( file_path, - file_log.data_for_node(file_node)?.into_data()?, + file_log.data_for_node(file_node)?.into_file_data()?, )); } diff --git a/rust/hg-core/src/operations/debugdata.rs b/rust/hg-core/src/operations/debugdata.rs --- a/rust/hg-core/src/operations/debugdata.rs +++ b/rust/hg-core/src/operations/debugdata.rs @@ -29,5 +29,5 @@ pub fn debug_data( let rev = crate::revset::resolve_rev_number_or_hex_prefix(revset, &revlog)?; let data = revlog.get_rev_data(rev)?; - Ok(data) + Ok(data.into_owned()) } diff --git a/rust/hg-core/src/operations/list_tracked_files.rs b/rust/hg-core/src/operations/list_tracked_files.rs --- a/rust/hg-core/src/operations/list_tracked_files.rs +++ b/rust/hg-core/src/operations/list_tracked_files.rs @@ -76,7 +76,7 @@ pub fn list_rev_tracked_files( pub struct FilesForRev(Manifest); impl FilesForRev { - pub fn iter(&self) -> impl Iterator { - self.0.files() + pub fn iter(&self) -> impl Iterator> { + self.0.iter().map(|entry| Ok(entry?.path)) } } diff --git a/rust/hg-core/src/repo.rs b/rust/hg-core/src/repo.rs --- a/rust/hg-core/src/repo.rs +++ b/rust/hg-core/src/repo.rs @@ -2,10 +2,12 @@ use crate::changelog::Changelog; use crate::config::{Config, ConfigError, ConfigParseError}; use crate::dirstate::DirstateParents; use crate::dirstate_tree::dirstate_map::DirstateMap; +use crate::dirstate_tree::on_disk::Docket as DirstateDocket; use crate::dirstate_tree::owning::OwningDirstateMap; -use crate::errors::HgError; use crate::errors::HgResultExt; +use crate::errors::{HgError, IoResultExt}; use crate::exit_codes; +use crate::lock::{try_with_lock_no_wait, LockError}; use crate::manifest::{Manifest, Manifestlog}; use crate::revlog::filelog::Filelog; use crate::revlog::revlog::RevlogError; @@ -15,8 +17,11 @@ use crate::utils::SliceExt; use crate::vfs::{is_dir, is_file, Vfs}; use crate::{requirements, NodePrefix}; use crate::{DirstateError, Revision}; -use std::cell::{Cell, Ref, RefCell, RefMut}; +use std::cell::{Ref, RefCell, RefMut}; use std::collections::HashSet; +use std::io::Seek; +use std::io::SeekFrom; +use std::io::Write as IoWrite; use std::path::{Path, PathBuf}; /// A repository on disk @@ -26,8 +31,8 @@ pub struct Repo { store: PathBuf, requirements: HashSet, config: Config, - // None means not known/initialized yet - dirstate_parents: Cell>, + dirstate_parents: LazyCell, + dirstate_data_file_uuid: LazyCell>, HgError>, dirstate_map: LazyCell, changelog: LazyCell, manifestlog: LazyCell, @@ -202,7 +207,10 @@ impl Repo { store: store_path, dot_hg, config: repo_config, - dirstate_parents: Cell::new(None), + dirstate_parents: LazyCell::new(Self::read_dirstate_parents), + dirstate_data_file_uuid: LazyCell::new( + Self::read_dirstate_data_file_uuid, + ), dirstate_map: LazyCell::new(Self::new_dirstate_map), changelog: LazyCell::new(Changelog::open), manifestlog: LazyCell::new(Manifestlog::open), @@ -243,11 +251,26 @@ impl Repo { } } + pub fn try_with_wlock_no_wait( + &self, + f: impl FnOnce() -> R, + ) -> Result { + try_with_lock_no_wait(self.hg_vfs(), "wlock", f) + } + pub fn has_dirstate_v2(&self) -> bool { self.requirements .contains(requirements::DIRSTATE_V2_REQUIREMENT) } + pub fn has_sparse(&self) -> bool { + self.requirements.contains(requirements::SPARSE_REQUIREMENT) + } + + pub fn has_narrow(&self) -> bool { + self.requirements.contains(requirements::NARROW_REQUIREMENT) + } + fn dirstate_file_contents(&self) -> Result, HgError> { Ok(self .hg_vfs() @@ -257,32 +280,64 @@ impl Repo { } pub fn dirstate_parents(&self) -> Result { - if let Some(parents) = self.dirstate_parents.get() { - return Ok(parents); - } + Ok(*self.dirstate_parents.get_or_init(self)?) + } + + fn read_dirstate_parents(&self) -> Result { let dirstate = self.dirstate_file_contents()?; let parents = if dirstate.is_empty() { + if self.has_dirstate_v2() { + self.dirstate_data_file_uuid.set(None); + } DirstateParents::NULL } else if self.has_dirstate_v2() { - crate::dirstate_tree::on_disk::read_docket(&dirstate)?.parents() + let docket = + crate::dirstate_tree::on_disk::read_docket(&dirstate)?; + self.dirstate_data_file_uuid + .set(Some(docket.uuid.to_owned())); + docket.parents() } else { crate::dirstate::parsers::parse_dirstate_parents(&dirstate)? .clone() }; - self.dirstate_parents.set(Some(parents)); + self.dirstate_parents.set(parents); Ok(parents) } + fn read_dirstate_data_file_uuid( + &self, + ) -> Result>, HgError> { + assert!( + self.has_dirstate_v2(), + "accessing dirstate data file ID without dirstate-v2" + ); + let dirstate = self.dirstate_file_contents()?; + if dirstate.is_empty() { + self.dirstate_parents.set(DirstateParents::NULL); + Ok(None) + } else { + let docket = + crate::dirstate_tree::on_disk::read_docket(&dirstate)?; + self.dirstate_parents.set(docket.parents()); + Ok(Some(docket.uuid.to_owned())) + } + } + fn new_dirstate_map(&self) -> Result { let dirstate_file_contents = self.dirstate_file_contents()?; if dirstate_file_contents.is_empty() { - self.dirstate_parents.set(Some(DirstateParents::NULL)); + self.dirstate_parents.set(DirstateParents::NULL); + if self.has_dirstate_v2() { + self.dirstate_data_file_uuid.set(None); + } Ok(OwningDirstateMap::new_empty(Vec::new())) } else if self.has_dirstate_v2() { let docket = crate::dirstate_tree::on_disk::read_docket( &dirstate_file_contents, )?; - self.dirstate_parents.set(Some(docket.parents())); + self.dirstate_parents.set(docket.parents()); + self.dirstate_data_file_uuid + .set(Some(docket.uuid.to_owned())); let data_size = docket.data_size(); let metadata = docket.tree_metadata(); let mut map = if let Some(data_mmap) = self @@ -302,7 +357,7 @@ impl Repo { let (on_disk, placeholder) = map.get_pair_mut(); let (inner, parents) = DirstateMap::new_v1(on_disk)?; self.dirstate_parents - .set(Some(parents.unwrap_or(DirstateParents::NULL))); + .set(parents.unwrap_or(DirstateParents::NULL)); *placeholder = inner; Ok(map) } @@ -362,9 +417,81 @@ impl Repo { ) } + pub fn has_subrepos(&self) -> Result { + if let Some(entry) = self.dirstate_map()?.get(HgPath::new(".hgsub"))? { + Ok(entry.state().is_tracked()) + } else { + Ok(false) + } + } + pub fn filelog(&self, path: &HgPath) -> Result { Filelog::open(self, path) } + + /// Write to disk any updates that were made through `dirstate_map_mut`. + /// + /// The "wlock" must be held while calling this. + /// See for example `try_with_wlock_no_wait`. + /// + /// TODO: have a `WritableRepo` type only accessible while holding the + /// lock? + pub fn write_dirstate(&self) -> Result<(), DirstateError> { + let map = self.dirstate_map()?; + // TODO: Maintain a `DirstateMap::dirty` flag, and return early here if + // it’s unset + let parents = self.dirstate_parents()?; + let packed_dirstate = if self.has_dirstate_v2() { + let uuid = self.dirstate_data_file_uuid.get_or_init(self)?; + let mut uuid = uuid.as_ref(); + let can_append = uuid.is_some(); + let (data, tree_metadata, append) = map.pack_v2(can_append)?; + if !append { + uuid = None + } + let uuid = if let Some(uuid) = uuid { + std::str::from_utf8(uuid) + .map_err(|_| { + HgError::corrupted("non-UTF-8 dirstate data file ID") + })? + .to_owned() + } else { + DirstateDocket::new_uid() + }; + let data_filename = format!("dirstate.{}", uuid); + let data_filename = self.hg_vfs().join(data_filename); + let mut options = std::fs::OpenOptions::new(); + if append { + options.append(true); + } else { + options.write(true).create_new(true); + } + let data_size = (|| { + // TODO: loop and try another random ID if !append and this + // returns `ErrorKind::AlreadyExists`? Collision chance of two + // random IDs is one in 2**32 + let mut file = options.open(&data_filename)?; + file.write_all(&data)?; + file.flush()?; + // TODO: use https://doc.rust-lang.org/std/io/trait.Seek.html#method.stream_position when we require Rust 1.51+ + file.seek(SeekFrom::Current(0)) + })() + .when_writing_file(&data_filename)?; + DirstateDocket::serialize( + parents, + tree_metadata, + data_size, + uuid.as_bytes(), + ) + .map_err(|_: std::num::TryFromIntError| { + HgError::corrupted("overflow in dirstate docket serialization") + })? + } else { + map.pack_v1(parents)? + }; + self.hg_vfs().atomic_write("dirstate", &packed_dirstate)?; + Ok(()) + } } /// Lazily-initialized component of `Repo` with interior mutability @@ -386,6 +513,10 @@ impl LazyCell { } } + fn set(&self, value: T) { + *self.value.borrow_mut() = Some(value) + } + fn get_or_init(&self, repo: &Repo) -> Result, E> { let mut borrowed = self.value.borrow(); if borrowed.is_none() { @@ -399,7 +530,7 @@ impl LazyCell { Ok(Ref::map(borrowed, |option| option.as_ref().unwrap())) } - pub fn get_mut_or_init(&self, repo: &Repo) -> Result, E> { + fn get_mut_or_init(&self, repo: &Repo) -> Result, E> { let mut borrowed = self.value.borrow_mut(); if borrowed.is_none() { *borrowed = Some((self.init)(repo)?); diff --git a/rust/hg-core/src/requirements.rs b/rust/hg-core/src/requirements.rs --- a/rust/hg-core/src/requirements.rs +++ b/rust/hg-core/src/requirements.rs @@ -88,6 +88,10 @@ const SUPPORTED: &[&str] = &[ // When it starts writing to the repository, it’ll need to either keep the // persistent nodemap up to date or remove this entry: NODEMAP_REQUIREMENT, + // Not all commands support `sparse` and `narrow`. The commands that do + // not should opt out by checking `has_sparse` and `has_narrow`. + SPARSE_REQUIREMENT, + NARROW_REQUIREMENT, ]; // Copied from mercurial/requirements.py: diff --git a/rust/hg-core/src/revlog/changelog.rs b/rust/hg-core/src/revlog/changelog.rs --- a/rust/hg-core/src/revlog/changelog.rs +++ b/rust/hg-core/src/revlog/changelog.rs @@ -22,7 +22,7 @@ impl Changelog { pub fn data_for_node( &self, node: NodePrefix, - ) -> Result { + ) -> Result { let rev = self.revlog.rev_from_node(node)?; self.data_for_rev(rev) } @@ -31,9 +31,9 @@ impl Changelog { pub fn data_for_rev( &self, rev: Revision, - ) -> Result { - let bytes = self.revlog.get_rev_data(rev)?; - Ok(ChangelogEntry { bytes }) + ) -> Result { + let bytes = self.revlog.get_rev_data(rev)?.into_owned(); + Ok(ChangelogRevisionData { bytes }) } pub fn node_from_rev(&self, rev: Revision) -> Option<&Node> { @@ -43,12 +43,12 @@ impl Changelog { /// `Changelog` entry which knows how to interpret the `changelog` data bytes. #[derive(Debug)] -pub struct ChangelogEntry { +pub struct ChangelogRevisionData { /// The data bytes of the `changelog` entry. bytes: Vec, } -impl ChangelogEntry { +impl ChangelogRevisionData { /// Return an iterator over the lines of the entry. pub fn lines(&self) -> impl Iterator { self.bytes diff --git a/rust/hg-core/src/revlog/filelog.rs b/rust/hg-core/src/revlog/filelog.rs --- a/rust/hg-core/src/revlog/filelog.rs +++ b/rust/hg-core/src/revlog/filelog.rs @@ -1,6 +1,7 @@ use crate::errors::HgError; use crate::repo::Repo; use crate::revlog::path_encode::path_encode; +use crate::revlog::revlog::RevlogEntry; use crate::revlog::revlog::{Revlog, RevlogError}; use crate::revlog::NodePrefix; use crate::revlog::Revision; @@ -23,24 +24,43 @@ impl Filelog { Ok(Self { revlog }) } - /// The given node ID is that of the file as found in a manifest, not of a + /// The given node ID is that of the file as found in a filelog, not of a /// changeset. pub fn data_for_node( &self, file_node: impl Into, + ) -> Result { + let file_rev = self.revlog.rev_from_node(file_node.into())?; + self.data_for_rev(file_rev) + } + + /// The given revision is that of the file as found in a filelog, not of a + /// changeset. + pub fn data_for_rev( + &self, + file_rev: Revision, + ) -> Result { + let data: Vec = self.revlog.get_rev_data(file_rev)?.into_owned(); + Ok(FilelogRevisionData(data.into())) + } + + /// The given node ID is that of the file as found in a filelog, not of a + /// changeset. + pub fn entry_for_node( + &self, + file_node: impl Into, ) -> Result { let file_rev = self.revlog.rev_from_node(file_node.into())?; - self.data_for_rev(file_rev) + self.entry_for_rev(file_rev) } - /// The given revision is that of the file as found in a manifest, not of a + /// The given revision is that of the file as found in a filelog, not of a /// changeset. - pub fn data_for_rev( + pub fn entry_for_rev( &self, file_rev: Revision, ) -> Result { - let data: Vec = self.revlog.get_rev_data(file_rev)?; - Ok(FilelogEntry(data.into())) + Ok(FilelogEntry(self.revlog.get_entry(file_rev)?)) } } @@ -50,9 +70,101 @@ fn store_path(hg_path: &HgPath, suffix: get_path_from_bytes(&encoded_bytes).into() } -pub struct FilelogEntry(Vec); +pub struct FilelogEntry<'a>(RevlogEntry<'a>); + +impl FilelogEntry<'_> { + /// `self.data()` can be expensive, with decompression and delta + /// resolution. + /// + /// *Without* paying this cost, based on revlog index information + /// including `RevlogEntry::uncompressed_len`: + /// + /// * Returns `true` if the length that `self.data().file_data().len()` + /// would return is definitely **not equal** to `other_len`. + /// * Returns `false` if available information is inconclusive. + pub fn file_data_len_not_equal_to(&self, other_len: u64) -> bool { + // Relevant code that implement this behavior in Python code: + // basefilectx.cmp, filelog.size, storageutil.filerevisioncopied, + // revlog.size, revlog.rawsize + + // Let’s call `file_data_len` what would be returned by + // `self.data().file_data().len()`. + + if self.0.is_cencored() { + let file_data_len = 0; + return other_len != file_data_len; + } + + if self.0.has_length_affecting_flag_processor() { + // We can’t conclude anything about `file_data_len`. + return false; + } -impl FilelogEntry { + // Revlog revisions (usually) have metadata for the size of + // their data after decompression and delta resolution + // as would be returned by `Revlog::get_rev_data`. + // + // For filelogs this is the file’s contents preceded by an optional + // metadata block. + let uncompressed_len = if let Some(l) = self.0.uncompressed_len() { + l as u64 + } else { + // The field was set to -1, the actual uncompressed len is unknown. + // We need to decompress to say more. + return false; + }; + // `uncompressed_len = file_data_len + optional_metadata_len`, + // so `file_data_len <= uncompressed_len`. + if uncompressed_len < other_len { + // Transitively, `file_data_len < other_len`. + // So `other_len != file_data_len` definitely. + return true; + } + + if uncompressed_len == other_len + 4 { + // It’s possible that `file_data_len == other_len` with an empty + // metadata block (2 start marker bytes + 2 end marker bytes). + // This happens when there wouldn’t otherwise be metadata, but + // the first 2 bytes of file data happen to match a start marker + // and would be ambiguous. + return false; + } + + if !self.0.has_p1() { + // There may or may not be copy metadata, so we can’t deduce more + // about `file_data_len` without computing file data. + return false; + } + + // Filelog ancestry is not meaningful in the way changelog ancestry is. + // It only provides hints to delta generation. + // p1 and p2 are set to null when making a copy or rename since + // contents are likely unrelatedto what might have previously existed + // at the destination path. + // + // Conversely, since here p1 is non-null, there is no copy metadata. + // Note that this reasoning may be invalidated in the presence of + // merges made by some previous versions of Mercurial that + // swapped p1 and p2. See + // and `tests/test-issue6528.t`. + // + // Since copy metadata is currently the only kind of metadata + // kept in revlog data of filelogs, + // this `FilelogEntry` does not have such metadata: + let file_data_len = uncompressed_len; + + return file_data_len != other_len; + } + + pub fn data(&self) -> Result { + Ok(FilelogRevisionData(self.0.data()?.into_owned())) + } +} + +/// The data for one revision in a filelog, uncompressed and delta-resolved. +pub struct FilelogRevisionData(Vec); + +impl FilelogRevisionData { /// Split into metadata and data pub fn split(&self) -> Result<(Option<&[u8]>, &[u8]), HgError> { const DELIMITER: &[u8; 2] = &[b'\x01', b'\n']; @@ -71,14 +183,14 @@ impl FilelogEntry { } /// Returns the file contents at this revision, stripped of any metadata - pub fn data(&self) -> Result<&[u8], HgError> { + pub fn file_data(&self) -> Result<&[u8], HgError> { let (_metadata, data) = self.split()?; Ok(data) } /// Consume the entry, and convert it into data, discarding any metadata, /// if present. - pub fn into_data(self) -> Result, HgError> { + pub fn into_file_data(self) -> Result, HgError> { if let (Some(_metadata), data) = self.split()? { Ok(data.to_owned()) } else { diff --git a/rust/hg-core/src/revlog/index.rs b/rust/hg-core/src/revlog/index.rs --- a/rust/hg-core/src/revlog/index.rs +++ b/rust/hg-core/src/revlog/index.rs @@ -9,12 +9,82 @@ use crate::revlog::{Revision, NULL_REVIS pub const INDEX_ENTRY_SIZE: usize = 64; +pub struct IndexHeader { + header_bytes: [u8; 4], +} + +#[derive(Copy, Clone)] +pub struct IndexHeaderFlags { + flags: u16, +} + +/// Corresponds to the high bits of `_format_flags` in python +impl IndexHeaderFlags { + /// Corresponds to FLAG_INLINE_DATA in python + pub fn is_inline(self) -> bool { + return self.flags & 1 != 0; + } + /// Corresponds to FLAG_GENERALDELTA in python + pub fn uses_generaldelta(self) -> bool { + return self.flags & 2 != 0; + } +} + +/// Corresponds to the INDEX_HEADER structure, +/// which is parsed as a `header` variable in `_loadindex` in `revlog.py` +impl IndexHeader { + fn format_flags(&self) -> IndexHeaderFlags { + // No "unknown flags" check here, unlike in python. Maybe there should + // be. + return IndexHeaderFlags { + flags: BigEndian::read_u16(&self.header_bytes[0..2]), + }; + } + + /// The only revlog version currently supported by rhg. + const REVLOGV1: u16 = 1; + + /// Corresponds to `_format_version` in Python. + fn format_version(&self) -> u16 { + return BigEndian::read_u16(&self.header_bytes[2..4]); + } + + const EMPTY_INDEX_HEADER: IndexHeader = IndexHeader { + // We treat an empty file as a valid index with no entries. + // Here we make an arbitrary choice of what we assume the format of the + // index to be (V1, using generaldelta). + // This doesn't matter too much, since we're only doing read-only + // access. but the value corresponds to the `new_header` variable in + // `revlog.py`, `_loadindex` + header_bytes: [0, 3, 0, 1], + }; + + fn parse(index_bytes: &[u8]) -> Result { + if index_bytes.len() == 0 { + return Ok(IndexHeader::EMPTY_INDEX_HEADER); + } + if index_bytes.len() < 4 { + return Err(HgError::corrupted( + "corrupted revlog: can't read the index format header", + )); + } + return Ok(IndexHeader { + header_bytes: { + let bytes: [u8; 4] = + index_bytes[0..4].try_into().expect("impossible"); + bytes + }, + }); + } +} + /// A Revlog index pub struct Index { bytes: Box + Send>, /// Offsets of starts of index blocks. /// Only needed when the index is interleaved with data. offsets: Option>, + uses_generaldelta: bool, } impl Index { @@ -23,7 +93,20 @@ impl Index { pub fn new( bytes: Box + Send>, ) -> Result { - if is_inline(&bytes) { + let header = IndexHeader::parse(bytes.as_ref())?; + + if header.format_version() != IndexHeader::REVLOGV1 { + // A proper new version should have had a repo/store + // requirement. + return Err(HgError::corrupted("unsupported revlog version")); + } + + // This is only correct because we know version is REVLOGV1. + // In v2 we always use generaldelta, while in v0 we never use + // generaldelta. Similar for [is_inline] (it's only used in v1). + let uses_generaldelta = header.format_flags().uses_generaldelta(); + + if header.format_flags().is_inline() { let mut offset: usize = 0; let mut offsets = Vec::new(); @@ -35,13 +118,14 @@ impl Index { offset_override: None, }; - offset += INDEX_ENTRY_SIZE + entry.compressed_len(); + offset += INDEX_ENTRY_SIZE + entry.compressed_len() as usize; } if offset == bytes.len() { Ok(Self { bytes, offsets: Some(offsets), + uses_generaldelta, }) } else { Err(HgError::corrupted("unexpected inline revlog length") @@ -51,10 +135,15 @@ impl Index { Ok(Self { bytes, offsets: None, + uses_generaldelta, }) } } + pub fn uses_generaldelta(&self) -> bool { + self.uses_generaldelta + } + /// Value of the inline flag. pub fn is_inline(&self) -> bool { self.offsets.is_some() @@ -171,18 +260,22 @@ impl<'a> IndexEntry<'a> { } } + pub fn flags(&self) -> u16 { + BigEndian::read_u16(&self.bytes[6..=7]) + } + /// Return the compressed length of the data. - pub fn compressed_len(&self) -> usize { - BigEndian::read_u32(&self.bytes[8..=11]) as usize + pub fn compressed_len(&self) -> u32 { + BigEndian::read_u32(&self.bytes[8..=11]) } /// Return the uncompressed length of the data. - pub fn uncompressed_len(&self) -> usize { - BigEndian::read_u32(&self.bytes[12..=15]) as usize + pub fn uncompressed_len(&self) -> i32 { + BigEndian::read_i32(&self.bytes[12..=15]) } /// Return the revision upon which the data has been derived. - pub fn base_revision(&self) -> Revision { + pub fn base_revision_or_base_of_delta_chain(&self) -> Revision { // TODO Maybe return an Option when base_revision == rev? // Requires to add rev to IndexEntry @@ -206,17 +299,6 @@ impl<'a> IndexEntry<'a> { } } -/// Value of the inline flag. -pub fn is_inline(index_bytes: &[u8]) -> bool { - if index_bytes.len() < 4 { - return true; - } - match &index_bytes[0..=1] { - [0, 0] | [0, 2] => false, - _ => true, - } -} - #[cfg(test)] mod tests { use super::*; @@ -231,7 +313,7 @@ mod tests { offset: usize, compressed_len: usize, uncompressed_len: usize, - base_revision: Revision, + base_revision_or_base_of_delta_chain: Revision, } #[cfg(test)] @@ -245,7 +327,7 @@ mod tests { offset: 0, compressed_len: 0, uncompressed_len: 0, - base_revision: 0, + base_revision_or_base_of_delta_chain: 0, } } @@ -284,8 +366,11 @@ mod tests { self } - pub fn with_base_revision(&mut self, value: Revision) -> &mut Self { - self.base_revision = value; + pub fn with_base_revision_or_base_of_delta_chain( + &mut self, + value: Revision, + ) -> &mut Self { + self.base_revision_or_base_of_delta_chain = value; self } @@ -308,42 +393,67 @@ mod tests { bytes.extend(&[0u8; 2]); // Revision flags. 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.extend( + &self.base_revision_or_base_of_delta_chain.to_be_bytes(), + ); bytes } } + pub fn is_inline(index_bytes: &[u8]) -> bool { + IndexHeader::parse(index_bytes) + .expect("too short") + .format_flags() + .is_inline() + } + + pub fn uses_generaldelta(index_bytes: &[u8]) -> bool { + IndexHeader::parse(index_bytes) + .expect("too short") + .format_flags() + .uses_generaldelta() + } + + pub fn get_version(index_bytes: &[u8]) -> u16 { + IndexHeader::parse(index_bytes) + .expect("too short") + .format_version() + } + #[test] - fn is_not_inline_when_no_inline_flag_test() { + fn flags_when_no_inline_flag_test() { let bytes = IndexEntryBuilder::new() .is_first(true) .with_general_delta(false) .with_inline(false) .build(); - assert_eq!(is_inline(&bytes), false) + assert_eq!(is_inline(&bytes), false); + assert_eq!(uses_generaldelta(&bytes), false); } #[test] - fn is_inline_when_inline_flag_test() { + fn flags_when_inline_flag_test() { let bytes = IndexEntryBuilder::new() .is_first(true) .with_general_delta(false) .with_inline(true) .build(); - assert_eq!(is_inline(&bytes), true) + assert_eq!(is_inline(&bytes), true); + assert_eq!(uses_generaldelta(&bytes), false); } #[test] - fn is_inline_when_inline_and_generaldelta_flags_test() { + fn flags_when_inline_and_generaldelta_flags_test() { let bytes = IndexEntryBuilder::new() .is_first(true) .with_general_delta(true) .with_inline(true) .build(); - assert_eq!(is_inline(&bytes), true) + assert_eq!(is_inline(&bytes), true); + assert_eq!(uses_generaldelta(&bytes), true); } #[test] @@ -391,14 +501,26 @@ mod tests { } #[test] - fn test_base_revision() { - let bytes = IndexEntryBuilder::new().with_base_revision(1).build(); + fn test_base_revision_or_base_of_delta_chain() { + let bytes = IndexEntryBuilder::new() + .with_base_revision_or_base_of_delta_chain(1) + .build(); let entry = IndexEntry { bytes: &bytes, offset_override: None, }; - assert_eq!(entry.base_revision(), 1) + assert_eq!(entry.base_revision_or_base_of_delta_chain(), 1) + } + + #[test] + fn version_test() { + let bytes = IndexEntryBuilder::new() + .is_first(true) + .with_version(1) + .build(); + + assert_eq!(get_version(&bytes), 1) } } diff --git a/rust/hg-core/src/revlog/manifest.rs b/rust/hg-core/src/revlog/manifest.rs --- a/rust/hg-core/src/revlog/manifest.rs +++ b/rust/hg-core/src/revlog/manifest.rs @@ -4,6 +4,7 @@ use crate::revlog::revlog::{Revlog, Revl use crate::revlog::Revision; use crate::revlog::{Node, NodePrefix}; use crate::utils::hg_path::HgPath; +use crate::utils::SliceExt; /// A specialized `Revlog` to work with `manifest` data format. pub struct Manifestlog { @@ -43,7 +44,7 @@ impl Manifestlog { &self, rev: Revision, ) -> Result { - let bytes = self.revlog.get_rev_data(rev)?; + let bytes = self.revlog.get_rev_data(rev)?.into_owned(); Ok(Manifest { bytes }) } } @@ -51,51 +52,142 @@ impl Manifestlog { /// `Manifestlog` entry which knows how to interpret the `manifest` data bytes. #[derive(Debug)] pub struct Manifest { + /// Format for a manifest: flat sequence of variable-size entries, + /// sorted by path, each as: + /// + /// ```text + /// \0 \n + /// ``` + /// + /// The last entry is also terminated by a newline character. + /// Flags is one of `b""` (the empty string), `b"x"`, `b"l"`, or `b"t"`. bytes: Vec, } impl Manifest { - /// Return an iterator over the lines of the entry. - pub fn lines(&self) -> impl Iterator { + pub fn iter( + &self, + ) -> impl Iterator> { self.bytes .split(|b| b == &b'\n') .filter(|line| !line.is_empty()) - } - - /// Return an iterator over the files of the entry. - pub fn files(&self) -> impl Iterator { - self.lines().filter(|line| !line.is_empty()).map(|line| { - let pos = line - .iter() - .position(|x| x == &b'\0') - .expect("manifest line should contain \\0"); - HgPath::new(&line[..pos]) - }) - } - - /// Return an iterator over the files of the entry. - pub fn files_with_nodes(&self) -> impl Iterator { - self.lines().filter(|line| !line.is_empty()).map(|line| { - let pos = line - .iter() - .position(|x| x == &b'\0') - .expect("manifest line should contain \\0"); - let hash_start = pos + 1; - let hash_end = hash_start + 40; - (HgPath::new(&line[..pos]), &line[hash_start..hash_end]) - }) + .map(ManifestEntry::from_raw) } /// If the given path is in this manifest, return its filelog node ID - pub fn find_file(&self, path: &HgPath) -> Result, HgError> { - // TODO: use binary search instead of linear scan. This may involve - // building (and caching) an index of the byte indicex of each manifest - // line. - for (manifest_path, node) in self.files_with_nodes() { - if manifest_path == path { - return Ok(Some(Node::from_hex_for_repo(node)?)); + pub fn find_by_path( + &self, + path: &HgPath, + ) -> Result, HgError> { + use std::cmp::Ordering::*; + let path = path.as_bytes(); + // Both boundaries of this `&[u8]` slice are always at the boundary of + // an entry + let mut bytes = &*self.bytes; + + // Binary search algorithm derived from `[T]::binary_search_by` + // + // except we don’t have a slice of entries. Instead we jump to the + // middle of the byte slice and look around for entry delimiters + // (newlines). + while let Some(entry_range) = Self::find_entry_near_middle_of(bytes)? { + let (entry_path, rest) = + ManifestEntry::split_path(&bytes[entry_range.clone()])?; + let cmp = entry_path.cmp(path); + if cmp == Less { + let after_newline = entry_range.end + 1; + bytes = &bytes[after_newline..]; + } else if cmp == Greater { + bytes = &bytes[..entry_range.start]; + } else { + return Ok(Some(ManifestEntry::from_path_and_rest( + entry_path, rest, + ))); } } Ok(None) } + + /// If there is at least one, return the byte range of an entry *excluding* + /// the final newline. + fn find_entry_near_middle_of( + bytes: &[u8], + ) -> Result>, HgError> { + let len = bytes.len(); + if len > 0 { + let middle = bytes.len() / 2; + // Integer division rounds down, so `middle < len`. + let (before, after) = bytes.split_at(middle); + let is_newline = |&byte: &u8| byte == b'\n'; + let entry_start = match before.iter().rposition(is_newline) { + Some(i) => i + 1, + None => 0, // We choose the first entry in `bytes` + }; + let entry_end = match after.iter().position(is_newline) { + Some(i) => { + // No `+ 1` here to exclude this newline from the range + middle + i + } + None => { + // In a well-formed manifest: + // + // * Since `len > 0`, `bytes` contains at least one entry + // * Every entry ends with a newline + // * Since `middle < len`, `after` contains at least the + // newline at the end of the last entry of `bytes`. + // + // We didn’t find a newline, so this manifest is not + // well-formed. + return Err(HgError::corrupted( + "manifest entry without \\n delimiter", + )); + } + }; + Ok(Some(entry_start..entry_end)) + } else { + // len == 0 + Ok(None) + } + } } + +/// `Manifestlog` entry which knows how to interpret the `manifest` data bytes. +#[derive(Debug)] +pub struct ManifestEntry<'manifest> { + pub path: &'manifest HgPath, + pub hex_node_id: &'manifest [u8], + + /// `Some` values are b'x', b'l', or 't' + pub flags: Option, +} + +impl<'a> ManifestEntry<'a> { + fn split_path(bytes: &[u8]) -> Result<(&[u8], &[u8]), HgError> { + bytes.split_2(b'\0').ok_or_else(|| { + HgError::corrupted("manifest entry without \\0 delimiter") + }) + } + + fn from_path_and_rest(path: &'a [u8], rest: &'a [u8]) -> Self { + let (hex_node_id, flags) = match rest.split_last() { + Some((&b'x', rest)) => (rest, Some(b'x')), + Some((&b'l', rest)) => (rest, Some(b'l')), + Some((&b't', rest)) => (rest, Some(b't')), + _ => (rest, None), + }; + Self { + path: HgPath::new(path), + hex_node_id, + flags, + } + } + + fn from_raw(bytes: &'a [u8]) -> Result { + let (path, rest) = Self::split_path(bytes)?; + Ok(Self::from_path_and_rest(path, rest)) + } + + pub fn node_id(&self) -> Result { + Node::from_hex_for_repo(self.hex_node_id) + } +} diff --git a/rust/hg-core/src/revlog/node.rs b/rust/hg-core/src/revlog/node.rs --- a/rust/hg-core/src/revlog/node.rs +++ b/rust/hg-core/src/revlog/node.rs @@ -174,6 +174,12 @@ impl Node { data: self.data, } } + + pub fn pad_to_256_bits(&self) -> [u8; 32] { + let mut bits = [0; 32]; + bits[..NODE_BYTES_LENGTH].copy_from_slice(&self.data); + bits + } } /// The beginning of a binary revision SHA. diff --git a/rust/hg-core/src/revlog/revlog.rs b/rust/hg-core/src/revlog/revlog.rs --- a/rust/hg-core/src/revlog/revlog.rs +++ b/rust/hg-core/src/revlog/revlog.rs @@ -1,9 +1,9 @@ use std::borrow::Cow; +use std::convert::TryFrom; use std::io::Read; use std::ops::Deref; use std::path::Path; -use byteorder::{BigEndian, ByteOrder}; use flate2::read::ZlibDecoder; use micro_timer::timed; use sha1::{Digest, Sha1}; @@ -20,6 +20,18 @@ use crate::repo::Repo; use crate::revlog::Revision; use crate::{Node, NULL_REVISION}; +const REVISION_FLAG_CENSORED: u16 = 1 << 15; +const REVISION_FLAG_ELLIPSIS: u16 = 1 << 14; +const REVISION_FLAG_EXTSTORED: u16 = 1 << 13; +const REVISION_FLAG_HASCOPIESINFO: u16 = 1 << 12; + +// Keep this in sync with REVIDX_KNOWN_FLAGS in +// mercurial/revlogutils/flagutil.py +const REVIDX_KNOWN_FLAGS: u16 = REVISION_FLAG_CENSORED + | REVISION_FLAG_ELLIPSIS + | REVISION_FLAG_EXTSTORED + | REVISION_FLAG_HASCOPIESINFO; + #[derive(derive_more::From)] pub enum RevlogError { InvalidRevision, @@ -40,9 +52,13 @@ impl From for RevlogError } } +fn corrupted() -> HgError { + HgError::corrupted("corrupted revlog") +} + impl RevlogError { fn corrupted() -> Self { - RevlogError::Other(HgError::corrupted("corrupted revlog")) + RevlogError::Other(corrupted()) } } @@ -74,13 +90,6 @@ impl Revlog { match repo.store_vfs().mmap_open_opt(&index_path)? { None => Index::new(Box::new(vec![])), Some(index_mmap) => { - let version = get_version(&index_mmap)?; - if version != 1 { - // A proper new version should have had a repo/store - // requirement. - return Err(HgError::corrupted("corrupted revlog")); - } - let index = Index::new(Box::new(index_mmap))?; Ok(index) } @@ -192,42 +201,14 @@ impl Revlog { /// retrieved as needed, and the deltas will be applied to the inital /// snapshot to rebuild the final data. #[timed] - pub fn get_rev_data(&self, rev: Revision) -> Result, RevlogError> { + pub fn get_rev_data( + &self, + rev: Revision, + ) -> Result, RevlogError> { if rev == NULL_REVISION { - return Ok(vec![]); + return Ok(Cow::Borrowed(&[])); }; - // Todo return -> Cow - let mut entry = self.get_entry(rev)?; - let mut delta_chain = vec![]; - while let Some(base_rev) = entry.base_rev { - delta_chain.push(entry); - entry = self - .get_entry(base_rev) - .map_err(|_| RevlogError::corrupted())?; - } - - // TODO do not look twice in the index - let index_entry = self - .index - .get_entry(rev) - .ok_or(RevlogError::InvalidRevision)?; - - let data: Vec = if delta_chain.is_empty() { - entry.data()?.into() - } else { - Revlog::build_data_from_deltas(entry, &delta_chain)? - }; - - if self.check_hash( - index_entry.p1(), - index_entry.p2(), - index_entry.hash().as_bytes(), - &data, - ) { - Ok(data) - } else { - Err(RevlogError::corrupted()) - } + Ok(self.get_entry(rev)?.data()?) } /// Check the hash of some given data against the recorded hash. @@ -258,13 +239,13 @@ impl Revlog { fn build_data_from_deltas( snapshot: RevlogEntry, deltas: &[RevlogEntry], - ) -> Result, RevlogError> { - let snapshot = snapshot.data()?; + ) -> Result, HgError> { + let snapshot = snapshot.data_chunk()?; let deltas = deltas .iter() .rev() - .map(RevlogEntry::data) - .collect::>, RevlogError>>()?; + .map(RevlogEntry::data_chunk) + .collect::, _>>()?; let patches: Vec<_> = deltas.iter().map(|d| patch::PatchList::new(d)).collect(); let patch = patch::fold_patch_lists(&patches); @@ -282,42 +263,67 @@ impl Revlog { } /// Get an entry of the revlog. - fn get_entry(&self, rev: Revision) -> Result { + pub fn get_entry( + &self, + rev: Revision, + ) -> Result { let index_entry = self .index .get_entry(rev) .ok_or(RevlogError::InvalidRevision)?; let start = index_entry.offset(); - let end = start + index_entry.compressed_len(); + let end = start + index_entry.compressed_len() as usize; let data = if self.index.is_inline() { self.index.data(start, end) } else { &self.data()[start..end] }; let entry = RevlogEntry { + revlog: self, rev, bytes: data, compressed_len: index_entry.compressed_len(), uncompressed_len: index_entry.uncompressed_len(), - base_rev: if index_entry.base_revision() == rev { + base_rev_or_base_of_delta_chain: if index_entry + .base_revision_or_base_of_delta_chain() + == rev + { None } else { - Some(index_entry.base_revision()) + Some(index_entry.base_revision_or_base_of_delta_chain()) }, + p1: index_entry.p1(), + p2: index_entry.p2(), + flags: index_entry.flags(), + hash: *index_entry.hash(), }; Ok(entry) } + + /// when resolving internal references within revlog, any errors + /// should be reported as corruption, instead of e.g. "invalid revision" + fn get_entry_internal( + &self, + rev: Revision, + ) -> Result { + return self.get_entry(rev).map_err(|_| corrupted()); + } } /// The revlog entry's bytes and the necessary informations to extract /// the entry's data. -#[derive(Debug)] +#[derive(Clone)] pub struct RevlogEntry<'a> { + revlog: &'a Revlog, rev: Revision, bytes: &'a [u8], - compressed_len: usize, - uncompressed_len: usize, - base_rev: Option, + compressed_len: u32, + uncompressed_len: i32, + base_rev_or_base_of_delta_chain: Option, + p1: Revision, + p2: Revision, + flags: u16, + hash: Node, } impl<'a> RevlogEntry<'a> { @@ -325,8 +331,65 @@ impl<'a> RevlogEntry<'a> { self.rev } + pub fn uncompressed_len(&self) -> Option { + u32::try_from(self.uncompressed_len).ok() + } + + pub fn has_p1(&self) -> bool { + self.p1 != NULL_REVISION + } + + pub fn is_cencored(&self) -> bool { + (self.flags & REVISION_FLAG_CENSORED) != 0 + } + + pub fn has_length_affecting_flag_processor(&self) -> bool { + // Relevant Python code: revlog.size() + // note: ELLIPSIS is known to not change the content + (self.flags & (REVIDX_KNOWN_FLAGS ^ REVISION_FLAG_ELLIPSIS)) != 0 + } + + /// The data for this entry, after resolving deltas if any. + pub fn data(&self) -> Result, HgError> { + let mut entry = self.clone(); + let mut delta_chain = vec![]; + + // The meaning of `base_rev_or_base_of_delta_chain` depends on + // generaldelta. See the doc on `ENTRY_DELTA_BASE` in + // `mercurial/revlogutils/constants.py` and the code in + // [_chaininfo] and in [index_deltachain]. + let uses_generaldelta = self.revlog.index.uses_generaldelta(); + while let Some(base_rev) = entry.base_rev_or_base_of_delta_chain { + let base_rev = if uses_generaldelta { + base_rev + } else { + entry.rev - 1 + }; + delta_chain.push(entry); + entry = self.revlog.get_entry_internal(base_rev)?; + } + + let data = if delta_chain.is_empty() { + entry.data_chunk()? + } else { + Revlog::build_data_from_deltas(entry, &delta_chain)?.into() + }; + + if self.revlog.check_hash( + self.p1, + self.p2, + self.hash.as_bytes(), + &data, + ) { + Ok(data) + } else { + Err(corrupted()) + } + } + /// Extract the data contained in the entry. - pub fn data(&self) -> Result, RevlogError> { + /// This may be a delta. (See `is_delta`.) + fn data_chunk(&self) -> Result, HgError> { if self.bytes.is_empty() { return Ok(Cow::Borrowed(&[])); } @@ -341,39 +404,37 @@ impl<'a> RevlogEntry<'a> { // zstd data. b'\x28' => Ok(Cow::Owned(self.uncompressed_zstd_data()?)), // A proper new format should have had a repo/store requirement. - _format_type => Err(RevlogError::corrupted()), + _format_type => Err(corrupted()), } } - fn uncompressed_zlib_data(&self) -> Result, RevlogError> { + fn uncompressed_zlib_data(&self) -> Result, HgError> { let mut decoder = ZlibDecoder::new(self.bytes); if self.is_delta() { - let mut buf = Vec::with_capacity(self.compressed_len); - decoder - .read_to_end(&mut buf) - .map_err(|_| RevlogError::corrupted())?; + let mut buf = Vec::with_capacity(self.compressed_len as usize); + decoder.read_to_end(&mut buf).map_err(|_| corrupted())?; Ok(buf) } else { - let mut buf = vec![0; self.uncompressed_len]; - decoder - .read_exact(&mut buf) - .map_err(|_| RevlogError::corrupted())?; + let cap = self.uncompressed_len.max(0) as usize; + let mut buf = vec![0; cap]; + decoder.read_exact(&mut buf).map_err(|_| corrupted())?; Ok(buf) } } - fn uncompressed_zstd_data(&self) -> Result, RevlogError> { + fn uncompressed_zstd_data(&self) -> Result, HgError> { if self.is_delta() { - let mut buf = Vec::with_capacity(self.compressed_len); + let mut buf = Vec::with_capacity(self.compressed_len as usize); zstd::stream::copy_decode(self.bytes, &mut buf) - .map_err(|_| RevlogError::corrupted())?; + .map_err(|_| corrupted())?; Ok(buf) } else { - let mut buf = vec![0; self.uncompressed_len]; + let cap = self.uncompressed_len.max(0) as usize; + let mut buf = vec![0; cap]; let len = zstd::block::decompress_to_buffer(self.bytes, &mut buf) - .map_err(|_| RevlogError::corrupted())?; - if len != self.uncompressed_len { - Err(RevlogError::corrupted()) + .map_err(|_| corrupted())?; + if len != self.uncompressed_len as usize { + Err(corrupted()) } else { Ok(buf) } @@ -383,23 +444,10 @@ impl<'a> RevlogEntry<'a> { /// Tell if the entry is a snapshot or a delta /// (influences on decompression). fn is_delta(&self) -> bool { - self.base_rev.is_some() + self.base_rev_or_base_of_delta_chain.is_some() } } -/// Format version of the revlog. -pub fn get_version(index_bytes: &[u8]) -> Result { - if index_bytes.len() == 0 { - return Ok(1); - }; - if index_bytes.len() < 4 { - return Err(HgError::corrupted( - "corrupted revlog: can't read the index format header", - )); - }; - Ok(BigEndian::read_u16(&index_bytes[2..=3])) -} - /// Calculate the hash of a revision given its data and its parents. fn hash( data: &[u8], @@ -418,20 +466,3 @@ fn hash( hasher.update(data); *hasher.finalize().as_ref() } - -#[cfg(test)] -mod tests { - use super::*; - - use super::super::index::IndexEntryBuilder; - - #[test] - fn version_test() { - let bytes = IndexEntryBuilder::new() - .is_first(true) - .with_version(1) - .build(); - - assert_eq!(get_version(&bytes).map_err(|_err| ()), Ok(1)) - } -} diff --git a/rust/hg-core/src/utils.rs b/rust/hg-core/src/utils.rs --- a/rust/hg-core/src/utils.rs +++ b/rust/hg-core/src/utils.rs @@ -145,6 +145,21 @@ impl SliceExt for [u8] { } } +pub trait StrExt { + // TODO: Use https://doc.rust-lang.org/nightly/std/primitive.str.html#method.split_once + // once we require Rust 1.52+ + fn split_2(&self, separator: char) -> Option<(&str, &str)>; +} + +impl StrExt for str { + fn split_2(&self, separator: char) -> Option<(&str, &str)> { + let mut iter = self.splitn(2, separator); + let a = iter.next()?; + let b = iter.next()?; + Some((a, b)) + } +} + pub trait Escaped { /// Return bytes escaped for display to the user fn escaped_bytes(&self) -> Vec; diff --git a/rust/hg-core/src/vfs.rs b/rust/hg-core/src/vfs.rs --- a/rust/hg-core/src/vfs.rs +++ b/rust/hg-core/src/vfs.rs @@ -1,6 +1,6 @@ use crate::errors::{HgError, IoErrorContext, IoResultExt}; use memmap2::{Mmap, MmapOptions}; -use std::io::ErrorKind; +use std::io::{ErrorKind, Write}; use std::path::{Path, PathBuf}; /// Filesystem access abstraction for the contents of a given "base" diretory @@ -16,6 +16,22 @@ impl Vfs<'_> { self.base.join(relative_path) } + pub fn symlink_metadata( + &self, + relative_path: impl AsRef, + ) -> Result { + let path = self.join(relative_path); + std::fs::symlink_metadata(&path).when_reading_file(&path) + } + + pub fn read_link( + &self, + relative_path: impl AsRef, + ) -> Result { + let path = self.join(relative_path); + std::fs::read_link(&path).when_reading_file(&path) + } + pub fn read( &self, relative_path: impl AsRef, @@ -71,6 +87,47 @@ impl Vfs<'_> { std::fs::rename(&from, &to) .with_context(|| IoErrorContext::RenamingFile { from, to }) } + + pub fn remove_file( + &self, + relative_path: impl AsRef, + ) -> Result<(), HgError> { + let path = self.join(relative_path); + std::fs::remove_file(&path) + .with_context(|| IoErrorContext::RemovingFile(path)) + } + + #[cfg(unix)] + pub fn create_symlink( + &self, + relative_link_path: impl AsRef, + target_path: impl AsRef, + ) -> Result<(), HgError> { + let link_path = self.join(relative_link_path); + std::os::unix::fs::symlink(target_path, &link_path) + .when_writing_file(&link_path) + } + + /// Write `contents` into a temporary file, then rename to `relative_path`. + /// This makes writing to a file "atomic": a reader opening that path will + /// see either the previous contents of the file or the complete new + /// content, never a partial write. + pub fn atomic_write( + &self, + relative_path: impl AsRef, + contents: &[u8], + ) -> Result<(), HgError> { + let mut tmp = tempfile::NamedTempFile::new_in(self.base) + .when_writing_file(self.base)?; + tmp.write_all(contents) + .and_then(|()| tmp.flush()) + .when_writing_file(tmp.path())?; + let path = self.join(relative_path); + tmp.persist(&path) + .map_err(|e| e.error) + .when_writing_file(&path)?; + Ok(()) + } } fn fs_metadata( diff --git a/rust/hg-core/tests/test_missing_ancestors.rs b/rust/hg-core/tests/test_missing_ancestors.rs --- a/rust/hg-core/tests/test_missing_ancestors.rs +++ b/rust/hg-core/tests/test_missing_ancestors.rs @@ -32,11 +32,11 @@ fn build_random_graph( if i == 2 || rng.gen_bool(prevprob) { (i - 1) as Revision } else { - rng.gen_range(0, i - 1) as Revision + rng.gen_range(0..i - 1) as Revision } }; // p2 is a random revision lower than i and different from p1 - let mut p2 = rng.gen_range(0, i - 1) as Revision; + let mut p2 = rng.gen_range(0..i - 1) as Revision; if p2 >= p1 { p2 = p2 + 1; } @@ -44,7 +44,7 @@ fn build_random_graph( } else if rng.gen_bool(prevprob) { vg.push([(i - 1) as Revision, NULL_REVISION]) } else { - vg.push([rng.gen_range(0, i - 1) as Revision, NULL_REVISION]) + vg.push([rng.gen_range(0..i - 1) as Revision, NULL_REVISION]) } } vg diff --git a/rust/hg-cpython/Cargo.toml b/rust/hg-cpython/Cargo.toml --- a/rust/hg-cpython/Cargo.toml +++ b/rust/hg-cpython/Cargo.toml @@ -28,3 +28,5 @@ libc = "0.2" log = "0.4.8" env_logger = "0.7.1" stable_deref_trait = "1.2.0" +vcsgraph = "0.2.0" + diff --git a/rust/hg-cpython/src/ancestors.rs b/rust/hg-cpython/src/ancestors.rs --- a/rust/hg-cpython/src/ancestors.rs +++ b/rust/hg-cpython/src/ancestors.rs @@ -42,20 +42,21 @@ use cpython::{ ObjectProtocol, PyClone, PyDict, PyList, PyModule, PyObject, PyResult, Python, PythonObject, ToPyObject, }; +use hg::MissingAncestors as CoreMissing; use hg::Revision; -use hg::{ - AncestorsIterator as CoreIterator, LazyAncestors as CoreLazy, - MissingAncestors as CoreMissing, -}; use std::cell::RefCell; use std::collections::HashSet; +use vcsgraph::lazy_ancestors::{ + AncestorsIterator as VCGAncestorsIterator, + LazyAncestors as VCGLazyAncestors, +}; py_class!(pub class AncestorsIterator |py| { - data inner: RefCell>>; + data inner: RefCell>>; def __next__(&self) -> PyResult> { match self.inner(py).borrow_mut().next() { - Some(Err(e)) => Err(GraphError::pynew(py, e)), + Some(Err(e)) => Err(GraphError::pynew_from_vcsgraph(py, e)), None => Ok(None), Some(Ok(r)) => Ok(Some(r)), } @@ -63,7 +64,7 @@ py_class!(pub class AncestorsIterator |p def __contains__(&self, rev: Revision) -> PyResult { self.inner(py).borrow_mut().contains(rev) - .map_err(|e| GraphError::pynew(py, e)) + .map_err(|e| GraphError::pynew_from_vcsgraph(py, e)) } def __iter__(&self) -> PyResult { @@ -73,32 +74,35 @@ py_class!(pub class AncestorsIterator |p def __new__(_cls, index: PyObject, initrevs: PyObject, stoprev: Revision, inclusive: bool) -> PyResult { let initvec: Vec = rev_pyiter_collect(py, &initrevs)?; - let ait = CoreIterator::new( + let ait = VCGAncestorsIterator::new( pyindex_to_graph(py, index)?, initvec, stoprev, inclusive, ) - .map_err(|e| GraphError::pynew(py, e))?; + .map_err(|e| GraphError::pynew_from_vcsgraph(py, e))?; AncestorsIterator::from_inner(py, ait) } }); impl AncestorsIterator { - pub fn from_inner(py: Python, ait: CoreIterator) -> PyResult { + pub fn from_inner( + py: Python, + ait: VCGAncestorsIterator, + ) -> PyResult { Self::create_instance(py, RefCell::new(Box::new(ait))) } } py_class!(pub class LazyAncestors |py| { - data inner: RefCell>>; + data inner: RefCell>>; def __contains__(&self, rev: Revision) -> PyResult { self.inner(py) .borrow_mut() .contains(rev) - .map_err(|e| GraphError::pynew(py, e)) + .map_err(|e| GraphError::pynew_from_vcsgraph(py, e)) } def __iter__(&self) -> PyResult { @@ -114,9 +118,9 @@ py_class!(pub class LazyAncestors |py| { let initvec: Vec = rev_pyiter_collect(py, &initrevs)?; let lazy = - CoreLazy::new(pyindex_to_graph(py, index)?, + VCGLazyAncestors::new(pyindex_to_graph(py, index)?, initvec, stoprev, inclusive) - .map_err(|e| GraphError::pynew(py, e))?; + .map_err(|e| GraphError::pynew_from_vcsgraph(py, e))?; Self::create_instance(py, RefCell::new(Box::new(lazy))) } diff --git a/rust/hg-cpython/src/cindex.rs b/rust/hg-cpython/src/cindex.rs --- a/rust/hg-cpython/src/cindex.rs +++ b/rust/hg-cpython/src/cindex.rs @@ -155,6 +155,24 @@ impl Graph for Index { } } +impl vcsgraph::graph::Graph for Index { + fn parents( + &self, + rev: Revision, + ) -> Result + { + match Graph::parents(self, rev) { + Ok(parents) => Ok(vcsgraph::graph::Parents(parents)), + Err(GraphError::ParentOutOfRange(rev)) => { + Err(vcsgraph::graph::GraphReadError::KeyedInvalidKey(rev)) + } + Err(GraphError::WorkingDirectoryUnsupported) => Err( + vcsgraph::graph::GraphReadError::WorkingDirectoryUnsupported, + ), + } + } +} + impl RevlogIndex for Index { /// Note C return type is Py_ssize_t (hence signed), but we shall /// force it to unsigned, because it's a length diff --git a/rust/hg-cpython/src/dirstate.rs b/rust/hg-cpython/src/dirstate.rs --- a/rust/hg-cpython/src/dirstate.rs +++ b/rust/hg-cpython/src/dirstate.rs @@ -54,7 +54,6 @@ pub fn init_module(py: Python, package: matcher: PyObject, ignorefiles: PyList, check_exec: bool, - last_normal_time: (u32, u32), list_clean: bool, list_ignored: bool, list_unknown: bool, diff --git a/rust/hg-cpython/src/dirstate/dirstate_map.rs b/rust/hg-cpython/src/dirstate/dirstate_map.rs --- a/rust/hg-cpython/src/dirstate/dirstate_map.rs +++ b/rust/hg-cpython/src/dirstate/dirstate_map.rs @@ -18,7 +18,7 @@ use cpython::{ use crate::{ dirstate::copymap::{CopyMap, CopyMapItemsIterator, CopyMapKeysIterator}, - dirstate::item::{timestamp, DirstateItem}, + dirstate::item::DirstateItem, pybytes_deref::PyBytesDeref, }; use hg::{ @@ -194,16 +194,13 @@ py_class!(pub class DirstateMap |py| { &self, p1: PyObject, p2: PyObject, - now: (u32, u32) ) -> PyResult { - let now = timestamp(py, now)?; - - let mut inner = self.inner(py).borrow_mut(); + let inner = self.inner(py).borrow(); let parents = DirstateParents { p1: extract_node_id(py, &p1)?, p2: extract_node_id(py, &p2)?, }; - let result = inner.pack_v1(parents, now); + let result = inner.pack_v1(parents); match result { Ok(packed) => Ok(PyBytes::new(py, &packed)), Err(_) => Err(PyErr::new::( @@ -218,17 +215,14 @@ py_class!(pub class DirstateMap |py| { /// instead of written to a new data file (False). def write_v2( &self, - now: (u32, u32), can_append: bool, ) -> PyResult { - let now = timestamp(py, now)?; - - let mut inner = self.inner(py).borrow_mut(); - let result = inner.pack_v2(now, can_append); + let inner = self.inner(py).borrow(); + let result = inner.pack_v2(can_append); match result { Ok((packed, tree_metadata, append)) => { let packed = PyBytes::new(py, &packed); - let tree_metadata = PyBytes::new(py, &tree_metadata); + let tree_metadata = PyBytes::new(py, tree_metadata.as_bytes()); let tuple = (packed, tree_metadata, append); Ok(tuple.to_py_object(py).into_object()) }, diff --git a/rust/hg-cpython/src/dirstate/item.rs b/rust/hg-cpython/src/dirstate/item.rs --- a/rust/hg-cpython/src/dirstate/item.rs +++ b/rust/hg-cpython/src/dirstate/item.rs @@ -23,7 +23,7 @@ py_class!(pub class DirstateItem |py| { p2_info: bool = false, has_meaningful_data: bool = true, has_meaningful_mtime: bool = true, - parentfiledata: Option<(u32, u32, (u32, u32))> = None, + parentfiledata: Option<(u32, u32, Option<(u32, u32, bool)>)> = None, fallback_exec: Option = None, fallback_symlink: Option = None, @@ -35,7 +35,9 @@ py_class!(pub class DirstateItem |py| { mode_size_opt = Some((mode, size)) } if has_meaningful_mtime { - mtime_opt = Some(timestamp(py, mtime)?) + if let Some(m) = mtime { + mtime_opt = Some(timestamp(py, m)?); + } } } let entry = DirstateEntry::from_v2_data( @@ -192,12 +194,8 @@ py_class!(pub class DirstateItem |py| { Ok(mtime) } - def need_delay(&self, now: (u32, u32)) -> PyResult { - let now = timestamp(py, now)?; - Ok(self.entry(py).get().need_delay(now)) - } - - def mtime_likely_equal_to(&self, other: (u32, u32)) -> PyResult { + def mtime_likely_equal_to(&self, other: (u32, u32, bool)) + -> PyResult { if let Some(mtime) = self.entry(py).get().truncated_mtime() { Ok(mtime.likely_equal(timestamp(py, other)?)) } else { @@ -230,7 +228,7 @@ py_class!(pub class DirstateItem |py| { &self, mode: u32, size: u32, - mtime: (u32, u32), + mtime: (u32, u32, bool), ) -> PyResult { let mtime = timestamp(py, mtime)?; self.update(py, |entry| entry.set_clean(mode, size, mtime)); @@ -275,12 +273,13 @@ impl DirstateItem { pub(crate) fn timestamp( py: Python<'_>, - (s, ns): (u32, u32), + (s, ns, second_ambiguous): (u32, u32, bool), ) -> PyResult { - TruncatedTimestamp::from_already_truncated(s, ns).map_err(|_| { - PyErr::new::( - py, - "expected mtime truncated to 31 bits", - ) - }) + TruncatedTimestamp::from_already_truncated(s, ns, second_ambiguous) + .map_err(|_| { + PyErr::new::( + py, + "expected mtime truncated to 31 bits", + ) + }) } diff --git a/rust/hg-cpython/src/dirstate/status.rs b/rust/hg-cpython/src/dirstate/status.rs --- a/rust/hg-cpython/src/dirstate/status.rs +++ b/rust/hg-cpython/src/dirstate/status.rs @@ -9,13 +9,12 @@ //! `hg-core` crate. From Python, this will be seen as //! `rustext.dirstate.status`. -use crate::dirstate::item::timestamp; use crate::{dirstate::DirstateMap, exceptions::FallbackError}; -use cpython::exc::OSError; use cpython::{ exc::ValueError, ObjectProtocol, PyBytes, PyErr, PyList, PyObject, PyResult, PyTuple, Python, PythonObject, ToPyObject, }; +use hg::dirstate::status::StatusPath; use hg::{ matchers::{AlwaysMatcher, FileMatcher, IncludeMatcher}, parse_pattern_syntax, @@ -28,15 +27,19 @@ use hg::{ }; use std::borrow::Borrow; +fn collect_status_path_list(py: Python, paths: &[StatusPath<'_>]) -> PyList { + collect_pybytes_list(py, paths.iter().map(|item| &*item.path)) +} + /// This will be useless once trait impls for collection are added to `PyBytes` /// upstream. fn collect_pybytes_list( py: Python, - collection: &[impl AsRef], + iter: impl Iterator>, ) -> PyList { let list = PyList::new(py, &[]); - for path in collection.iter() { + for path in iter { list.append( py, PyBytes::new(py, path.as_ref().as_bytes()).into_object(), @@ -91,7 +94,6 @@ fn handle_fallback(py: Python, err: Stat PyErr::new::(py, &as_string) } - StatusError::IO(e) => PyErr::new::(py, e.to_string()), e => PyErr::new::(py, e.to_string()), } } @@ -103,13 +105,11 @@ pub fn status_wrapper( root_dir: PyObject, ignore_files: PyList, check_exec: bool, - last_normal_time: (u32, u32), list_clean: bool, list_ignored: bool, list_unknown: bool, collect_traversed_dirs: bool, ) -> PyResult { - let last_normal_time = timestamp(py, last_normal_time)?; let bytes = root_dir.extract::(py)?; let root_dir = get_path_from_bytes(bytes.data(py)); @@ -124,6 +124,8 @@ pub fn status_wrapper( }) .collect(); let ignore_files = ignore_files?; + // The caller may call `copymap.items()` separately + let list_copies = false; match matcher.get_type(py).name(py).borrow() { "alwaysmatcher" => { @@ -135,10 +137,10 @@ pub fn status_wrapper( ignore_files, StatusOptions { check_exec, - last_normal_time, list_clean, list_ignored, list_unknown, + list_copies, collect_traversed_dirs, }, ) @@ -172,10 +174,10 @@ pub fn status_wrapper( ignore_files, StatusOptions { check_exec, - last_normal_time, list_clean, list_ignored, list_unknown, + list_copies, collect_traversed_dirs, }, ) @@ -224,10 +226,10 @@ pub fn status_wrapper( ignore_files, StatusOptions { check_exec, - last_normal_time, list_clean, list_ignored, list_unknown, + list_copies, collect_traversed_dirs, }, ) @@ -247,16 +249,16 @@ fn build_response( status_res: DirstateStatus, warnings: Vec, ) -> PyResult { - let modified = collect_pybytes_list(py, status_res.modified.as_ref()); - let added = collect_pybytes_list(py, status_res.added.as_ref()); - let removed = collect_pybytes_list(py, status_res.removed.as_ref()); - let deleted = collect_pybytes_list(py, status_res.deleted.as_ref()); - let clean = collect_pybytes_list(py, status_res.clean.as_ref()); - let ignored = collect_pybytes_list(py, status_res.ignored.as_ref()); - let unknown = collect_pybytes_list(py, status_res.unknown.as_ref()); - let unsure = collect_pybytes_list(py, status_res.unsure.as_ref()); - let bad = collect_bad_matches(py, status_res.bad.as_ref())?; - let traversed = collect_pybytes_list(py, status_res.traversed.as_ref()); + let modified = collect_status_path_list(py, &status_res.modified); + let added = collect_status_path_list(py, &status_res.added); + let removed = collect_status_path_list(py, &status_res.removed); + let deleted = collect_status_path_list(py, &status_res.deleted); + let clean = collect_status_path_list(py, &status_res.clean); + let ignored = collect_status_path_list(py, &status_res.ignored); + let unknown = collect_status_path_list(py, &status_res.unknown); + let unsure = collect_status_path_list(py, &status_res.unsure); + let bad = collect_bad_matches(py, &status_res.bad)?; + let traversed = collect_pybytes_list(py, status_res.traversed.iter()); let dirty = status_res.dirty.to_py_object(py); let py_warnings = PyList::new(py, &[]); for warning in warnings.iter() { diff --git a/rust/hg-cpython/src/exceptions.rs b/rust/hg-cpython/src/exceptions.rs --- a/rust/hg-cpython/src/exceptions.rs +++ b/rust/hg-cpython/src/exceptions.rs @@ -37,6 +37,32 @@ impl GraphError { } } } + + pub fn pynew_from_vcsgraph( + py: Python, + inner: vcsgraph::graph::GraphReadError, + ) -> PyErr { + match inner { + vcsgraph::graph::GraphReadError::InconsistentGraphData => { + GraphError::new(py, "InconsistentGraphData") + } + vcsgraph::graph::GraphReadError::InvalidKey => { + GraphError::new(py, "ParentOutOfRange") + } + vcsgraph::graph::GraphReadError::KeyedInvalidKey(r) => { + GraphError::new(py, ("ParentOutOfRange", r)) + } + vcsgraph::graph::GraphReadError::WorkingDirectoryUnsupported => { + match py + .import("mercurial.error") + .and_then(|m| m.get(py, "WdirUnsupported")) + { + Err(e) => e, + Ok(cls) => PyErr::from_instance(py, cls), + } + } + } + } } py_exception!(rustext, HgPathPyError, RuntimeError); diff --git a/rust/rhg/Cargo.toml b/rust/rhg/Cargo.toml --- a/rust/rhg/Cargo.toml +++ b/rust/rhg/Cargo.toml @@ -8,6 +8,7 @@ authors = [ edition = "2018" [dependencies] +atty = "0.2" hg-core = { path = "../hg-core"} chrono = "0.4.19" clap = "2.33.1" @@ -18,5 +19,5 @@ log = "0.4.11" micro-timer = "0.3.1" regex = "1.3.9" env_logger = "0.7.1" -format-bytes = "0.2.1" +format-bytes = "0.3.0" users = "0.11.0" diff --git a/rust/rhg/src/blackbox.rs b/rust/rhg/src/blackbox.rs --- a/rust/rhg/src/blackbox.rs +++ b/rust/rhg/src/blackbox.rs @@ -13,7 +13,7 @@ const DEFAULT_MAX_SIZE: u64 = ONE_MEBIBY 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"; +const DEFAULT_DATE_FORMAT: &str = "%Y-%m-%d %H:%M:%S%.3f"; type DateTime = chrono::DateTime; diff --git a/rust/rhg/src/color.rs b/rust/rhg/src/color.rs new file mode 100644 --- /dev/null +++ b/rust/rhg/src/color.rs @@ -0,0 +1,255 @@ +use crate::ui::formatted; +use crate::ui::plain; +use format_bytes::write_bytes; +use hg::config::Config; +use hg::config::ConfigOrigin; +use hg::errors::HgError; +use std::collections::HashMap; + +pub type Effect = u32; + +pub type EffectsMap = HashMap, Vec>; + +macro_rules! effects { + ($( $name: ident: $value: expr ,)+) => { + + #[allow(non_upper_case_globals)] + mod effects { + $( + pub const $name: super::Effect = $value; + )+ + } + + fn effect(name: &[u8]) -> Option { + $( + if name == stringify!($name).as_bytes() { + Some(effects::$name) + } else + )+ + { + None + } + } + }; +} + +effects! { + none: 0, + black: 30, + red: 31, + green: 32, + yellow: 33, + blue: 34, + magenta: 35, + cyan: 36, + white: 37, + bold: 1, + italic: 3, + underline: 4, + inverse: 7, + dim: 2, + black_background: 40, + red_background: 41, + green_background: 42, + yellow_background: 43, + blue_background: 44, + purple_background: 45, + cyan_background: 46, + white_background: 47, +} + +macro_rules! default_styles { + ($( $key: expr => [$($value: expr),*],)+) => { + fn default_styles() -> EffectsMap { + use effects::*; + let mut map = HashMap::new(); + $( + map.insert($key[..].to_owned(), vec![$( $value ),*]); + )+ + map + } + }; +} + +default_styles! { + b"grep.match" => [red, bold], + b"grep.linenumber" => [green], + b"grep.rev" => [blue], + b"grep.sep" => [cyan], + b"grep.filename" => [magenta], + b"grep.user" => [magenta], + b"grep.date" => [magenta], + b"grep.inserted" => [green, bold], + b"grep.deleted" => [red, bold], + b"bookmarks.active" => [green], + b"branches.active" => [none], + b"branches.closed" => [black, bold], + b"branches.current" => [green], + b"branches.inactive" => [none], + b"diff.changed" => [white], + b"diff.deleted" => [red], + b"diff.deleted.changed" => [red, bold, underline], + b"diff.deleted.unchanged" => [red], + b"diff.diffline" => [bold], + b"diff.extended" => [cyan, bold], + b"diff.file_a" => [red, bold], + b"diff.file_b" => [green, bold], + b"diff.hunk" => [magenta], + b"diff.inserted" => [green], + b"diff.inserted.changed" => [green, bold, underline], + b"diff.inserted.unchanged" => [green], + b"diff.tab" => [], + b"diff.trailingwhitespace" => [bold, red_background], + b"changeset.public" => [], + b"changeset.draft" => [], + b"changeset.secret" => [], + b"diffstat.deleted" => [red], + b"diffstat.inserted" => [green], + b"formatvariant.name.mismatchconfig" => [red], + b"formatvariant.name.mismatchdefault" => [yellow], + b"formatvariant.name.uptodate" => [green], + b"formatvariant.repo.mismatchconfig" => [red], + b"formatvariant.repo.mismatchdefault" => [yellow], + b"formatvariant.repo.uptodate" => [green], + b"formatvariant.config.special" => [yellow], + b"formatvariant.config.default" => [green], + b"formatvariant.default" => [], + b"histedit.remaining" => [red, bold], + b"ui.addremove.added" => [green], + b"ui.addremove.removed" => [red], + b"ui.error" => [red], + b"ui.prompt" => [yellow], + b"log.changeset" => [yellow], + b"patchbomb.finalsummary" => [], + b"patchbomb.from" => [magenta], + b"patchbomb.to" => [cyan], + b"patchbomb.subject" => [green], + b"patchbomb.diffstats" => [], + b"rebase.rebased" => [blue], + b"rebase.remaining" => [red, bold], + b"resolve.resolved" => [green, bold], + b"resolve.unresolved" => [red, bold], + b"shelve.age" => [cyan], + b"shelve.newest" => [green, bold], + b"shelve.name" => [blue, bold], + b"status.added" => [green, bold], + b"status.clean" => [none], + b"status.copied" => [none], + b"status.deleted" => [cyan, bold, underline], + b"status.ignored" => [black, bold], + b"status.modified" => [blue, bold], + b"status.removed" => [red, bold], + b"status.unknown" => [magenta, bold, underline], + b"tags.normal" => [green], + b"tags.local" => [black, bold], + b"upgrade-repo.requirement.preserved" => [cyan], + b"upgrade-repo.requirement.added" => [green], + b"upgrade-repo.requirement.removed" => [red], +} + +fn parse_effect(config_key: &[u8], effect_name: &[u8]) -> Option { + let found = effect(effect_name); + if found.is_none() { + // TODO: have some API for warnings + // TODO: handle IO errors during warnings + let stderr = std::io::stderr(); + let _ = write_bytes!( + &mut stderr.lock(), + b"ignoring unknown color/effect '{}' \ + (configured in color.{})\n", + effect_name, + config_key, + ); + } + found +} + +fn effects_from_config(config: &Config) -> EffectsMap { + let mut styles = default_styles(); + for (key, _value) in config.iter_section(b"color") { + if !key.contains(&b'.') + || key.starts_with(b"color.") + || key.starts_with(b"terminfo.") + { + continue; + } + // `unwrap` shouldn’t panic since we just got this key from + // iteration + let list = config.get_list(b"color", key).unwrap(); + let parsed = list + .iter() + .filter_map(|name| parse_effect(key, name)) + .collect(); + styles.insert(key.to_owned(), parsed); + } + styles +} + +enum ColorMode { + // TODO: support other modes + Ansi, +} + +impl ColorMode { + // Similar to _modesetup in mercurial/color.py + fn get(config: &Config) -> Result, HgError> { + if plain(Some("color")) { + return Ok(None); + } + let enabled_default = b"auto"; + // `origin` is only used when `!auto`, so its default doesn’t matter + let (enabled, origin) = config + .get_with_origin(b"ui", b"color") + .unwrap_or((enabled_default, &ConfigOrigin::CommandLineColor)); + if enabled == b"debug" { + return Err(HgError::unsupported("debug color mode")); + } + let auto = enabled == b"auto"; + let always; + if !auto { + let enabled_bool = config.get_bool(b"ui", b"color")?; + if !enabled_bool { + return Ok(None); + } + always = enabled == b"always" + || *origin == ConfigOrigin::CommandLineColor + } else { + always = false + }; + let formatted = always + || (std::env::var_os("TERM").unwrap_or_default() != "dumb" + && formatted(config)?); + + let mode_default = b"auto"; + let mode = config.get(b"color", b"mode").unwrap_or(mode_default); + + if formatted { + match mode { + b"ansi" | b"auto" => Ok(Some(ColorMode::Ansi)), + // TODO: support other modes + _ => Err(HgError::UnsupportedFeature(format!( + "color mode {}", + String::from_utf8_lossy(mode) + ))), + } + } else { + Ok(None) + } + } +} + +pub struct ColorConfig { + pub styles: EffectsMap, +} + +impl ColorConfig { + // Similar to _modesetup in mercurial/color.py + pub fn new(config: &Config) -> Result, HgError> { + Ok(match ColorMode::get(config)? { + None => None, + Some(ColorMode::Ansi) => Some(ColorConfig { + styles: effects_from_config(config), + }), + }) + } +} diff --git a/rust/rhg/src/commands/debugignorerhg.rs b/rust/rhg/src/commands/debugignorerhg.rs new file mode 100644 --- /dev/null +++ b/rust/rhg/src/commands/debugignorerhg.rs @@ -0,0 +1,40 @@ +use crate::error::CommandError; +use clap::SubCommand; +use hg; +use hg::matchers::get_ignore_matcher; +use hg::StatusError; +use log::warn; + +pub const HELP_TEXT: &str = " +Show effective hgignore patterns used by rhg. + +This is a pure Rust version of `hg debugignore`. + +Some options might be missing, check the list below. +"; + +pub fn args() -> clap::App<'static, 'static> { + SubCommand::with_name("debugignorerhg").about(HELP_TEXT) +} + +pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> { + let repo = invocation.repo?; + + let ignore_file = repo.working_directory_vfs().join(".hgignore"); // TODO hardcoded + + let (ignore_matcher, warnings) = get_ignore_matcher( + vec![ignore_file], + &repo.working_directory_path().to_owned(), + &mut |_pattern_bytes| (), + ) + .map_err(|e| StatusError::from(e))?; + + if !warnings.is_empty() { + warn!("Pattern warnings: {:?}", &warnings); + } + + let patterns = ignore_matcher.debug_get_patterns(); + invocation.ui.write_stdout(patterns)?; + invocation.ui.write_stdout(b"\n")?; + Ok(()) +} diff --git a/rust/rhg/src/commands/files.rs b/rust/rhg/src/commands/files.rs --- a/rust/rhg/src/commands/files.rs +++ b/rust/rhg/src/commands/files.rs @@ -1,13 +1,12 @@ use crate::error::CommandError; use crate::ui::Ui; -use crate::ui::UiError; -use crate::utils::path_utils::relativize_paths; +use crate::utils::path_utils::RelativizePaths; use clap::Arg; +use hg::errors::HgError; use hg::operations::list_rev_tracked_files; use hg::operations::Dirstate; use hg::repo::Repo; use hg::utils::hg_path::HgPath; -use std::borrow::Cow; pub const HELP_TEXT: &str = " List tracked files. @@ -39,29 +38,60 @@ pub fn run(invocation: &crate::CliInvoca let rev = invocation.subcommand_args.value_of("rev"); let repo = invocation.repo?; + + // It seems better if this check is removed: this would correspond to + // automatically enabling the extension if the repo requires it. + // However we need this check to be in sync with vanilla hg so hg tests + // pass. + if repo.has_sparse() + && invocation.config.get(b"extensions", b"sparse").is_none() + { + return Err(CommandError::unsupported( + "repo is using sparse, but sparse extension is not enabled", + )); + } + if let Some(rev) = rev { + if repo.has_narrow() { + return Err(CommandError::unsupported( + "rhg files -r is not supported in narrow clones", + )); + } let files = list_rev_tracked_files(repo, rev).map_err(|e| (e, rev))?; display_files(invocation.ui, repo, files.iter()) } else { + // The dirstate always reflects the sparse narrowspec, so if + // we only have sparse without narrow all is fine. + // If we have narrow, then [hg files] needs to check if + // the store narrowspec is in sync with the one of the dirstate, + // so we can't support that without explicit code. + if repo.has_narrow() { + return Err(CommandError::unsupported( + "rhg files is not supported in narrow clones", + )); + } let distate = Dirstate::new(repo)?; let files = distate.tracked_files()?; - display_files(invocation.ui, repo, files) + display_files(invocation.ui, repo, files.into_iter().map(Ok)) } } fn display_files<'a>( ui: &Ui, repo: &Repo, - files: impl IntoIterator, + files: impl IntoIterator>, ) -> Result<(), CommandError> { let mut stdout = ui.stdout_buffer(); let mut any = false; - relativize_paths(repo, files, |path: Cow<[u8]>| -> Result<(), UiError> { + let relativize = RelativizePaths::new(repo)?; + for result in files { + let path = result?; + stdout.write_all(&relativize.relativize(path))?; + stdout.write_all(b"\n")?; any = true; - stdout.write_all(path.as_ref())?; - stdout.write_all(b"\n") - })?; + } + stdout.flush()?; if any { Ok(()) diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -6,20 +6,29 @@ // GNU General Public License version 2 or any later version. use crate::error::CommandError; -use crate::ui::{Ui, UiError}; -use crate::utils::path_utils::relativize_paths; +use crate::ui::Ui; +use crate::utils::path_utils::RelativizePaths; use clap::{Arg, SubCommand}; +use format_bytes::format_bytes; use hg; use hg::config::Config; +use hg::dirstate::has_exec_bit; +use hg::dirstate::status::StatusPath; use hg::dirstate::TruncatedTimestamp; -use hg::errors::HgError; +use hg::dirstate::RANGE_MASK_31BIT; +use hg::errors::{HgError, IoResultExt}; +use hg::lock::LockError; use hg::manifest::Manifest; use hg::matchers::AlwaysMatcher; use hg::repo::Repo; -use hg::utils::hg_path::{hg_path_to_os_string, HgPath}; -use hg::{HgPathCow, StatusOptions}; -use log::{info, warn}; -use std::borrow::Cow; +use hg::utils::files::get_bytes_from_os_string; +use hg::utils::files::get_bytes_from_path; +use hg::utils::files::get_path_from_bytes; +use hg::utils::hg_path::{hg_path_to_path_buf, HgPath}; +use hg::StatusOptions; +use log::info; +use std::io; +use std::path::PathBuf; pub const HELP_TEXT: &str = " Show changed files in the working directory @@ -81,6 +90,18 @@ pub fn args() -> clap::App<'static, 'sta .short("-i") .long("--ignored"), ) + .arg( + Arg::with_name("copies") + .help("show source of copied files (DEFAULT: ui.statuscopies)") + .short("-C") + .long("--copies"), + ) + .arg( + Arg::with_name("no-status") + .help("hide status prefix") + .short("-n") + .long("--no-status"), + ) } /// Pure data type allowing the caller to specify file states to display @@ -128,31 +149,43 @@ impl DisplayStates { } 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')" - )); - } - // TODO: lift these limitations - if invocation.config.get_bool(b"ui", b"tweakdefaults").ok() == Some(true) { + if invocation.config.get_bool(b"ui", b"tweakdefaults")? { return Err(CommandError::unsupported( "ui.tweakdefaults is not yet supported with rhg status", )); } - if invocation.config.get_bool(b"ui", b"statuscopies").ok() == Some(true) { + if invocation.config.get_bool(b"ui", b"statuscopies")? { return Err(CommandError::unsupported( "ui.statuscopies is not yet supported with rhg status", )); } + if invocation + .config + .get(b"commands", b"status.terse") + .is_some() + { + return Err(CommandError::unsupported( + "status.terse is not yet supported with rhg status", + )); + } let ui = invocation.ui; let config = invocation.config; let args = invocation.subcommand_args; - let display_states = if args.is_present("all") { + + let verbose = !ui.plain(None) + && !args.is_present("print0") + && (config.get_bool(b"ui", b"verbose")? + || config.get_bool(b"commands", b"status.verbose")?); + if verbose { + return Err(CommandError::unsupported( + "verbose status is not supported yet", + )); + } + + let all = args.is_present("all"); + let display_states = if all { // TODO when implementing `--quiet`: it excludes clean files // from `--all` ALL_DISPLAY_STATES @@ -172,44 +205,84 @@ pub fn run(invocation: &crate::CliInvoca requested } }; + let no_status = args.is_present("no-status"); + let list_copies = all + || args.is_present("copies") + || config.get_bool(b"ui", b"statuscopies")?; let repo = invocation.repo?; + + if repo.has_sparse() || repo.has_narrow() { + return Err(CommandError::unsupported( + "rhg status is not supported for sparse checkouts or narrow clones yet" + )); + } + let mut dmap = repo.dirstate_map_mut()?; 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: TruncatedTimestamp::new_truncate(0, 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, + list_copies, collect_traversed_dirs: false, }; - let ignore_file = repo.working_directory_vfs().join(".hgignore"); // TODO hardcoded let (mut ds_status, pattern_warnings) = dmap.status( &AlwaysMatcher, repo.working_directory_path().to_owned(), - vec![ignore_file], + ignore_files(repo, config), options, )?; - if !pattern_warnings.is_empty() { - warn!("Pattern warnings: {:?}", &pattern_warnings); + for warning in pattern_warnings { + match warning { + hg::PatternFileWarning::InvalidSyntax(path, syntax) => ui + .write_stderr(&format_bytes!( + b"{}: ignoring invalid syntax '{}'\n", + get_bytes_from_path(path), + &*syntax + ))?, + hg::PatternFileWarning::NoSuchFile(path) => { + let path = if let Ok(relative) = + path.strip_prefix(repo.working_directory_path()) + { + relative + } else { + &*path + }; + ui.write_stderr(&format_bytes!( + b"skipping unreadable pattern file '{}': \ + No such file or directory\n", + get_bytes_from_path(path), + ))? + } + } } - if !ds_status.bad.is_empty() { - warn!("Bad matches {:?}", &(ds_status.bad)) + for (path, error) in ds_status.bad { + let error = match error { + hg::BadMatch::OsError(code) => { + std::io::Error::from_raw_os_error(code).to_string() + } + hg::BadMatch::BadType(ty) => { + format!("unsupported file type (type is {})", ty) + } + }; + ui.write_stderr(&format_bytes!( + b"{}: {}\n", + path.as_bytes(), + error.as_bytes() + ))? } if !ds_status.unsure.is_empty() { info!( "Files to be rechecked by retrieval from filelog: {:?}", - &ds_status.unsure + ds_status.unsure.iter().map(|s| &s.path).collect::>() ); } + let mut fixup = Vec::new(); if !ds_status.unsure.is_empty() && (display_states.modified || display_states.clean) { @@ -218,99 +291,241 @@ pub fn run(invocation: &crate::CliInvoca CommandError::from((e, &*format!("{:x}", p1.short()))) })?; for to_check in ds_status.unsure { - if cat_file_is_modified(repo, &manifest, &to_check)? { + if unsure_is_modified(repo, &manifest, &to_check.path)? { if display_states.modified { ds_status.modified.push(to_check); } } else { if display_states.clean { - ds_status.clean.push(to_check); + ds_status.clean.push(to_check.clone()); } + fixup.push(to_check.path.into_owned()) } } } + let relative_paths = (!ui.plain(None)) + && config + .get_option(b"commands", b"status.relative")? + .unwrap_or(config.get_bool(b"ui", b"relative-paths")?); + let output = DisplayStatusPaths { + ui, + no_status, + relativize: if relative_paths { + Some(RelativizePaths::new(repo)?) + } else { + None + }, + }; if display_states.modified { - display_status_paths(ui, repo, config, &mut ds_status.modified, b"M")?; + output.display(b"M ", "status.modified", ds_status.modified)?; } if display_states.added { - display_status_paths(ui, repo, config, &mut ds_status.added, b"A")?; + output.display(b"A ", "status.added", ds_status.added)?; } if display_states.removed { - display_status_paths(ui, repo, config, &mut ds_status.removed, b"R")?; + output.display(b"R ", "status.removed", ds_status.removed)?; } if display_states.deleted { - display_status_paths(ui, repo, config, &mut ds_status.deleted, b"!")?; + output.display(b"! ", "status.deleted", ds_status.deleted)?; } if display_states.unknown { - display_status_paths(ui, repo, config, &mut ds_status.unknown, b"?")?; + output.display(b"? ", "status.unknown", ds_status.unknown)?; } if display_states.ignored { - display_status_paths(ui, repo, config, &mut ds_status.ignored, b"I")?; + output.display(b"I ", "status.ignored", ds_status.ignored)?; } if display_states.clean { - display_status_paths(ui, repo, config, &mut ds_status.clean, b"C")?; + output.display(b"C ", "status.clean", ds_status.clean)?; + } + + let mut dirstate_write_needed = ds_status.dirty; + let filesystem_time_at_status_start = + ds_status.filesystem_time_at_status_start; + + if (fixup.is_empty() || filesystem_time_at_status_start.is_none()) + && !dirstate_write_needed + { + // Nothing to update + return Ok(()); + } + + // Update the dirstate on disk if we can + let with_lock_result = + repo.try_with_wlock_no_wait(|| -> Result<(), CommandError> { + if let Some(mtime_boundary) = filesystem_time_at_status_start { + for hg_path in fixup { + use std::os::unix::fs::MetadataExt; + let fs_path = hg_path_to_path_buf(&hg_path) + .expect("HgPath conversion"); + // Specifically do not reuse `fs_metadata` from + // `unsure_is_clean` which was needed before reading + // contents. Here we access metadata again after reading + // content, in case it changed in the meantime. + let fs_metadata = repo + .working_directory_vfs() + .symlink_metadata(&fs_path)?; + if let Some(mtime) = + TruncatedTimestamp::for_reliable_mtime_of( + &fs_metadata, + &mtime_boundary, + ) + .when_reading_file(&fs_path)? + { + let mode = fs_metadata.mode(); + let size = fs_metadata.len() as u32 & RANGE_MASK_31BIT; + let mut entry = dmap + .get(&hg_path)? + .expect("ambiguous file not in dirstate"); + entry.set_clean(mode, size, mtime); + dmap.add_file(&hg_path, entry)?; + dirstate_write_needed = true + } + } + } + drop(dmap); // Avoid "already mutably borrowed" RefCell panics + if dirstate_write_needed { + repo.write_dirstate()? + } + Ok(()) + }); + match with_lock_result { + Ok(closure_result) => closure_result?, + Err(LockError::AlreadyHeld) => { + // Not updating the dirstate is not ideal but not critical: + // don’t keep our caller waiting until some other Mercurial + // process releases the lock. + } + Err(LockError::Other(HgError::IoError { error, .. })) + if error.kind() == io::ErrorKind::PermissionDenied => + { + // `hg status` on a read-only repository is fine + } + Err(LockError::Other(error)) => { + // Report other I/O errors + Err(error)? + } } 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, - repo: &Repo, - config: &Config, - paths: &mut [HgPathCow], - status_prefix: &[u8], -) -> Result<(), CommandError> { - paths.sort_unstable(); - let mut relative: bool = - config.get_bool(b"ui", b"relative-paths").unwrap_or(false); - relative = config - .get_bool(b"commands", b"status.relative") - .unwrap_or(relative); - if relative && !ui.plain() { - relativize_paths( - repo, - paths, - |path: Cow<[u8]>| -> Result<(), UiError> { - ui.write_stdout( - &[status_prefix, b" ", path.as_ref(), b"\n"].concat(), - ) - }, - )?; - } else { - 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())?; +fn ignore_files(repo: &Repo, config: &Config) -> Vec { + let mut ignore_files = Vec::new(); + let repo_ignore = repo.working_directory_vfs().join(".hgignore"); + if repo_ignore.exists() { + ignore_files.push(repo_ignore) + } + for (key, value) in config.iter_section(b"ui") { + if key == b"ignore" || key.starts_with(b"ignore.") { + let path = get_path_from_bytes(value); + // TODO: expand "~/" and environment variable here, like Python + // does with `os.path.expanduser` and `os.path.expandvars` + + let joined = repo.working_directory_path().join(path); + ignore_files.push(joined); } } - Ok(()) + ignore_files +} + +struct DisplayStatusPaths<'a> { + ui: &'a Ui, + no_status: bool, + relativize: Option, +} + +impl DisplayStatusPaths<'_> { + // Probably more elegant to use a Deref or Borrow trait rather than + // harcode HgPathBuf, but probably not really useful at this point + fn display( + &self, + status_prefix: &[u8], + label: &'static str, + mut paths: Vec>, + ) -> Result<(), CommandError> { + paths.sort_unstable(); + // TODO: get the stdout lock once for the whole loop + // instead of in each write + for StatusPath { path, copy_source } in paths { + let relative; + let path = if let Some(relativize) = &self.relativize { + relative = relativize.relativize(&path); + &*relative + } else { + path.as_bytes() + }; + // TODO: Add a way to use `write_bytes!` instead of `format_bytes!` + // in order to stream to stdout instead of allocating an + // itermediate `Vec`. + if !self.no_status { + self.ui.write_stdout_labelled(status_prefix, label)? + } + self.ui + .write_stdout_labelled(&format_bytes!(b"{}\n", path), label)?; + if let Some(source) = copy_source { + let label = "status.copied"; + self.ui.write_stdout_labelled( + &format_bytes!(b" {}\n", source.as_bytes()), + label, + )? + } + } + 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( +fn unsure_is_modified( repo: &Repo, manifest: &Manifest, hg_path: &HgPath, ) -> Result { - let file_node = manifest - .find_file(hg_path)? + let vfs = repo.working_directory_vfs(); + let fs_path = hg_path_to_path_buf(hg_path).expect("HgPath conversion"); + let fs_metadata = vfs.symlink_metadata(&fs_path)?; + let is_symlink = fs_metadata.file_type().is_symlink(); + // TODO: Also account for `FALLBACK_SYMLINK` and `FALLBACK_EXEC` from the + // dirstate + let fs_flags = if is_symlink { + Some(b'l') + } else if has_exec_bit(&fs_metadata) { + Some(b'x') + } else { + None + }; + + let entry = manifest + .find_by_path(hg_path)? .expect("ambgious file not in p1"); + if entry.flags != fs_flags { + return Ok(true); + } let filelog = repo.filelog(hg_path)?; - let filelog_entry = filelog.data_for_node(file_node).map_err(|_| { - HgError::corrupted("filelog missing node from manifest") - })?; - let contents_in_p1 = filelog_entry.data()?; + let fs_len = fs_metadata.len(); + let filelog_entry = + filelog.entry_for_node(entry.node_id()?).map_err(|_| { + HgError::corrupted("filelog missing node from manifest") + })?; + if filelog_entry.file_data_len_not_equal_to(fs_len) { + // No need to read file contents: + // it cannot be equal if it has a different length. + return Ok(true); + } - let fs_path = hg_path_to_os_string(hg_path).expect("HgPath conversion"); - let fs_contents = repo.working_directory_vfs().read(fs_path)?; - return Ok(contents_in_p1 != &*fs_contents); + let p1_filelog_data = filelog_entry.data()?; + let p1_contents = p1_filelog_data.file_data()?; + if p1_contents.len() as u64 != fs_len { + // No need to read file contents: + // it cannot be equal if it has a different length. + return Ok(true); + } + + let fs_contents = if is_symlink { + get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string()) + } else { + vfs.read(fs_path)? + }; + Ok(p1_contents != &*fs_contents) } diff --git a/rust/rhg/src/main.rs b/rust/rhg/src/main.rs --- a/rust/rhg/src/main.rs +++ b/rust/rhg/src/main.rs @@ -1,5 +1,6 @@ extern crate log; -use crate::ui::Ui; +use crate::error::CommandError; +use crate::ui::{local_to_utf8, Ui}; use clap::App; use clap::AppSettings; use clap::Arg; @@ -10,17 +11,18 @@ use hg::exit_codes; use hg::repo::{Repo, RepoError}; use hg::utils::files::{get_bytes_from_os_str, get_path_from_bytes}; use hg::utils::SliceExt; +use std::collections::HashSet; use std::ffi::OsString; use std::path::PathBuf; use std::process::Command; mod blackbox; +mod color; mod error; mod ui; pub mod utils { pub mod path_utils; } -use error::CommandError; fn main_with_result( process_start_time: &blackbox::ProcessStartTime, @@ -28,7 +30,7 @@ fn main_with_result( repo: Result<&Repo, &NoRepoInCwdError>, config: &Config, ) -> Result<(), CommandError> { - check_extensions(config)?; + check_unsupported(config, repo)?; let app = App::new("rhg") .global_setting(AppSettings::AllowInvalidUtf8) @@ -65,6 +67,14 @@ fn main_with_result( .takes_value(true) .global(true), ) + .arg( + Arg::with_name("color") + .help("when to colorize (boolean, always, auto, never, or debug)") + .long("--color") + .value_name("TYPE") + .takes_value(true) + .global(true), + ) .version("0.0.1"); let app = add_subcommand_args(app); @@ -110,18 +120,23 @@ fn main_with_result( } } - 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 + if config.is_extension_enabled(b"blackbox") { + 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 + } else { + run(&invocation) + } } fn main() { @@ -131,7 +146,6 @@ fn main() { let process_start_time = blackbox::ProcessStartTime::now(); env_logger::init(); - let ui = ui::Ui::new(); let early_args = EarlyArgs::parse(std::env::args_os()); @@ -145,7 +159,7 @@ fn main() { .unwrap_or_else(|error| { exit( &None, - &ui, + &Ui::new_infallible(&Config::empty()), OnUnsupported::Abort, Err(CommandError::abort(format!( "abort: {}: '{}'", @@ -166,7 +180,7 @@ fn main() { exit( &initial_current_dir, - &ui, + &Ui::new_infallible(&Config::empty()), on_unsupported, Err(error.into()), false, @@ -174,12 +188,12 @@ fn main() { }); non_repo_config - .load_cli_args_config(early_args.config) + .load_cli_args(early_args.config, early_args.color) .unwrap_or_else(|error| { exit( &initial_current_dir, - &ui, - OnUnsupported::from_config(&ui, &non_repo_config), + &Ui::new_infallible(&non_repo_config), + OnUnsupported::from_config(&non_repo_config), Err(error.into()), non_repo_config .get_bool(b"ui", b"detailed-exit-code") @@ -196,8 +210,8 @@ fn main() { if SCHEME_RE.is_match(&repo_path_bytes) { exit( &initial_current_dir, - &ui, - OnUnsupported::from_config(&ui, &non_repo_config), + &Ui::new_infallible(&non_repo_config), + OnUnsupported::from_config(&non_repo_config), Err(CommandError::UnsupportedFeature { message: format_bytes!( b"URL-like --repository {}", @@ -286,8 +300,8 @@ fn main() { } Err(error) => exit( &initial_current_dir, - &ui, - OnUnsupported::from_config(&ui, &non_repo_config), + &Ui::new_infallible(&non_repo_config), + OnUnsupported::from_config(&non_repo_config), Err(error.into()), // TODO: show a warning or combine with original error if // `get_bool` returns an error @@ -302,7 +316,18 @@ fn main() { } else { &non_repo_config }; - let on_unsupported = OnUnsupported::from_config(&ui, config); + let ui = Ui::new(&config).unwrap_or_else(|error| { + exit( + &initial_current_dir, + &Ui::new_infallible(&config), + OnUnsupported::from_config(&config), + Err(error.into()), + config + .get_bool(b"ui", b"detailed-exit-code") + .unwrap_or(false), + ) + }); + let on_unsupported = OnUnsupported::from_config(config); let result = main_with_result( &process_start_time, @@ -358,10 +383,24 @@ fn exit( ) -> ! { if let ( OnUnsupported::Fallback { executable }, - Err(CommandError::UnsupportedFeature { .. }), + Err(CommandError::UnsupportedFeature { message }), ) = (&on_unsupported, &result) { let mut args = std::env::args_os(); + let executable = match executable { + None => { + exit_no_fallback( + ui, + OnUnsupported::Abort, + Err(CommandError::abort( + "abort: 'rhg.on-unsupported=fallback' without \ + 'rhg.fallback-executable' set.", + )), + false, + ); + } + Some(executable) => executable, + }; 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) { @@ -374,7 +413,10 @@ fn exit( )); on_unsupported = OnUnsupported::Abort } else { - // `args` is now `argv[1..]` since we’ve already consumed `argv[0]` + log::debug!("falling back (see trace-level log)"); + log::trace!("{}", local_to_utf8(message)); + // `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 { @@ -465,6 +507,7 @@ subcommands! { cat debugdata debugrequirements + debugignorerhg files root config @@ -494,6 +537,8 @@ struct NoRepoInCwdError { struct EarlyArgs { /// Values of all `--config` arguments. (Possibly none) config: Vec>, + /// Value of all the `--color` argument, if any. + color: Option>, /// Value of the `-R` or `--repository` argument, if any. repo: Option>, /// Value of the `--cwd` argument, if any. @@ -504,6 +549,7 @@ 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 color = None; let mut repo = None; let mut cwd = None; // Use `while let` instead of `for` so that we can also call @@ -517,6 +563,14 @@ impl EarlyArgs { config.push(value.to_owned()) } + if arg == b"--color" { + if let Some(value) = args.next() { + color = Some(value) + } + } else if let Some(value) = arg.drop_prefix(b"--color=") { + color = Some(value.to_owned()) + } + if arg == b"--cwd" { if let Some(value) = args.next() { cwd = Some(value) @@ -535,7 +589,12 @@ impl EarlyArgs { repo = Some(value.to_owned()) } } - Self { config, repo, cwd } + Self { + config, + color, + repo, + cwd, + } } } @@ -549,13 +608,13 @@ enum OnUnsupported { /// Silently exit with code 252. AbortSilent, /// Try running a Python implementation - Fallback { executable: Vec }, + Fallback { executable: Option> }, } impl OnUnsupported { const DEFAULT: Self = OnUnsupported::Abort; - fn from_config(ui: &Ui, config: &Config) -> Self { + fn from_config(config: &Config) -> Self { match config .get(b"rhg", b"on-unsupported") .map(|value| value.to_ascii_lowercase()) @@ -566,18 +625,7 @@ impl OnUnsupported { 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(), + .map(|x| x.to_owned()), }, None => Self::DEFAULT, Some(_) => { @@ -588,10 +636,23 @@ impl OnUnsupported { } } -const SUPPORTED_EXTENSIONS: &[&[u8]] = &[b"blackbox", b"share"]; +/// The `*` extension is an edge-case for config sub-options that apply to all +/// extensions. For now, only `:required` exists, but that may change in the +/// future. +const SUPPORTED_EXTENSIONS: &[&[u8]] = + &[b"blackbox", b"share", b"sparse", b"narrow", b"*"]; fn check_extensions(config: &Config) -> Result<(), CommandError> { - let enabled = config.get_section_keys(b"extensions"); + let enabled: HashSet<&[u8]> = config + .get_section_keys(b"extensions") + .into_iter() + .map(|extension| { + // Ignore extension suboptions. Only `required` exists for now. + // `rhg` either supports an extension or doesn't, so it doesn't + // make sense to consider the loading of an extension. + extension.split_2(b':').unwrap_or((extension, b"")).0 + }) + .collect(); let mut unsupported = enabled; for supported in SUPPORTED_EXTENSIONS { @@ -616,3 +677,32 @@ fn check_extensions(config: &Config) -> }) } } + +fn check_unsupported( + config: &Config, + repo: Result<&Repo, &NoRepoInCwdError>, +) -> Result<(), CommandError> { + check_extensions(config)?; + + if std::env::var_os("HG_PENDING").is_some() { + // TODO: only if the value is `== repo.working_directory`? + // What about relative v.s. absolute paths? + Err(CommandError::unsupported("$HG_PENDING"))? + } + + if let Ok(repo) = repo { + if repo.has_subrepos()? { + Err(CommandError::unsupported("sub-repositories"))? + } + } + + if config.has_non_empty_section(b"encode") { + Err(CommandError::unsupported("[encode] config"))? + } + + if config.has_non_empty_section(b"decode") { + Err(CommandError::unsupported("[decode] config"))? + } + + Ok(()) +} diff --git a/rust/rhg/src/ui.rs b/rust/rhg/src/ui.rs --- a/rust/rhg/src/ui.rs +++ b/rust/rhg/src/ui.rs @@ -1,13 +1,19 @@ +use crate::color::ColorConfig; +use crate::color::Effect; use format_bytes::format_bytes; +use format_bytes::write_bytes; +use hg::config::Config; +use hg::errors::HgError; +use hg::utils::files::get_bytes_from_os_string; use std::borrow::Cow; use std::env; use std::io; use std::io::{ErrorKind, Write}; -#[derive(Debug)] pub struct Ui { stdout: std::io::Stdout, stderr: std::io::Stderr, + colors: Option, } /// The kind of user interface error @@ -20,10 +26,26 @@ pub enum UiError { /// The commandline user interface impl Ui { - pub fn new() -> Self { + pub fn new(config: &Config) -> Result { + Ok(Ui { + // If using something else, also adapt `isatty()` below. + stdout: std::io::stdout(), + + stderr: std::io::stderr(), + colors: ColorConfig::new(config)?, + }) + } + + /// Default to no color if color configuration errors. + /// + /// Useful when we’re already handling another error. + pub fn new_infallible(config: &Config) -> Self { Ui { + // If using something else, also adapt `isatty()` below. stdout: std::io::stdout(), + stderr: std::io::stderr(), + colors: ColorConfig::new(config).unwrap_or(None), } } @@ -51,7 +73,62 @@ impl Ui { stderr.flush().or_else(handle_stderr_error) } - /// is plain mode active + /// Write bytes to stdout with the given label + /// + /// Like the optional `label` parameter in `mercurial/ui.py`, + /// this label influences the color used for this output. + pub fn write_stdout_labelled( + &self, + bytes: &[u8], + label: &str, + ) -> Result<(), UiError> { + if let Some(colors) = &self.colors { + if let Some(effects) = colors.styles.get(label.as_bytes()) { + if !effects.is_empty() { + return self + .write_stdout_with_effects(bytes, effects) + .or_else(handle_stdout_error); + } + } + } + self.write_stdout(bytes) + } + + fn write_stdout_with_effects( + &self, + bytes: &[u8], + effects: &[Effect], + ) -> io::Result<()> { + let stdout = &mut self.stdout.lock(); + let mut write_line = |line: &[u8], first: bool| { + // `line` does not include the newline delimiter + if !first { + stdout.write_all(b"\n")?; + } + if line.is_empty() { + return Ok(()); + } + /// 0x1B == 27 == 0o33 + const ASCII_ESCAPE: &[u8] = b"\x1b"; + write_bytes!(stdout, b"{}[0", ASCII_ESCAPE)?; + for effect in effects { + write_bytes!(stdout, b";{}", effect)?; + } + write_bytes!(stdout, b"m")?; + stdout.write_all(line)?; + write_bytes!(stdout, b"{}[0m", ASCII_ESCAPE) + }; + let mut lines = bytes.split(|&byte| byte == b'\n'); + if let Some(first) = lines.next() { + write_line(first, true)?; + for line in lines { + write_line(line, false)? + } + } + stdout.flush() + } + + /// Return whether plain mode is active. /// /// Plain mode means that all configuration variables which affect /// the behavior and output of Mercurial should be @@ -65,8 +142,19 @@ impl Ui { /// - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT /// - False if feature is disabled by default and not included in HGPLAIN /// - True otherwise - pub fn plain(&self) -> bool { - // TODO: add support for HGPLAINEXCEPT + pub fn plain(&self, feature: Option<&str>) -> bool { + plain(feature) + } +} + +pub fn plain(opt_feature: Option<&str>) -> bool { + if let Some(except) = env::var_os("HGPLAINEXCEPT") { + opt_feature.map_or(true, |feature| { + get_bytes_from_os_string(except) + .split(|&byte| byte == b',') + .all(|exception| exception != feature.as_bytes()) + }) + } else { env::var_os("HGPLAIN").is_some() } } @@ -130,3 +218,29 @@ pub fn utf8_to_local(s: &str) -> Cow<[u8 let bytes = s.as_bytes(); Cow::Borrowed(bytes) } + +/// Decode user system bytes to Rust string. +pub fn local_to_utf8(s: &[u8]) -> Cow { + // TODO decode from the user's system + String::from_utf8_lossy(s) +} + +/// Should formatted output be used? +/// +/// Note: rhg does not have the formatter mechanism yet, +/// but this is also used when deciding whether to use color. +pub fn formatted(config: &Config) -> Result { + if let Some(formatted) = config.get_option(b"ui", b"formatted")? { + Ok(formatted) + } else { + isatty(config) + } +} + +fn isatty(config: &Config) -> Result { + Ok(if config.get_bool(b"ui", b"nontty")? { + false + } else { + atty::is(atty::Stream::Stdout) + }) +} diff --git a/rust/rhg/src/utils/path_utils.rs b/rust/rhg/src/utils/path_utils.rs --- a/rust/rhg/src/utils/path_utils.rs +++ b/rust/rhg/src/utils/path_utils.rs @@ -3,8 +3,7 @@ // 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::UiError; +use hg::errors::HgError; use hg::repo::Repo; use hg::utils::current_dir; use hg::utils::files::{get_bytes_from_path, relativize_path}; @@ -12,37 +11,45 @@ use hg::utils::hg_path::HgPath; use hg::utils::hg_path::HgPathBuf; use std::borrow::Cow; -pub fn relativize_paths( - repo: &Repo, - paths: impl IntoIterator>, - mut callback: impl FnMut(Cow<[u8]>) -> Result<(), UiError>, -) -> Result<(), CommandError> { - let cwd = current_dir()?; - let repo_root = repo.working_directory_path(); - let repo_root = cwd.join(repo_root); // Make it absolute - let repo_root_hgpath = - HgPathBuf::from(get_bytes_from_path(repo_root.to_owned())); - let outside_repo: bool; - let cwd_hgpath: HgPathBuf; +pub struct RelativizePaths { + repo_root: HgPathBuf, + cwd: HgPathBuf, + outside_repo: bool, +} + +impl RelativizePaths { + pub fn new(repo: &Repo) -> Result { + let cwd = current_dir()?; + let repo_root = repo.working_directory_path(); + let repo_root = cwd.join(repo_root); // Make it absolute + let repo_root_hgpath = + HgPathBuf::from(get_bytes_from_path(repo_root.to_owned())); - if let Ok(cwd_relative_to_repo) = cwd.strip_prefix(&repo_root) { - // The current directory is inside the repo, so we can work with - // relative paths - outside_repo = false; - cwd_hgpath = - HgPathBuf::from(get_bytes_from_path(cwd_relative_to_repo)); - } else { - outside_repo = true; - cwd_hgpath = HgPathBuf::from(get_bytes_from_path(cwd)); + if let Ok(cwd_relative_to_repo) = cwd.strip_prefix(&repo_root) { + // The current directory is inside the repo, so we can work with + // relative paths + Ok(Self { + repo_root: repo_root_hgpath, + cwd: HgPathBuf::from(get_bytes_from_path( + cwd_relative_to_repo, + )), + outside_repo: false, + }) + } else { + Ok(Self { + repo_root: repo_root_hgpath, + cwd: HgPathBuf::from(get_bytes_from_path(cwd)), + outside_repo: true, + }) + } } - for file in paths { - if outside_repo { - let file = repo_root_hgpath.join(file.as_ref()); - callback(relativize_path(&file, &cwd_hgpath))?; + pub fn relativize<'a>(&self, path: &'a HgPath) -> Cow<'a, [u8]> { + if self.outside_repo { + let joined = self.repo_root.join(path); + Cow::Owned(relativize_path(&joined, &self.cwd).into_owned()) } else { - callback(relativize_path(file.as_ref(), &cwd_hgpath))?; + relativize_path(path, &self.cwd) } } - Ok(()) } diff --git a/setup.py b/setup.py --- a/setup.py +++ b/setup.py @@ -209,7 +209,7 @@ from distutils.errors import ( from distutils.sysconfig import get_python_inc, get_config_var from distutils.version import StrictVersion -# Explain to distutils.StrictVersion how our release candidates are versionned +# Explain to distutils.StrictVersion how our release candidates are versioned StrictVersion.version_re = re.compile(r'^(\d+)\.(\d+)(\.(\d+))?-?(rc(\d+))?$') @@ -535,7 +535,7 @@ class hgdist(Distribution): # (see mercurial/__modulepolicy__.py) if hgrustext != 'cpython' and hgrustext is not None: if hgrustext: - msg = 'unkown HGWITHRUSTEXT value: %s' % hgrustext + msg = 'unknown HGWITHRUSTEXT value: %s' % hgrustext printf(msg, file=sys.stderr) hgrustext = None self.rust = hgrustext is not None @@ -597,8 +597,8 @@ class hgbuildext(build_ext): e for e in self.extensions if e.name != 'mercurial.zstd' ] - # Build Rust standalon extensions if it'll be used - # and its build is not explictely disabled (for external build + # Build Rust standalone extensions if it'll be used + # and its build is not explicitly disabled (for external build # as Linux distributions would do) if self.distribution.rust and self.rust: if not sys.platform.startswith('linux'): @@ -1502,7 +1502,7 @@ class RustExtension(Extension): raise RustCompilationError("Cargo not found") elif exc.errno == errno.EACCES: raise RustCompilationError( - "Cargo found, but permisssion to execute it is denied" + "Cargo found, but permission to execute it is denied" ) else: raise diff --git a/tests/failfilemerge.py b/tests/failfilemerge.py --- a/tests/failfilemerge.py +++ b/tests/failfilemerge.py @@ -9,12 +9,9 @@ from mercurial import ( ) -def failfilemerge( - filemergefn, premerge, repo, wctx, mynode, orig, fcd, fco, fca, labels=None -): +def failfilemerge(*args, **kwargs): raise error.Abort(b"^C") - return filemergefn(premerge, repo, mynode, orig, fcd, fco, fca, labels) def extsetup(ui): - extensions.wrapfunction(filemerge, '_filemerge', failfilemerge) + extensions.wrapfunction(filemerge, 'filemerge', failfilemerge) diff --git a/tests/fakedirstatewritetime.py b/tests/fakedirstatewritetime.py --- a/tests/fakedirstatewritetime.py +++ b/tests/fakedirstatewritetime.py @@ -9,7 +9,6 @@ from __future__ import absolute_import from mercurial import ( context, - dirstate, dirstatemap as dirstatemapmod, extensions, policy, @@ -38,14 +37,8 @@ parsers = policy.importmod('parsers') has_rust_dirstate = policy.importrust('dirstate') is not None -def pack_dirstate(fakenow, orig, dmap, copymap, pl, now): - # execute what original parsers.pack_dirstate should do actually - # for consistency - for f, e in dmap.items(): - if e.need_delay(now): - e.set_possibly_dirty() - - return orig(dmap, copymap, pl, fakenow) +def pack_dirstate(orig, dmap, copymap, pl): + return orig(dmap, copymap, pl) def fakewrite(ui, func): @@ -62,30 +55,30 @@ def fakewrite(ui, func): # parsing 'fakenow' in YYYYmmddHHMM format makes comparison between # 'fakenow' value and 'touch -t YYYYmmddHHMM' argument easy fakenow = dateutil.parsedate(fakenow, [b'%Y%m%d%H%M'])[0] - fakenow = timestamp.timestamp((fakenow, 0)) + fakenow = timestamp.timestamp((fakenow, 0, False)) if has_rust_dirstate: # The Rust implementation does not use public parse/pack dirstate # to prevent conversion round-trips orig_dirstatemap_write = dirstatemapmod.dirstatemap.write - wrapper = lambda self, tr, st, now: orig_dirstatemap_write( - self, tr, st, fakenow - ) + wrapper = lambda self, tr, st: orig_dirstatemap_write(self, tr, st) dirstatemapmod.dirstatemap.write = wrapper - orig_dirstate_getfsnow = dirstate._getfsnow - wrapper = lambda *args: pack_dirstate(fakenow, orig_pack_dirstate, *args) + orig_get_fs_now = timestamp.get_fs_now + wrapper = lambda *args: pack_dirstate(orig_pack_dirstate, *args) orig_module = parsers orig_pack_dirstate = parsers.pack_dirstate orig_module.pack_dirstate = wrapper - dirstate._getfsnow = lambda *args: fakenow + timestamp.get_fs_now = ( + lambda *args: fakenow + ) # XXX useless for this purpose now try: return func() finally: orig_module.pack_dirstate = orig_pack_dirstate - dirstate._getfsnow = orig_dirstate_getfsnow + timestamp.get_fs_now = orig_get_fs_now if has_rust_dirstate: dirstatemapmod.dirstatemap.write = orig_dirstatemap_write diff --git a/tests/hghave.py b/tests/hghave.py --- a/tests/hghave.py +++ b/tests/hghave.py @@ -663,6 +663,22 @@ def pygments25(): return (major, minor) >= (2, 5) +@check("pygments211", "Pygments version >= 2.11") +def pygments211(): + try: + import pygments + + v = pygments.__version__ + except ImportError: + return False + + parts = v.split(".") + major = int(parts[0]) + minor = int(parts[1]) + + return (major, minor) >= (2, 11) + + @check("outer-repo", "outer repo") def has_outer_repo(): # failing for other reasons than 'no repo' imply that there is a repo diff --git a/tests/test-annotate.t b/tests/test-annotate.t --- a/tests/test-annotate.t +++ b/tests/test-annotate.t @@ -221,16 +221,16 @@ annotate -nlf b a a a - <<<<<<< working copy: 5fbdc1152d97 - test: b2.1 + <<<<<<< working copy: 5fbdc1152d97 - test: b2.1 b4 c b5 - ||||||| base + ||||||| common ancestor: 3086dbafde1c - test: b ======= b4 b5 b6 - >>>>>>> merge rev: 37ec9f5c3d1f - test: b2 + >>>>>>> merge rev: 37ec9f5c3d1f - test: b2 $ cat < b > a > a @@ -747,16 +747,16 @@ merge 0 1 baz:1 2 baz:2 - <<<<<<< working copy: 863de62655ef - test: baz:3+->3- + <<<<<<< working copy: 863de62655ef - test: baz:3+->3- 3- baz:3 4 baz:4 - ||||||| base + ||||||| common ancestor: 56fc739c091f - test: baz:3->3+ 3+ baz:3 4 baz:4 ======= 3+ baz:3 4+ baz:4 - >>>>>>> merge rev: cb8df70ae185 - test: qux:4->4+ + >>>>>>> merge rev: cb8df70ae185 - test: qux:4->4+ 5 6 7 @@ -794,16 +794,16 @@ merge 0 1 baz:1 2 baz:2 - <<<<<<< working copy: cb8df70ae185 - test: qux:4->4+ + <<<<<<< working copy: cb8df70ae185 - test: qux:4->4+ 3+ baz:3 4+ baz:4 - ||||||| base + ||||||| common ancestor: 56fc739c091f - test: baz:3->3+ 3+ baz:3 4 baz:4 ======= 3- baz:3 4 baz:4 - >>>>>>> merge rev: 863de62655ef - test: baz:3+->3- + >>>>>>> merge rev: 863de62655ef - test: baz:3+->3- 5 6 7 @@ -886,7 +886,7 @@ track of possible further descendants in created new head $ hg merge --tool :merge-other 24 merging baz - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + 0 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) $ hg ci -m 'merge forgetting about baz rewrite' $ cat > baz << EOF @@ -1172,16 +1172,16 @@ Issue5360: Deleted chunk in p1 of a merg warning: conflicts while merging a! (edit, then use 'hg resolve --mark') [1] $ cat a - <<<<<<< working copy: 0a068f0261cf - test: 3 + <<<<<<< working copy: 0a068f0261cf - test: 3 1 2 3 - ||||||| base + ||||||| common ancestor: 1ed24be7e7a0 - test: 2 1 2 ======= a - >>>>>>> merge rev: 9409851bc20a - test: a + >>>>>>> merge rev: 9409851bc20a - test: a $ cat > a << EOF > b > 1 diff --git a/tests/test-audit-path.t b/tests/test-audit-path.t --- a/tests/test-audit-path.t +++ b/tests/test-audit-path.t @@ -8,7 +8,7 @@ audit of .hg $ hg add .hg/00changelog.i abort: path contains illegal component: .hg/00changelog.i - [255] + [10] #if symlink @@ -91,7 +91,7 @@ attack .hg/test .hg/test $ hg update -Cr0 abort: path contains illegal component: .hg/test - [255] + [10] attack foo/.hg/test @@ -99,7 +99,7 @@ attack foo/.hg/test foo/.hg/test $ hg update -Cr1 abort: path 'foo/.hg/test' is inside nested repo 'foo' - [255] + [10] attack back/test where back symlinks to .. @@ -125,7 +125,7 @@ attack ../test $ echo data > ../test/file $ hg update -Cr3 abort: path contains illegal component: ../test - [255] + [10] $ cat ../test/file data @@ -135,7 +135,7 @@ attack /tmp/test /tmp/test $ hg update -Cr4 abort: path contains illegal component: /tmp/test - [255] + [10] $ cd .. diff --git a/tests/test-audit-subrepo.t b/tests/test-audit-subrepo.t --- a/tests/test-audit-subrepo.t +++ b/tests/test-audit-subrepo.t @@ -10,7 +10,7 @@ on commit: $ echo 'sub/.hg = sub/.hg' >> .hgsub $ hg ci -qAm 'add subrepo "sub/.hg"' abort: path 'sub/.hg' is inside nested repo 'sub' - [255] + [10] prepare tampered repo (including the commit above): @@ -34,7 +34,7 @@ on clone (and update): $ hg clone -q hgname hgname2 abort: path 'sub/.hg' is inside nested repo 'sub' - [255] + [10] Test absolute path ------------------ @@ -47,7 +47,7 @@ on commit: $ echo '/sub = sub' >> .hgsub $ hg ci -qAm 'add subrepo "/sub"' abort: path contains illegal component: /sub - [255] + [10] prepare tampered repo (including the commit above): @@ -71,7 +71,7 @@ on clone (and update): $ hg clone -q absolutepath absolutepath2 abort: path contains illegal component: /sub - [255] + [10] Test root path -------------- @@ -84,7 +84,7 @@ on commit: $ echo '/ = sub' >> .hgsub $ hg ci -qAm 'add subrepo "/"' abort: path ends in directory separator: / - [255] + [10] prepare tampered repo (including the commit above): @@ -108,7 +108,7 @@ on clone (and update): $ hg clone -q rootpath rootpath2 abort: path ends in directory separator: / - [255] + [10] Test empty path --------------- @@ -197,7 +197,7 @@ on commit: $ echo '../sub = ../sub' >> .hgsub $ hg ci -qAm 'add subrepo "../sub"' abort: path contains illegal component: ../sub - [255] + [10] prepare tampered repo (including the commit above): @@ -221,7 +221,7 @@ on clone (and update): $ hg clone -q main main2 abort: path contains illegal component: ../sub - [255] + [10] $ cd .. Test variable expansion @@ -718,7 +718,7 @@ on clone (and update): $ hg clone -q driveletter driveletter2 abort: path contains illegal component: X: - [255] + [10] #else diff --git a/tests/test-bad-extension.t b/tests/test-bad-extension.t --- a/tests/test-bad-extension.t +++ b/tests/test-bad-extension.t @@ -52,16 +52,18 @@ another bad extension > EOF $ hg -q help help 2>&1 |grep extension - *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow - *** failed to import extension badext2: No module named *badext2* (glob) + *** failed to import extension "badext" from $TESTTMP/badext.py: bit bucket overflow + *** failed to import extension "badext2": No module named 'badext2' (py3 !) + *** failed to import extension "badext2": No module named badext2 (no-py3 !) show traceback $ hg -q help help --traceback 2>&1 | egrep ' extension|^Exception|Traceback|ImportError|ModuleNotFound' - *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow + *** failed to import extension "badext" from $TESTTMP/badext.py: bit bucket overflow Traceback (most recent call last): Exception: bit bucket overflow - *** failed to import extension badext2: No module named *badext2* (glob) + *** failed to import extension "badext2": No module named 'badext2' (py3 !) + *** failed to import extension "badext2": No module named badext2 (no-py3 !) Traceback (most recent call last): ImportError: No module named badext2 (no-py3 !) ImportError: No module named 'hgext.badext2' (py3 no-py36 !) @@ -101,7 +103,7 @@ show traceback for ImportError of hgext. YYYY/MM/DD HH:MM:SS (PID)> - invoking registered callbacks: gpg YYYY/MM/DD HH:MM:SS (PID)> > callbacks completed in * (glob) YYYY/MM/DD HH:MM:SS (PID)> - loading extension: badext - *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow + *** failed to import extension "badext" from $TESTTMP/badext.py: bit bucket overflow Traceback (most recent call last): Exception: bit bucket overflow YYYY/MM/DD HH:MM:SS (PID)> - loading extension: baddocext @@ -123,7 +125,8 @@ show traceback for ImportError of hgext. Traceback (most recent call last): (py3 !) ImportError: No module named 'hgext3rd.badext2' (py3 no-py36 !) ModuleNotFoundError: No module named 'hgext3rd.badext2' (py36 !) - *** failed to import extension badext2: No module named *badext2* (glob) + *** failed to import extension "badext2": No module named 'badext2' (py3 !) + *** failed to import extension "badext2": No module named badext2 (no-py3 !) Traceback (most recent call last): ImportError: No module named 'hgext.badext2' (py3 no-py36 !) ModuleNotFoundError: No module named 'hgext.badext2' (py36 !) @@ -160,8 +163,9 @@ show traceback for ImportError of hgext. confirm that there's no crash when an extension's documentation is bad $ hg help --keyword baddocext - *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow - *** failed to import extension badext2: No module named *badext2* (glob) + *** failed to import extension "badext" from $TESTTMP/badext.py: bit bucket overflow + *** failed to import extension "badext2": No module named 'badext2' (py3 !) + *** failed to import extension "badext2": No module named badext2 (no-py3 !) Topics: extensions Using Additional Features diff --git a/tests/test-basic.t b/tests/test-basic.t --- a/tests/test-basic.t +++ b/tests/test-basic.t @@ -40,7 +40,7 @@ Writes to stdio succeed and fail appropr A a $ hg status >/dev/full - abort: No space left on device + abort: No space left on device* (glob) [255] #endif diff --git a/tests/test-blackbox.t b/tests/test-blackbox.t --- a/tests/test-blackbox.t +++ b/tests/test-blackbox.t @@ -32,10 +32,10 @@ command, exit codes, and duration $ echo a > a $ hg add a $ hg blackbox --config blackbox.dirty=True - 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> init blackboxtest exited 0 after * seconds (glob) - 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add a - 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add a exited 0 after * seconds (glob) - 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000+ (5000)> blackbox --config *blackbox.dirty=True* (glob) + 1970-01-01 00:00:00.000 bob @0000000000000000000000000000000000000000 (5000)> init blackboxtest exited 0 after * seconds (glob) + 1970-01-01 00:00:00.000 bob @0000000000000000000000000000000000000000 (5000)> add a + 1970-01-01 00:00:00.000 bob @0000000000000000000000000000000000000000 (5000)> add a exited 0 after * seconds (glob) + 1970-01-01 00:00:00.000 bob @0000000000000000000000000000000000000000+ (5000)> blackbox --config *blackbox.dirty=True* (glob) failure exit code $ rm ./.hg/blackbox.log @@ -43,17 +43,17 @@ failure exit code non-existent: $ENOENT$ [1] $ hg blackbox - 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add non-existent - 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add non-existent exited 1 after * seconds (glob) - 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox + 1970-01-01 00:00:00.000 bob @0000000000000000000000000000000000000000 (5000)> add non-existent + 1970-01-01 00:00:00.000 bob @0000000000000000000000000000000000000000 (5000)> add non-existent exited 1 after * seconds (glob) + 1970-01-01 00:00:00.000 bob @0000000000000000000000000000000000000000 (5000)> blackbox abort exit code $ rm ./.hg/blackbox.log $ hg abortcmd 2> /dev/null [255] $ hg blackbox -l 2 - 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> abortcmd exited 255 after * seconds (glob) - 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox -l 2 + 1970-01-01 00:00:00.000 bob @0000000000000000000000000000000000000000 (5000)> abortcmd exited 255 after * seconds (glob) + 1970-01-01 00:00:00.000 bob @0000000000000000000000000000000000000000 (5000)> blackbox -l 2 unhandled exception $ rm ./.hg/blackbox.log @@ -67,27 +67,27 @@ unhandled exception [1] #endif $ hg blackbox -l 2 - 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> crash exited 1 after * seconds (glob) - 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox -l 2 + 1970-01-01 00:00:00.000 bob @0000000000000000000000000000000000000000 (5000)> crash exited 1 after * seconds (glob) + 1970-01-01 00:00:00.000 bob @0000000000000000000000000000000000000000 (5000)> blackbox -l 2 alias expansion is logged $ rm ./.hg/blackbox.log $ hg confuse $ hg blackbox - 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> confuse - 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> alias 'confuse' expands to 'log --limit 3' - 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> confuse exited 0 after * seconds (glob) - 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox + 1970-01-01 00:00:00.000 bob @0000000000000000000000000000000000000000 (5000)> confuse + 1970-01-01 00:00:00.000 bob @0000000000000000000000000000000000000000 (5000)> alias 'confuse' expands to 'log --limit 3' + 1970-01-01 00:00:00.000 bob @0000000000000000000000000000000000000000 (5000)> confuse exited 0 after * seconds (glob) + 1970-01-01 00:00:00.000 bob @0000000000000000000000000000000000000000 (5000)> blackbox recursive aliases work correctly $ rm ./.hg/blackbox.log $ hg so-confusing $ hg blackbox - 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> so-confusing - 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> alias 'so-confusing' expands to 'confuse --style compact' - 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> alias 'confuse' expands to 'log --limit 3' - 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> so-confusing exited 0 after * seconds (glob) - 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox + 1970-01-01 00:00:00.000 bob @0000000000000000000000000000000000000000 (5000)> so-confusing + 1970-01-01 00:00:00.000 bob @0000000000000000000000000000000000000000 (5000)> alias 'so-confusing' expands to 'confuse --style compact' + 1970-01-01 00:00:00.000 bob @0000000000000000000000000000000000000000 (5000)> alias 'confuse' expands to 'log --limit 3' + 1970-01-01 00:00:00.000 bob @0000000000000000000000000000000000000000 (5000)> so-confusing exited 0 after * seconds (glob) + 1970-01-01 00:00:00.000 bob @0000000000000000000000000000000000000000 (5000)> blackbox custom date format $ rm ./.hg/blackbox.log @@ -97,7 +97,7 @@ custom date format $ hg blackbox 2012-04-13 @ 20:13:13 bob @0000000000000000000000000000000000000000 (5000)> --config *blackbox.date-format=%Y-%m-%d @ %H:%M:%S* --config *devel.default-date=1334347993 0* --traceback status (glob) 2012-04-13 @ 20:13:13 bob @0000000000000000000000000000000000000000 (5000)> --config *blackbox.date-format=%Y-%m-%d @ %H:%M:%S* --config *devel.default-date=1334347993 0* --traceback status exited 0 after * seconds (glob) - 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox + 1970-01-01 00:00:00.000 bob @0000000000000000000000000000000000000000 (5000)> blackbox incoming change tracking @@ -128,12 +128,12 @@ clone, commit, pull new changesets d02f48003e62 (run 'hg update' to get a working copy) $ hg blackbox -l 6 - 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> wrote branch cache (served) with 1 labels and 2 nodes - 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> updated branch cache (served.hidden) in * seconds (glob) - 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> wrote branch cache (served.hidden) with 1 labels and 2 nodes - 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> 1 incoming changes - new heads: d02f48003e62 - 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> pull exited 0 after * seconds (glob) - 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> blackbox -l 6 + 1970-01-01 00:00:00.000 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> wrote branch cache (served) with 1 labels and 2 nodes + 1970-01-01 00:00:00.000 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> updated branch cache (served.hidden) in * seconds (glob) + 1970-01-01 00:00:00.000 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> wrote branch cache (served.hidden) with 1 labels and 2 nodes + 1970-01-01 00:00:00.000 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> 1 incoming changes - new heads: d02f48003e62 + 1970-01-01 00:00:00.000 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> pull exited 0 after * seconds (glob) + 1970-01-01 00:00:00.000 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> blackbox -l 6 we must not cause a failure if we cannot write to the log @@ -191,12 +191,12 @@ backup bundles get logged 0 files updated, 0 files merged, 1 files removed, 0 files unresolved saved backup bundle to $TESTTMP/blackboxtest2/.hg/strip-backup/*-backup.hg (glob) $ hg blackbox -l 6 - 1970/01/01 00:00:00 bob @73f6ee326b27d820b0472f1a825e3a50f3dc489b (5000)> strip tip - 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> saved backup bundle to $TESTTMP/blackboxtest2/.hg/strip-backup/73f6ee326b27-7612e004-backup.hg - 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> updated branch cache (immutable) in * seconds (glob) - 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> wrote branch cache (immutable) with 1 labels and 2 nodes - 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> strip tip exited 0 after * seconds (glob) - 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> blackbox -l 6 + 1970-01-01 00:00:00.000 bob @73f6ee326b27d820b0472f1a825e3a50f3dc489b (5000)> strip tip + 1970-01-01 00:00:00.000 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> saved backup bundle to $TESTTMP/blackboxtest2/.hg/strip-backup/73f6ee326b27-7612e004-backup.hg + 1970-01-01 00:00:00.000 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> updated branch cache (immutable) in * seconds (glob) + 1970-01-01 00:00:00.000 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> wrote branch cache (immutable) with 1 labels and 2 nodes + 1970-01-01 00:00:00.000 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> strip tip exited 0 after * seconds (glob) + 1970-01-01 00:00:00.000 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> blackbox -l 6 extension and python hooks - use the eol extension for a pythonhook @@ -217,12 +217,12 @@ extension and python hooks - use the eol > eol=! > EOF $ hg blackbox -l 5 - 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> update (no-chg !) - 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> pythonhook-preupdate: hgext.eol.preupdate finished in * seconds (glob) - 1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> exthook-update: echo hooked finished in * seconds (glob) - 1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> update exited 0 after * seconds (glob) - 1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> serve --no-profile --cmdserver chgunix --address $TESTTMP.chgsock/server.* --daemon-postexec 'chdir:/' (glob) (chg !) - 1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> blackbox -l 5 + 1970-01-01 00:00:00.000 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> update (no-chg !) + 1970-01-01 00:00:00.000 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> pythonhook-preupdate: hgext.eol.preupdate finished in * seconds (glob) + 1970-01-01 00:00:00.000 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> exthook-update: echo hooked finished in * seconds (glob) + 1970-01-01 00:00:00.000 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> update exited 0 after * seconds (glob) + 1970-01-01 00:00:00.000 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> serve --no-profile --cmdserver chgunix --address $TESTTMP.chgsock/server.* --daemon-postexec 'chdir:/' (glob) (chg !) + 1970-01-01 00:00:00.000 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> blackbox -l 5 log rotation @@ -243,8 +243,8 @@ log rotation $ hg init blackboxtest3 $ cd blackboxtest3 $ hg blackbox - 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> init blackboxtest3 exited 0 after * seconds (glob) - 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox + 1970-01-01 00:00:00.000 bob @0000000000000000000000000000000000000000 (5000)> init blackboxtest3 exited 0 after * seconds (glob) + 1970-01-01 00:00:00.000 bob @0000000000000000000000000000000000000000 (5000)> blackbox $ mv .hg/blackbox.log .hg/blackbox.log- $ mkdir .hg/blackbox.log $ sed -e 's/\(.*test1.*\)/#\1/; s#\(.*commit2.*\)#os.rmdir(".hg/blackbox.log")\ @@ -306,16 +306,16 @@ log rotation result: 0 $ hg blackbox - 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> updating the branch cache - 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> updated branch cache (served) in * seconds (glob) - 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> wrote branch cache (served) with 1 labels and 1 nodes - 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> --debug commit -m commit2 -d 2000-01-02 foo exited 0 after *.?? seconds (glob) - 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> --debug log -r 0 - 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> writing .hg/cache/tags2-visible with 0 tags - 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> --debug log -r 0 exited 0 after *.?? seconds (glob) - 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> --debug log -r tip - 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 + 1970-01-01 00:00:00.000 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> updating the branch cache + 1970-01-01 00:00:00.000 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> updated branch cache (served) in * seconds (glob) + 1970-01-01 00:00:00.000 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> wrote branch cache (served) with 1 labels and 1 nodes + 1970-01-01 00:00:00.000 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> --debug commit -m commit2 -d 2000-01-02 foo exited 0 after *.?? seconds (glob) + 1970-01-01 00:00:00.000 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> --debug log -r 0 + 1970-01-01 00:00:00.000 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> writing .hg/cache/tags2-visible with 0 tags + 1970-01-01 00:00:00.000 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> --debug log -r 0 exited 0 after *.?? seconds (glob) + 1970-01-01 00:00:00.000 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> --debug log -r tip + 1970-01-01 00:00:00.000 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> --debug log -r tip exited 0 after *.?? seconds (glob) + 1970-01-01 00:00:00.000 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> blackbox Skip rotation if the .hg is read-only @@ -394,8 +394,8 @@ a '*' entry in blackbox.track is interpr (only look for entries with specific logged sources, otherwise this test is pretty brittle) $ hg blackbox | egrep '\[command(finish)?\]' - 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000) [commandfinish]> --config *blackbox.track=* --config *blackbox.logsource=True* init track_star exited 0 after * seconds (glob) - 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000) [command]> blackbox + 1970-01-01 00:00:00.000 bob @0000000000000000000000000000000000000000 (5000) [commandfinish]> --config *blackbox.track=* --config *blackbox.logsource=True* init track_star exited 0 after * seconds (glob) + 1970-01-01 00:00:00.000 bob @0000000000000000000000000000000000000000 (5000) [command]> blackbox $ cd $TESTTMP #if chg @@ -493,7 +493,7 @@ blackbox should work if repo.ui.log is n #endif $ head -1 .hg/blackbox.log - 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> ** Unknown exception encountered with possibly-broken third-party extension "mock" (version N/A) + 1970-01-01 00:00:00.000 bob @0000000000000000000000000000000000000000 (5000)> ** Unknown exception encountered with possibly-broken third-party extension "mock" (version N/A) $ tail -2 .hg/blackbox.log RuntimeError: raise diff --git a/tests/test-bookmarks-current.t b/tests/test-bookmarks-current.t --- a/tests/test-bookmarks-current.t +++ b/tests/test-bookmarks-current.t @@ -245,4 +245,4 @@ display how "{activebookmark}" template $ hg bookmarks --inactive $ hg bookmarks -ql . abort: no active bookmark - [255] + [10] diff --git a/tests/test-bookmarks-pushpull.t b/tests/test-bookmarks-pushpull.t --- a/tests/test-bookmarks-pushpull.t +++ b/tests/test-bookmarks-pushpull.t @@ -357,7 +357,7 @@ demand that one of the bookmarks is acti (leaving bookmark V) $ hg push -B . ../a abort: no active bookmark - [255] + [10] $ hg update -r V 0 files updated, 0 files merged, 1 files removed, 0 files unresolved (activating bookmark V) @@ -715,14 +715,15 @@ Update a bookmark right after the initia $ cat < ../lookuphook.py > """small extensions adding a hook after wireprotocol lookup to test race""" > import functools - > from mercurial import wireprotov1server, wireprotov2server + > from mercurial import wireprotov1server > > def wrappedlookup(orig, repo, *args, **kwargs): > ret = orig(repo, *args, **kwargs) > repo.hook(b'lookup') > return ret - > for table in [wireprotov1server.commands, wireprotov2server.COMMANDS]: - > table[b'lookup'].func = functools.partial(wrappedlookup, table[b'lookup'].func) + > + > table = wireprotov1server.commands + > table[b'lookup'].func = functools.partial(wrappedlookup, table[b'lookup'].func) > EOF $ cat < ../pull-race/.hg/hgrc > [extensions] diff --git a/tests/test-bookmarks.t b/tests/test-bookmarks.t --- a/tests/test-bookmarks.t +++ b/tests/test-bookmarks.t @@ -278,7 +278,7 @@ rename bookmark using . with no active b $ hg book -i rename-me $ hg book -m . renamed abort: no active bookmark - [255] + [10] $ hg up -q Y $ hg book -d rename-me @@ -298,7 +298,7 @@ delete bookmark using . with no active b $ hg book -i delete-me $ hg book -d . abort: no active bookmark - [255] + [10] $ hg up -q Y $ hg book -d delete-me diff --git a/tests/test-branch-change.t b/tests/test-branch-change.t --- a/tests/test-branch-change.t +++ b/tests/test-branch-change.t @@ -185,6 +185,7 @@ Make sure bookmark movement is correct changed branch on 2 changesets updating the branch cache invalid branch cache (served): tip differs + invalid branch cache (served.hidden): tip differs $ hg glog -r '(.^)::' @ 9:de1404b45a69 Added e @@ -211,7 +212,7 @@ Make sure phase handling is correct secret 11:38a9b2d53f98 foo 7:8a4729a5e2b8 wat 9:de1404b45a69 (inactive) - default 2:28ad74487de9 (inactive) + default 1:29becc82797a (inactive) $ hg branch secret diff --git a/tests/test-branch-option.t b/tests/test-branch-option.t --- a/tests/test-branch-option.t +++ b/tests/test-branch-option.t @@ -58,12 +58,12 @@ unknown branch and fallback $ hg in -qbz abort: unknown branch 'z' - [255] + [10] $ hg in -q ../branch#z 2:f25d57ab0566 $ hg out -qbz abort: unknown branch 'z' - [255] + [10] in rev c branch a diff --git a/tests/test-bundle.t b/tests/test-bundle.t --- a/tests/test-bundle.t +++ b/tests/test-bundle.t @@ -292,32 +292,56 @@ Cannot produce streaming clone bundles w packed1 is produced properly -#if reporevlogstore + +#if reporevlogstore rust $ hg -R test debugcreatestreamclonebundle packed.hg - writing 2664 bytes for 6 files (no-zstd !) - writing 2665 bytes for 6 files (zstd !) - bundle requirements: generaldelta, revlogv1, sparserevlog (no-rust !) - bundle requirements: generaldelta, persistent-nodemap, revlogv1, sparserevlog (rust !) + writing 2665 bytes for 6 files + bundle requirements: generaldelta, revlog-compression-zstd, revlogv1, sparserevlog + + $ f -B 64 --size --sha1 --hexdump packed.hg + packed.hg: size=2865, sha1=353d10311f4befa195d9a1ca4b8e26518115c702 + 0000: 48 47 53 31 55 4e 00 00 00 00 00 00 00 06 00 00 |HGS1UN..........| + 0010: 00 00 00 00 0a 69 00 3b 67 65 6e 65 72 61 6c 64 |.....i.;generald| + 0020: 65 6c 74 61 2c 72 65 76 6c 6f 67 2d 63 6f 6d 70 |elta,revlog-comp| + 0030: 72 65 73 73 69 6f 6e 2d 7a 73 74 64 2c 72 65 76 |ression-zstd,rev| + $ hg debugbundle --spec packed.hg + none-packed1;requirements%3Dgeneraldelta%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog +#endif + +#if reporevlogstore no-rust zstd + + $ hg -R test debugcreatestreamclonebundle packed.hg + writing 2665 bytes for 6 files + bundle requirements: generaldelta, revlog-compression-zstd, revlogv1, sparserevlog $ f -B 64 --size --sha1 --hexdump packed.hg - packed.hg: size=2840, sha1=12bf3eee3eb8a04c503ce2d29b48f0135c7edff5 (no-zstd !) - packed.hg: size=2841, sha1=8b645a65f49b0ae43042a9f3da56d4bfdf1c7f99 (zstd no-rust !) - packed.hg: size=2860, sha1=81d7a2e535892cda51e82c200f818de2cca828d3 (rust !) + packed.hg: size=2865, sha1=353d10311f4befa195d9a1ca4b8e26518115c702 0000: 48 47 53 31 55 4e 00 00 00 00 00 00 00 06 00 00 |HGS1UN..........| - 0010: 00 00 00 00 0a 68 00 23 67 65 6e 65 72 61 6c 64 |.....h.#generald| (no-zstd !) - 0020: 65 6c 74 61 2c 72 65 76 6c 6f 67 76 31 2c 73 70 |elta,revlogv1,sp| (no-zstd !) - 0030: 61 72 73 65 72 65 76 6c 6f 67 00 64 61 74 61 2f |arserevlog.data/| (no-zstd !) - 0010: 00 00 00 00 0a 69 00 23 67 65 6e 65 72 61 6c 64 |.....i.#generald| (zstd no-rust !) - 0020: 65 6c 74 61 2c 72 65 76 6c 6f 67 76 31 2c 73 70 |elta,revlogv1,sp| (zstd no-rust !) - 0030: 61 72 73 65 72 65 76 6c 6f 67 00 64 61 74 61 2f |arserevlog.data/| (zstd no-rust !) - 0010: 00 00 00 00 0a 69 00 36 67 65 6e 65 72 61 6c 64 |.....i.6generald| (rust !) - 0020: 65 6c 74 61 2c 70 65 72 73 69 73 74 65 6e 74 2d |elta,persistent-| (rust !) - 0030: 6e 6f 64 65 6d 61 70 2c 72 65 76 6c 6f 67 76 31 |nodemap,revlogv1| (rust !) + 0010: 00 00 00 00 0a 69 00 3b 67 65 6e 65 72 61 6c 64 |.....i.;generald| + 0020: 65 6c 74 61 2c 72 65 76 6c 6f 67 2d 63 6f 6d 70 |elta,revlog-comp| + 0030: 72 65 73 73 69 6f 6e 2d 7a 73 74 64 2c 72 65 76 |ression-zstd,rev| + $ hg debugbundle --spec packed.hg + none-packed1;requirements%3Dgeneraldelta%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog +#endif + +#if reporevlogstore no-rust no-zstd + $ hg -R test debugcreatestreamclonebundle packed.hg + writing 2664 bytes for 6 files + bundle requirements: generaldelta, revlogv1, sparserevlog + + $ f -B 64 --size --sha1 --hexdump packed.hg + packed.hg: size=2840, sha1=12bf3eee3eb8a04c503ce2d29b48f0135c7edff5 + 0000: 48 47 53 31 55 4e 00 00 00 00 00 00 00 06 00 00 |HGS1UN..........| + 0010: 00 00 00 00 0a 68 00 23 67 65 6e 65 72 61 6c 64 |.....h.#generald| + 0020: 65 6c 74 61 2c 72 65 76 6c 6f 67 76 31 2c 73 70 |elta,revlogv1,sp| + 0030: 61 72 73 65 72 65 76 6c 6f 67 00 64 61 74 61 2f |arserevlog.data/| $ hg debugbundle --spec packed.hg - none-packed1;requirements%3Dgeneraldelta%2Crevlogv1%2Csparserevlog (no-rust !) - none-packed1;requirements%3Dgeneraldelta%2Cpersistent-nodemap%2Crevlogv1%2Csparserevlog (rust !) + none-packed1;requirements%3Dgeneraldelta%2Crevlogv1%2Csparserevlog +#endif + +#if reporevlogstore generaldelta requirement is not listed in stream clone bundles unless used @@ -326,25 +350,66 @@ generaldelta requirement is not listed i $ touch foo $ hg -q commit -A -m initial $ cd .. + +#endif + +#if reporevlogstore rust + $ hg -R testnongd debugcreatestreamclonebundle packednongd.hg writing 301 bytes for 3 files - bundle requirements: revlogv1 (no-rust !) - bundle requirements: persistent-nodemap, revlogv1 (rust !) + bundle requirements: revlog-compression-zstd, revlogv1 + + $ f -B 64 --size --sha1 --hexdump packednongd.hg + packednongd.hg: size=407, sha1=0b8714422b785ba8eb98c916b41ffd5fb994c9b5 + 0000: 48 47 53 31 55 4e 00 00 00 00 00 00 00 03 00 00 |HGS1UN..........| + 0010: 00 00 00 00 01 2d 00 21 72 65 76 6c 6f 67 2d 63 |.....-.!revlog-c| + 0020: 6f 6d 70 72 65 73 73 69 6f 6e 2d 7a 73 74 64 2c |ompression-zstd,| + 0030: 72 65 76 6c 6f 67 76 31 00 64 61 74 61 2f 66 6f |revlogv1.data/fo| + + $ hg debugbundle --spec packednongd.hg + none-packed1;requirements%3Drevlog-compression-zstd%2Crevlogv1 + +#endif + +#if reporevlogstore no-rust zstd + + $ hg -R testnongd debugcreatestreamclonebundle packednongd.hg + writing 301 bytes for 3 files + bundle requirements: revlog-compression-zstd, revlogv1 $ f -B 64 --size --sha1 --hexdump packednongd.hg - packednongd.hg: size=383, sha1=1d9c230238edd5d38907100b729ba72b1831fe6f (no-rust !) - packednongd.hg: size=402, sha1=d3cc1417f0e8142cf9340aaaa520b660ad3ec3ea (rust !) + packednongd.hg: size=407, sha1=0b8714422b785ba8eb98c916b41ffd5fb994c9b5 0000: 48 47 53 31 55 4e 00 00 00 00 00 00 00 03 00 00 |HGS1UN..........| - 0010: 00 00 00 00 01 2d 00 09 72 65 76 6c 6f 67 76 31 |.....-..revlogv1| (no-rust !) - 0020: 00 64 61 74 61 2f 66 6f 6f 2e 69 00 36 34 0a 00 |.data/foo.i.64..| (no-rust !) - 0030: 01 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| (no-rust !) - 0010: 00 00 00 00 01 2d 00 1c 70 65 72 73 69 73 74 65 |.....-..persiste| (rust !) - 0020: 6e 74 2d 6e 6f 64 65 6d 61 70 2c 72 65 76 6c 6f |nt-nodemap,revlo| (rust !) - 0030: 67 76 31 00 64 61 74 61 2f 66 6f 6f 2e 69 00 36 |gv1.data/foo.i.6| (rust !) + 0010: 00 00 00 00 01 2d 00 21 72 65 76 6c 6f 67 2d 63 |.....-.!revlog-c| + 0020: 6f 6d 70 72 65 73 73 69 6f 6e 2d 7a 73 74 64 2c |ompression-zstd,| + 0030: 72 65 76 6c 6f 67 76 31 00 64 61 74 61 2f 66 6f |revlogv1.data/fo| $ hg debugbundle --spec packednongd.hg - none-packed1;requirements%3Drevlogv1 (no-rust !) - none-packed1;requirements%3Dpersistent-nodemap%2Crevlogv1 (rust !) + none-packed1;requirements%3Drevlog-compression-zstd%2Crevlogv1 + + +#endif + +#if reporevlogstore no-rust no-zstd + + $ hg -R testnongd debugcreatestreamclonebundle packednongd.hg + writing 301 bytes for 3 files + bundle requirements: revlogv1 + + $ f -B 64 --size --sha1 --hexdump packednongd.hg + packednongd.hg: size=383, sha1=1d9c230238edd5d38907100b729ba72b1831fe6f + 0000: 48 47 53 31 55 4e 00 00 00 00 00 00 00 03 00 00 |HGS1UN..........| + 0010: 00 00 00 00 01 2d 00 09 72 65 76 6c 6f 67 76 31 |.....-..revlogv1| + 0020: 00 64 61 74 61 2f 66 6f 6f 2e 69 00 36 34 0a 00 |.data/foo.i.64..| + 0030: 01 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| + + $ hg debugbundle --spec packednongd.hg + none-packed1;requirements%3Drevlogv1 + + +#endif + +#if reporevlogstore Warning emitted when packed bundles contain secret changesets @@ -355,11 +420,36 @@ Warning emitted when packed bundles cont $ hg phase --force --secret -r . $ cd .. +#endif + +#if reporevlogstore rust + $ hg -R testsecret debugcreatestreamclonebundle packedsecret.hg (warning: stream clone bundle will contain secret revisions) writing 301 bytes for 3 files - bundle requirements: generaldelta, revlogv1, sparserevlog (no-rust !) - bundle requirements: generaldelta, persistent-nodemap, revlogv1, sparserevlog (rust !) + bundle requirements: generaldelta, revlog-compression-zstd, revlogv1, sparserevlog + +#endif + +#if reporevlogstore no-rust zstd + + $ hg -R testsecret debugcreatestreamclonebundle packedsecret.hg + (warning: stream clone bundle will contain secret revisions) + writing 301 bytes for 3 files + bundle requirements: generaldelta, revlog-compression-zstd, revlogv1, sparserevlog + +#endif + +#if reporevlogstore no-rust no-zstd + + $ hg -R testsecret debugcreatestreamclonebundle packedsecret.hg + (warning: stream clone bundle will contain secret revisions) + writing 301 bytes for 3 files + bundle requirements: generaldelta, revlogv1, sparserevlog + +#endif + +#if reporevlogstore Unpacking packed1 bundles with "hg unbundle" isn't allowed @@ -716,7 +806,7 @@ test bundle with # in the filename (issu $ hg incoming '../test#bundle.hg' comparing with ../test abort: unknown revision 'bundle.hg' - [255] + [10] note that percent encoding is not handled: diff --git a/tests/test-bundle2-exchange.t b/tests/test-bundle2-exchange.t --- a/tests/test-bundle2-exchange.t +++ b/tests/test-bundle2-exchange.t @@ -1,13 +1,3 @@ -#testcases sshv1 sshv2 - -#if sshv2 - $ cat >> $HGRCPATH << EOF - > [experimental] - > sshpeer.advertise-v2 = true - > sshserver.support-v2 = true - > EOF -#endif - Test exchange of common information using bundle2 diff --git a/tests/test-bundle2-pushback.t b/tests/test-bundle2-pushback.t --- a/tests/test-bundle2-pushback.t +++ b/tests/test-bundle2-pushback.t @@ -1,13 +1,3 @@ -#testcases sshv1 sshv2 - -#if sshv2 - $ cat >> $HGRCPATH << EOF - > [experimental] - > sshpeer.advertise-v2 = true - > sshserver.support-v2 = true - > EOF -#endif - $ cat > bundle2.py << EOF > """A small extension to test bundle2 pushback parts. > Current bundle2 implementation doesn't provide a way to generate those diff --git a/tests/test-bundle2-remote-changegroup.t b/tests/test-bundle2-remote-changegroup.t --- a/tests/test-bundle2-remote-changegroup.t +++ b/tests/test-bundle2-remote-changegroup.t @@ -1,13 +1,3 @@ -#testcases sshv1 sshv2 - -#if sshv2 - $ cat >> $HGRCPATH << EOF - > [experimental] - > sshpeer.advertise-v2 = true - > sshserver.support-v2 = true - > EOF -#endif - Create an extension to test bundle2 remote-changegroup parts $ cat > bundle2.py << EOF diff --git a/tests/test-casecollision.t b/tests/test-casecollision.t --- a/tests/test-casecollision.t +++ b/tests/test-casecollision.t @@ -12,7 +12,7 @@ test file addition with colliding case ? A $ hg add --config ui.portablefilenames=abort A abort: possible case-folding collision for A - [255] + [20] $ hg st A a ? A diff --git a/tests/test-check-code.t b/tests/test-check-code.t --- a/tests/test-check-code.t +++ b/tests/test-check-code.t @@ -33,7 +33,7 @@ New errors are not allowed. Warnings are Skipping contrib/packaging/hgpackaging/wix.py it has no-che?k-code (glob) Skipping i18n/polib.py it has no-che?k-code (glob) Skipping mercurial/statprof.py it has no-che?k-code (glob) - Skipping tests/badserverext.py it has no-che?k-code (glob) + Skipping tests/testlib/badserverext.py it has no-che?k-code (glob) @commands in debugcommands.py should be in alphabetical order. diff --git a/tests/test-check-interfaces.py b/tests/test-check-interfaces.py --- a/tests/test-check-interfaces.py +++ b/tests/test-check-interfaces.py @@ -39,7 +39,6 @@ from mercurial import ( wireprotoserver, wireprototypes, wireprotov1peer, - wireprotov2server, ) testdir = os.path.dirname(__file__) @@ -129,9 +128,6 @@ def main(): ziverify.verifyClass(repository.ipeerbase, httppeer.httppeer) checkzobject(httppeer.httppeer(None, None, None, dummyopener(), None, None)) - ziverify.verifyClass(repository.ipeerv2, httppeer.httpv2peer) - checkzobject(httppeer.httpv2peer(None, b'', b'', None, None, None)) - ziverify.verifyClass(repository.ipeerbase, localrepo.localpeer) checkzobject(localrepo.localpeer(dummyrepo())) @@ -158,19 +154,6 @@ def main(): ) ) - ziverify.verifyClass(repository.ipeerbase, sshpeer.sshv2peer) - checkzobject( - sshpeer.sshv2peer( - ui, - b'ssh://localhost/foo', - b'', - dummypipe(), - dummypipe(), - None, - None, - ) - ) - ziverify.verifyClass(repository.ipeerbase, bundlerepo.bundlepeer) checkzobject(bundlerepo.bundlepeer(dummyrepo())) @@ -193,26 +176,15 @@ def main(): wireprototypes.baseprotocolhandler, wireprotoserver.sshv1protocolhandler ) ziverify.verifyClass( - wireprototypes.baseprotocolhandler, wireprotoserver.sshv2protocolhandler - ) - ziverify.verifyClass( wireprototypes.baseprotocolhandler, wireprotoserver.httpv1protocolhandler, ) - ziverify.verifyClass( - wireprototypes.baseprotocolhandler, - wireprotov2server.httpv2protocolhandler, - ) sshv1 = wireprotoserver.sshv1protocolhandler(None, None, None) checkzobject(sshv1) - sshv2 = wireprotoserver.sshv2protocolhandler(None, None, None) - checkzobject(sshv2) httpv1 = wireprotoserver.httpv1protocolhandler(None, None, None) checkzobject(httpv1) - httpv2 = wireprotov2server.httpv2protocolhandler(None, None) - checkzobject(httpv2) ziverify.verifyClass(repository.ifilestorage, filelog.filelog) ziverify.verifyClass(repository.imanifestdict, manifest.manifestdict) diff --git a/tests/test-check-module-imports.t b/tests/test-check-module-imports.t --- a/tests/test-check-module-imports.t +++ b/tests/test-check-module-imports.t @@ -41,4 +41,5 @@ will likely receive warnings about a dir > -X tests/test-demandimport.py \ > -X tests/test-imports-checker.t \ > -X tests/test-verify-repo-operations.py \ + > -X tests/test-extension.t \ > | sed 's-\\-/-g' | "$PYTHON" "$import_checker" - diff --git a/tests/test-check-pytype.t b/tests/test-check-pytype.t --- a/tests/test-check-pytype.t +++ b/tests/test-check-pytype.t @@ -10,94 +10,63 @@ endeavor to empty this list out over tim 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 diff --git a/tests/test-clone-stream-format.t b/tests/test-clone-stream-format.t new file mode 100644 --- /dev/null +++ b/tests/test-clone-stream-format.t @@ -0,0 +1,435 @@ +This file contains tests case that deal with format change accross stream clone + +#require serve no-reposimplestore no-chg + +#testcases stream-legacy stream-bundle2 + + $ cat << EOF >> $HGRCPATH + > [storage] + > revlog.persistent-nodemap.slow-path=allow + > EOF + +#if stream-legacy + $ cat << EOF >> $HGRCPATH + > [server] + > bundle2.stream = no + > EOF +#endif + +Initialize repository + + $ hg init server --config format.use-share-safe=yes --config format.use-persistent-nodemap=yes + $ cd server + $ sh $TESTDIR/testlib/stream_clone_setup.sh + adding 00changelog-ab349180a0405010.nd + adding 00changelog.d + adding 00changelog.i + adding 00changelog.n + adding 00manifest.d + adding 00manifest.i + adding container/isam-build-centos7/bazel-coverage-generator-sandboxfs-compatibility-0758e3e4f6057904d44399bd666faba9e7f40686.patch + adding data/foo.d + adding data/foo.i + adding data/foo.n + adding data/undo.babar + adding data/undo.d + adding data/undo.foo.d + adding data/undo.foo.i + adding data/undo.foo.n + adding data/undo.i + adding data/undo.n + adding data/undo.py + adding foo.d + adding foo.i + adding foo.n + adding meta/foo.d + adding meta/foo.i + adding meta/foo.n + adding meta/undo.babar + adding meta/undo.d + adding meta/undo.foo.d + adding meta/undo.foo.i + adding meta/undo.foo.n + adding meta/undo.i + adding meta/undo.n + adding meta/undo.py + adding savanah/foo.d + adding savanah/foo.i + adding savanah/foo.n + adding savanah/undo.babar + adding savanah/undo.d + adding savanah/undo.foo.d + adding savanah/undo.foo.i + adding savanah/undo.foo.n + adding savanah/undo.i + adding savanah/undo.n + adding savanah/undo.py + adding store/C\xc3\xa9lesteVille_is_a_Capital_City (esc) + adding store/foo.d + adding store/foo.i + adding store/foo.n + adding store/undo.babar + adding store/undo.d + adding store/undo.foo.d + adding store/undo.foo.i + adding store/undo.foo.n + adding store/undo.i + adding store/undo.n + adding store/undo.py + adding undo.babar + adding undo.d + adding undo.foo.d + adding undo.foo.i + adding undo.foo.n + adding undo.i + adding undo.n + adding undo.py + $ hg debugbuilddag .+5000 --from-existing + $ ls -1 .hg/store/00changelog* + .hg/store/00changelog-*.nd (glob) + .hg/store/00changelog.d + .hg/store/00changelog.i + .hg/store/00changelog.n + $ cd .. + + +Test streaming from/to repository without a store: +================================================== + + $ hg clone --pull --config format.usestore=no server server-no-store + requesting all changes + adding changesets + adding manifests + adding file changes + added 5004 changesets with 1088 changes to 1088 files (+1 heads) + new changesets 96ee1d7354c4:06ddac466af5 + updating to branch default + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg verify -R server-no-store + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + checked 5004 changesets with 1088 changes to 1088 files + $ hg -R server serve -p $HGPORT -d --pid-file=hg-1.pid --error errors-1.txt + $ cat hg-1.pid > $DAEMON_PIDS + $ hg -R server-no-store serve -p $HGPORT2 -d --pid-file=hg-2.pid --error errors-2.txt + $ cat hg-2.pid >> $DAEMON_PIDS + $ hg debugrequires -R server | grep store + store + $ hg debugrequires -R server-no-store | grep store + [1] + +store → no-store cloning + + $ hg clone --quiet --stream -U http://localhost:$HGPORT clone-remove-store --config format.usestore=no + $ cat errors-1.txt + $ hg -R clone-remove-store verify + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + checked 5004 changesets with 1088 changes to 1088 files + $ hg debugrequires -R clone-remove-store | grep store + [1] + + +no-store → store cloning + + $ hg clone --quiet --stream -U http://localhost:$HGPORT2 clone-add-store --config format.usestore=yes + $ cat errors-2.txt + $ hg -R clone-add-store verify + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + checked 5004 changesets with 1088 changes to 1088 files + $ hg debugrequires -R clone-add-store | grep store + store + + + $ killdaemons.py + + +Test streaming from/to repository without a fncache +=================================================== + + $ rm hg-*.pid errors-*.txt + $ hg clone --pull --config format.usefncache=no server server-no-fncache + requesting all changes + adding changesets + adding manifests + adding file changes + added 5004 changesets with 1088 changes to 1088 files (+1 heads) + new changesets 96ee1d7354c4:06ddac466af5 + updating to branch default + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg verify -R server-no-fncache + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + checked 5004 changesets with 1088 changes to 1088 files + $ hg -R server serve -p $HGPORT -d --pid-file=hg-1.pid --error errors-1.txt + $ cat hg-1.pid > $DAEMON_PIDS + $ hg -R server-no-fncache serve -p $HGPORT2 -d --pid-file=hg-2.pid --error errors-2.txt + $ cat hg-2.pid >> $DAEMON_PIDS + $ hg debugrequires -R server | grep fncache + fncache + $ hg debugrequires -R server-no-fncache | grep fncache + [1] + +fncache → no-fncache cloning + + $ hg clone --quiet --stream -U http://localhost:$HGPORT clone-remove-fncache --config format.usefncache=no + $ cat errors-1.txt + $ hg -R clone-remove-fncache verify + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + checked 5004 changesets with 1088 changes to 1088 files + $ hg debugrequires -R clone-remove-fncache | grep fncache + [1] + + +no-fncache → fncache cloning + + $ hg clone --quiet --stream -U http://localhost:$HGPORT2 clone-add-fncache --config format.usefncache=yes + $ cat errors-2.txt + $ hg -R clone-add-fncache verify + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + checked 5004 changesets with 1088 changes to 1088 files + $ hg debugrequires -R clone-add-fncache | grep fncache + fncache + + + $ killdaemons.py + + + +Test streaming from/to repository without a dotencode +=================================================== + + $ rm hg-*.pid errors-*.txt + $ hg clone --pull --config format.dotencode=no server server-no-dotencode + requesting all changes + adding changesets + adding manifests + adding file changes + added 5004 changesets with 1088 changes to 1088 files (+1 heads) + new changesets 96ee1d7354c4:06ddac466af5 + updating to branch default + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg verify -R server-no-dotencode + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + checked 5004 changesets with 1088 changes to 1088 files + $ hg -R server serve -p $HGPORT -d --pid-file=hg-1.pid --error errors-1.txt + $ cat hg-1.pid > $DAEMON_PIDS + $ hg -R server-no-dotencode serve -p $HGPORT2 -d --pid-file=hg-2.pid --error errors-2.txt + $ cat hg-2.pid >> $DAEMON_PIDS + $ hg debugrequires -R server | grep dotencode + dotencode + $ hg debugrequires -R server-no-dotencode | grep dotencode + [1] + +dotencode → no-dotencode cloning + + $ hg clone --quiet --stream -U http://localhost:$HGPORT clone-remove-dotencode --config format.dotencode=no + $ cat errors-1.txt + $ hg -R clone-remove-dotencode verify + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + checked 5004 changesets with 1088 changes to 1088 files + $ hg debugrequires -R clone-remove-dotencode | grep dotencode + [1] + + +no-dotencode → dotencode cloning + + $ hg clone --quiet --stream -U http://localhost:$HGPORT2 clone-add-dotencode --config format.dotencode=yes + $ cat errors-2.txt + $ hg -R clone-add-dotencode verify + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + checked 5004 changesets with 1088 changes to 1088 files + $ hg debugrequires -R clone-add-dotencode | grep dotencode + dotencode + + + $ killdaemons.py + +Cloning from a share +-------------------- + +We should be able to clone from a "share" repository, it will use the source store for streaming. + +The resulting clone should not use share. + + $ rm hg-*.pid errors-*.txt + $ hg share --config extensions.share= server server-share -U + $ hg -R server-share serve -p $HGPORT -d --pid-file=hg-1.pid --error errors-1.txt + $ cat hg-1.pid > $DAEMON_PIDS + + $ hg clone --quiet --stream -U http://localhost:$HGPORT clone-from-share + $ hg -R clone-from-share verify + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + checked 5004 changesets with 1088 changes to 1088 files + $ hg debugrequires -R clone-from-share | egrep 'share$' + [1] + + $ killdaemons.py + +Test streaming from/to repository without a share-safe +====================================================== + + $ rm hg-*.pid errors-*.txt + $ hg clone --pull --config format.use-share-safe=no server server-no-share-safe + requesting all changes + adding changesets + adding manifests + adding file changes + added 5004 changesets with 1088 changes to 1088 files (+1 heads) + new changesets 96ee1d7354c4:06ddac466af5 + updating to branch default + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg verify -R server-no-share-safe + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + checked 5004 changesets with 1088 changes to 1088 files + $ hg -R server serve -p $HGPORT -d --pid-file=hg-1.pid --error errors-1.txt + $ cat hg-1.pid > $DAEMON_PIDS + $ hg -R server-no-share-safe serve -p $HGPORT2 -d --pid-file=hg-2.pid --error errors-2.txt + $ cat hg-2.pid >> $DAEMON_PIDS + $ hg debugrequires -R server | grep share-safe + share-safe + $ hg debugrequires -R server-no-share-safe | grep share-safe + [1] + +share-safe → no-share-safe cloning + + $ hg clone --quiet --stream -U http://localhost:$HGPORT clone-remove-share-safe --config format.use-share-safe=no + $ cat errors-1.txt + $ hg -R clone-remove-share-safe verify + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + checked 5004 changesets with 1088 changes to 1088 files + $ hg debugrequires -R clone-remove-share-safe | grep share-safe + [1] + + +no-share-safe → share-safe cloning + + $ hg clone --quiet --stream -U http://localhost:$HGPORT2 clone-add-share-safe --config format.use-share-safe=yes + $ cat errors-2.txt + $ hg -R clone-add-share-safe verify + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + checked 5004 changesets with 1088 changes to 1088 files + $ hg debugrequires -R clone-add-share-safe | grep share-safe + share-safe + + + $ killdaemons.py + + +Test streaming from/to repository without a persistent-nodemap +============================================================== + +persistent nodemap affects revlog, but they are easy to generate locally, so we allow it to be changed over a stream clone + + $ rm hg-*.pid errors-*.txt + $ hg clone --pull --config format.use-persistent-nodemap=no server server-no-persistent-nodemap + requesting all changes + adding changesets + adding manifests + adding file changes + added 5004 changesets with 1088 changes to 1088 files (+1 heads) + new changesets 96ee1d7354c4:06ddac466af5 + updating to branch default + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg verify -R server-no-persistent-nodemap + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + checked 5004 changesets with 1088 changes to 1088 files + $ hg -R server serve -p $HGPORT -d --pid-file=hg-1.pid --error errors-1.txt + $ cat hg-1.pid > $DAEMON_PIDS + $ hg -R server-no-persistent-nodemap serve -p $HGPORT2 -d --pid-file=hg-2.pid --error errors-2.txt + $ cat hg-2.pid >> $DAEMON_PIDS + $ hg debugrequires -R server | grep persistent-nodemap + persistent-nodemap + $ hg debugrequires -R server-no-persistent-nodemap | grep persistent-nodemap + [1] + $ ls -1 server/.hg/store/00changelog* + server/.hg/store/00changelog-*.nd (glob) + server/.hg/store/00changelog.d + server/.hg/store/00changelog.i + server/.hg/store/00changelog.n + $ ls -1 server-no-persistent-nodemap/.hg/store/00changelog* + server-no-persistent-nodemap/.hg/store/00changelog.d + server-no-persistent-nodemap/.hg/store/00changelog.i + +persistent-nodemap → no-persistent-nodemap cloning + + $ hg clone --quiet --stream -U http://localhost:$HGPORT clone-remove-persistent-nodemap --config format.use-persistent-nodemap=no + $ cat errors-1.txt + $ hg -R clone-remove-persistent-nodemap verify + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + checked 5004 changesets with 1088 changes to 1088 files + $ hg debugrequires -R clone-remove-persistent-nodemap | grep persistent-nodemap + [1] + +The persistent-nodemap files should no longer exists + + $ ls -1 clone-remove-persistent-nodemap/.hg/store/00changelog* + clone-remove-persistent-nodemap/.hg/store/00changelog.d + clone-remove-persistent-nodemap/.hg/store/00changelog.i + + +no-persistent-nodemap → persistent-nodemap cloning + + $ hg clone --quiet --stream -U http://localhost:$HGPORT2 clone-add-persistent-nodemap --config format.use-persistent-nodemap=yes + $ cat errors-2.txt + $ hg -R clone-add-persistent-nodemap verify + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + checked 5004 changesets with 1088 changes to 1088 files + $ hg debugrequires -R clone-add-persistent-nodemap | grep persistent-nodemap + persistent-nodemap + +The persistent-nodemap files should exists + + $ ls -1 clone-add-persistent-nodemap/.hg/store/00changelog* + clone-add-persistent-nodemap/.hg/store/00changelog-*.nd (glob) + clone-add-persistent-nodemap/.hg/store/00changelog.d + clone-add-persistent-nodemap/.hg/store/00changelog.i + clone-add-persistent-nodemap/.hg/store/00changelog.n + + + $ killdaemons.py diff --git a/tests/test-clone-stream.t b/tests/test-clone-stream.t --- a/tests/test-clone-stream.t +++ b/tests/test-clone-stream.t @@ -10,98 +10,10 @@ #endif Initialize repository -the status call is to check for issue5130 $ hg init server $ cd server - $ touch foo - $ hg -q commit -A -m initial - >>> for i in range(1024): - ... with open(str(i), 'wb') as fh: - ... fh.write(b"%d" % i) and None - $ hg -q commit -A -m 'add a lot of files' - $ hg st - -add files with "tricky" name: - - $ echo foo > 00changelog.i - $ echo foo > 00changelog.d - $ echo foo > 00changelog.n - $ echo foo > 00changelog-ab349180a0405010.nd - $ echo foo > 00manifest.i - $ echo foo > 00manifest.d - $ echo foo > foo.i - $ echo foo > foo.d - $ echo foo > foo.n - $ echo foo > undo.py - $ echo foo > undo.i - $ echo foo > undo.d - $ echo foo > undo.n - $ echo foo > undo.foo.i - $ echo foo > undo.foo.d - $ echo foo > undo.foo.n - $ echo foo > undo.babar - $ mkdir savanah - $ echo foo > savanah/foo.i - $ echo foo > savanah/foo.d - $ echo foo > savanah/foo.n - $ echo foo > savanah/undo.py - $ echo foo > savanah/undo.i - $ echo foo > savanah/undo.d - $ echo foo > savanah/undo.n - $ echo foo > savanah/undo.foo.i - $ echo foo > savanah/undo.foo.d - $ echo foo > savanah/undo.foo.n - $ echo foo > savanah/undo.babar - $ mkdir data - $ echo foo > data/foo.i - $ echo foo > data/foo.d - $ echo foo > data/foo.n - $ echo foo > data/undo.py - $ echo foo > data/undo.i - $ echo foo > data/undo.d - $ echo foo > data/undo.n - $ echo foo > data/undo.foo.i - $ echo foo > data/undo.foo.d - $ echo foo > data/undo.foo.n - $ echo foo > data/undo.babar - $ mkdir meta - $ echo foo > meta/foo.i - $ echo foo > meta/foo.d - $ echo foo > meta/foo.n - $ echo foo > meta/undo.py - $ echo foo > meta/undo.i - $ echo foo > meta/undo.d - $ echo foo > meta/undo.n - $ echo foo > meta/undo.foo.i - $ echo foo > meta/undo.foo.d - $ echo foo > meta/undo.foo.n - $ echo foo > meta/undo.babar - $ mkdir store - $ echo foo > store/foo.i - $ echo foo > store/foo.d - $ echo foo > store/foo.n - $ echo foo > store/undo.py - $ echo foo > store/undo.i - $ echo foo > store/undo.d - $ echo foo > store/undo.n - $ echo foo > store/undo.foo.i - $ echo foo > store/undo.foo.d - $ echo foo > store/undo.foo.n - $ echo foo > store/undo.babar - -Name with special characters - - $ echo foo > store/CélesteVille_is_a_Capital_City - -name causing issue6581 - - $ mkdir -p container/isam-build-centos7/ - $ touch container/isam-build-centos7/bazel-coverage-generator-sandboxfs-compatibility-0758e3e4f6057904d44399bd666faba9e7f40686.patch - -Add all that - - $ hg add . + $ sh $TESTDIR/testlib/stream_clone_setup.sh adding 00changelog-ab349180a0405010.nd adding 00changelog.d adding 00changelog.i @@ -165,7 +77,7 @@ Add all that adding undo.i adding undo.n adding undo.py - $ hg ci -m 'add files with "tricky" name' + $ hg --config server.uncompressed=false serve -p $HGPORT -d --pid-file=hg.pid $ cat hg.pid > $DAEMON_PIDS $ cd .. @@ -384,65 +296,65 @@ getbundle requests with stream=1 are unc #if no-zstd no-rust $ f --size --hex --bytes 256 body - body: size=119153 + body: size=119123 0000: 04 6e 6f 6e 65 48 47 32 30 00 00 00 00 00 00 00 |.noneHG20.......| - 0010: 80 07 53 54 52 45 41 4d 32 00 00 00 00 03 00 09 |..STREAM2.......| - 0020: 06 09 04 0c 44 62 79 74 65 63 6f 75 6e 74 31 30 |....Dbytecount10| + 0010: 62 07 53 54 52 45 41 4d 32 00 00 00 00 03 00 09 |b.STREAM2.......| + 0020: 06 09 04 0c 26 62 79 74 65 63 6f 75 6e 74 31 30 |....&bytecount10| 0030: 34 31 31 35 66 69 6c 65 63 6f 75 6e 74 31 30 39 |4115filecount109| - 0040: 33 72 65 71 75 69 72 65 6d 65 6e 74 73 64 6f 74 |3requirementsdot| - 0050: 65 6e 63 6f 64 65 25 32 43 66 6e 63 61 63 68 65 |encode%2Cfncache| - 0060: 25 32 43 67 65 6e 65 72 61 6c 64 65 6c 74 61 25 |%2Cgeneraldelta%| - 0070: 32 43 72 65 76 6c 6f 67 76 31 25 32 43 73 70 61 |2Crevlogv1%2Cspa| - 0080: 72 73 65 72 65 76 6c 6f 67 25 32 43 73 74 6f 72 |rserevlog%2Cstor| - 0090: 65 00 00 80 00 73 08 42 64 61 74 61 2f 30 2e 69 |e....s.Bdata/0.i| - 00a0: 00 03 00 01 00 00 00 00 00 00 00 02 00 00 00 01 |................| - 00b0: 00 00 00 00 00 00 00 01 ff ff ff ff ff ff ff ff |................| - 00c0: 80 29 63 a0 49 d3 23 87 bf ce fe 56 67 92 67 2c |.)c.I.#....Vg.g,| - 00d0: 69 d1 ec 39 00 00 00 00 00 00 00 00 00 00 00 00 |i..9............| - 00e0: 75 30 73 26 45 64 61 74 61 2f 30 30 63 68 61 6e |u0s&Edata/00chan| - 00f0: 67 65 6c 6f 67 2d 61 62 33 34 39 31 38 30 61 30 |gelog-ab349180a0| + 0040: 33 72 65 71 75 69 72 65 6d 65 6e 74 73 67 65 6e |3requirementsgen| + 0050: 65 72 61 6c 64 65 6c 74 61 25 32 43 72 65 76 6c |eraldelta%2Crevl| + 0060: 6f 67 76 31 25 32 43 73 70 61 72 73 65 72 65 76 |ogv1%2Csparserev| + 0070: 6c 6f 67 00 00 80 00 73 08 42 64 61 74 61 2f 30 |log....s.Bdata/0| + 0080: 2e 69 00 03 00 01 00 00 00 00 00 00 00 02 00 00 |.i..............| + 0090: 00 01 00 00 00 00 00 00 00 01 ff ff ff ff ff ff |................| + 00a0: ff ff 80 29 63 a0 49 d3 23 87 bf ce fe 56 67 92 |...)c.I.#....Vg.| + 00b0: 67 2c 69 d1 ec 39 00 00 00 00 00 00 00 00 00 00 |g,i..9..........| + 00c0: 00 00 75 30 73 26 45 64 61 74 61 2f 30 30 63 68 |..u0s&Edata/00ch| + 00d0: 61 6e 67 65 6c 6f 67 2d 61 62 33 34 39 31 38 30 |angelog-ab349180| + 00e0: 61 30 34 30 35 30 31 30 2e 6e 64 2e 69 00 03 00 |a0405010.nd.i...| + 00f0: 01 00 00 00 00 00 00 00 05 00 00 00 04 00 00 00 |................| #endif #if zstd no-rust $ f --size --hex --bytes 256 body - body: size=116340 (no-bigendian !) + body: size=116310 body: size=116335 (bigendian !) 0000: 04 6e 6f 6e 65 48 47 32 30 00 00 00 00 00 00 00 |.noneHG20.......| - 0010: 9a 07 53 54 52 45 41 4d 32 00 00 00 00 03 00 09 |..STREAM2.......| - 0020: 06 09 04 0c 5e 62 79 74 65 63 6f 75 6e 74 31 30 |....^bytecount10| + 0010: 7c 07 53 54 52 45 41 4d 32 00 00 00 00 03 00 09 ||.STREAM2.......| + 0020: 06 09 04 0c 40 62 79 74 65 63 6f 75 6e 74 31 30 |....@bytecount10| 0030: 31 32 37 36 66 69 6c 65 63 6f 75 6e 74 31 30 39 |1276filecount109| (no-bigendian !) 0030: 31 32 37 31 66 69 6c 65 63 6f 75 6e 74 31 30 39 |1271filecount109| (bigendian !) - 0040: 33 72 65 71 75 69 72 65 6d 65 6e 74 73 64 6f 74 |3requirementsdot| - 0050: 65 6e 63 6f 64 65 25 32 43 66 6e 63 61 63 68 65 |encode%2Cfncache| - 0060: 25 32 43 67 65 6e 65 72 61 6c 64 65 6c 74 61 25 |%2Cgeneraldelta%| - 0070: 32 43 72 65 76 6c 6f 67 2d 63 6f 6d 70 72 65 73 |2Crevlog-compres| - 0080: 73 69 6f 6e 2d 7a 73 74 64 25 32 43 72 65 76 6c |sion-zstd%2Crevl| - 0090: 6f 67 76 31 25 32 43 73 70 61 72 73 65 72 65 76 |ogv1%2Csparserev| - 00a0: 6c 6f 67 25 32 43 73 74 6f 72 65 00 00 80 00 73 |log%2Cstore....s| - 00b0: 08 42 64 61 74 61 2f 30 2e 69 00 03 00 01 00 00 |.Bdata/0.i......| - 00c0: 00 00 00 00 00 02 00 00 00 01 00 00 00 00 00 00 |................| - 00d0: 00 01 ff ff ff ff ff ff ff ff 80 29 63 a0 49 d3 |...........)c.I.| - 00e0: 23 87 bf ce fe 56 67 92 67 2c 69 d1 ec 39 00 00 |#....Vg.g,i..9..| - 00f0: 00 00 00 00 00 00 00 00 00 00 75 30 73 26 45 64 |..........u0s&Ed| + 0040: 33 72 65 71 75 69 72 65 6d 65 6e 74 73 67 65 6e |3requirementsgen| + 0050: 65 72 61 6c 64 65 6c 74 61 25 32 43 72 65 76 6c |eraldelta%2Crevl| + 0060: 6f 67 2d 63 6f 6d 70 72 65 73 73 69 6f 6e 2d 7a |og-compression-z| + 0070: 73 74 64 25 32 43 72 65 76 6c 6f 67 76 31 25 32 |std%2Crevlogv1%2| + 0080: 43 73 70 61 72 73 65 72 65 76 6c 6f 67 00 00 80 |Csparserevlog...| + 0090: 00 73 08 42 64 61 74 61 2f 30 2e 69 00 03 00 01 |.s.Bdata/0.i....| + 00a0: 00 00 00 00 00 00 00 02 00 00 00 01 00 00 00 00 |................| + 00b0: 00 00 00 01 ff ff ff ff ff ff ff ff 80 29 63 a0 |.............)c.| + 00c0: 49 d3 23 87 bf ce fe 56 67 92 67 2c 69 d1 ec 39 |I.#....Vg.g,i..9| + 00d0: 00 00 00 00 00 00 00 00 00 00 00 00 75 30 73 26 |............u0s&| + 00e0: 45 64 61 74 61 2f 30 30 63 68 61 6e 67 65 6c 6f |Edata/00changelo| + 00f0: 67 2d 61 62 33 34 39 31 38 30 61 30 34 30 35 30 |g-ab349180a04050| #endif #if zstd rust no-dirstate-v2 $ f --size --hex --bytes 256 body - body: size=116361 + body: size=116310 0000: 04 6e 6f 6e 65 48 47 32 30 00 00 00 00 00 00 00 |.noneHG20.......| - 0010: af 07 53 54 52 45 41 4d 32 00 00 00 00 03 00 09 |..STREAM2.......| - 0020: 06 09 04 0c 73 62 79 74 65 63 6f 75 6e 74 31 30 |....sbytecount10| + 0010: 7c 07 53 54 52 45 41 4d 32 00 00 00 00 03 00 09 ||.STREAM2.......| + 0020: 06 09 04 0c 40 62 79 74 65 63 6f 75 6e 74 31 30 |....@bytecount10| 0030: 31 32 37 36 66 69 6c 65 63 6f 75 6e 74 31 30 39 |1276filecount109| - 0040: 33 72 65 71 75 69 72 65 6d 65 6e 74 73 64 6f 74 |3requirementsdot| - 0050: 65 6e 63 6f 64 65 25 32 43 66 6e 63 61 63 68 65 |encode%2Cfncache| - 0060: 25 32 43 67 65 6e 65 72 61 6c 64 65 6c 74 61 25 |%2Cgeneraldelta%| - 0070: 32 43 70 65 72 73 69 73 74 65 6e 74 2d 6e 6f 64 |2Cpersistent-nod| - 0080: 65 6d 61 70 25 32 43 72 65 76 6c 6f 67 2d 63 6f |emap%2Crevlog-co| - 0090: 6d 70 72 65 73 73 69 6f 6e 2d 7a 73 74 64 25 32 |mpression-zstd%2| - 00a0: 43 72 65 76 6c 6f 67 76 31 25 32 43 73 70 61 72 |Crevlogv1%2Cspar| - 00b0: 73 65 72 65 76 6c 6f 67 25 32 43 73 74 6f 72 65 |serevlog%2Cstore| - 00c0: 00 00 80 00 73 08 42 64 61 74 61 2f 30 2e 69 00 |....s.Bdata/0.i.| - 00d0: 03 00 01 00 00 00 00 00 00 00 02 00 00 00 01 00 |................| - 00e0: 00 00 00 00 00 00 01 ff ff ff ff ff ff ff ff 80 |................| - 00f0: 29 63 a0 49 d3 23 87 bf ce fe 56 67 92 67 2c 69 |)c.I.#....Vg.g,i| + 0040: 33 72 65 71 75 69 72 65 6d 65 6e 74 73 67 65 6e |3requirementsgen| + 0050: 65 72 61 6c 64 65 6c 74 61 25 32 43 72 65 76 6c |eraldelta%2Crevl| + 0060: 6f 67 2d 63 6f 6d 70 72 65 73 73 69 6f 6e 2d 7a |og-compression-z| + 0070: 73 74 64 25 32 43 72 65 76 6c 6f 67 76 31 25 32 |std%2Crevlogv1%2| + 0080: 43 73 70 61 72 73 65 72 65 76 6c 6f 67 00 00 80 |Csparserevlog...| + 0090: 00 73 08 42 64 61 74 61 2f 30 2e 69 00 03 00 01 |.s.Bdata/0.i....| + 00a0: 00 00 00 00 00 00 00 02 00 00 00 01 00 00 00 00 |................| + 00b0: 00 00 00 01 ff ff ff ff ff ff ff ff 80 29 63 a0 |.............)c.| + 00c0: 49 d3 23 87 bf ce fe 56 67 92 67 2c 69 d1 ec 39 |I.#....Vg.g,i..9| + 00d0: 00 00 00 00 00 00 00 00 00 00 00 00 75 30 73 26 |............u0s&| + 00e0: 45 64 61 74 61 2f 30 30 63 68 61 6e 67 65 6c 6f |Edata/00changelo| + 00f0: 67 2d 61 62 33 34 39 31 38 30 61 30 34 30 35 30 |g-ab349180a04050| #endif #if zstd dirstate-v2 $ f --size --hex --bytes 256 body diff --git a/tests/test-clone.t b/tests/test-clone.t --- a/tests/test-clone.t +++ b/tests/test-clone.t @@ -1,13 +1,3 @@ -#testcases sshv1 sshv2 - -#if sshv2 - $ cat >> $HGRCPATH << EOF - > [experimental] - > sshpeer.advertise-v2 = true - > sshserver.support-v2 = true - > EOF -#endif - Prepare repo a: $ hg init a @@ -1206,14 +1196,12 @@ SEC: check for unsafe ssh url #if windows $ hg clone "ssh://%26touch%20owned%20/" --debug running sh -c "read l; read l; read l" "&touch owned " "hg -R . serve --stdio" - sending upgrade request: * proto=exp-ssh-v2-0003 (glob) (sshv2 !) sending hello command sending between command abort: no suitable response from remote hg [255] $ hg clone "ssh://example.com:%26touch%20owned%20/" --debug running sh -c "read l; read l; read l" -p "&touch owned " example.com "hg -R . serve --stdio" - sending upgrade request: * proto=exp-ssh-v2-0003 (glob) (sshv2 !) sending hello command sending between command abort: no suitable response from remote hg @@ -1221,14 +1209,12 @@ SEC: check for unsafe ssh url #else $ hg clone "ssh://%3btouch%20owned%20/" --debug running sh -c "read l; read l; read l" ';touch owned ' 'hg -R . serve --stdio' - sending upgrade request: * proto=exp-ssh-v2-0003 (glob) (sshv2 !) sending hello command sending between command abort: no suitable response from remote hg [255] $ hg clone "ssh://example.com:%3btouch%20owned%20/" --debug running sh -c "read l; read l; read l" -p ';touch owned ' example.com 'hg -R . serve --stdio' - sending upgrade request: * proto=exp-ssh-v2-0003 (glob) (sshv2 !) sending hello command sending between command abort: no suitable response from remote hg @@ -1237,7 +1223,6 @@ SEC: check for unsafe ssh url $ hg clone "ssh://v-alid.example.com/" --debug running sh -c "read l; read l; read l" v-alid\.example\.com ['"]hg -R \. serve --stdio['"] (re) - sending upgrade request: * proto=exp-ssh-v2-0003 (glob) (sshv2 !) sending hello command sending between command abort: no suitable response from remote hg diff --git a/tests/test-clonebundles.t b/tests/test-clonebundles.t --- a/tests/test-clonebundles.t +++ b/tests/test-clonebundles.t @@ -279,8 +279,9 @@ Stream clone bundles are supported $ hg -R server debugcreatestreamclonebundle packed.hg writing 613 bytes for 4 files - bundle requirements: generaldelta, revlogv1, sparserevlog (no-rust !) - bundle requirements: generaldelta, persistent-nodemap, revlogv1, sparserevlog (rust !) + bundle requirements: generaldelta, revlogv1, sparserevlog (no-rust no-zstd !) + bundle requirements: generaldelta, revlog-compression-zstd, revlogv1, sparserevlog (no-rust zstd !) + bundle requirements: generaldelta, revlog-compression-zstd, revlogv1, sparserevlog (rust !) No bundle spec should work diff --git a/tests/test-commandserver.t b/tests/test-commandserver.t --- a/tests/test-commandserver.t +++ b/tests/test-commandserver.t @@ -159,7 +159,7 @@ check strict parsing of early options: ... b'default']) *** runcommand log -b --config=alias.log=!echo pwned default abort: unknown revision '--config=alias.log=!echo pwned' - [255] + [10] check that "histedit --commands=-" can read rules from the input channel: diff --git a/tests/test-commit-interactive.t b/tests/test-commit-interactive.t --- a/tests/test-commit-interactive.t +++ b/tests/test-commit-interactive.t @@ -1494,7 +1494,7 @@ Invalid patch Hunk #1 FAILED at 0 1 out of 1 hunks FAILED -- saving rejects to file editedfile.rej abort: patch failed to apply - [10] + [20] $ cat editedfile This change will not be committed This is the second line diff --git a/tests/test-commit.t b/tests/test-commit.t --- a/tests/test-commit.t +++ b/tests/test-commit.t @@ -134,13 +134,13 @@ Using the advanced --extra flag $ hg add quux $ hg commit -m "adding internal used extras" --extra amend_source=hash abort: key 'amend_source' is used internally, can't be set manually - [255] + [10] $ hg commit -m "special chars in extra" --extra id@phab=214 abort: keys can only contain ascii letters, digits, '_' and '-' - [255] + [10] $ hg commit -m "empty key" --extra =value abort: unable to parse '=value', keys can't be empty - [255] + [10] $ hg commit -m "adding extras" --extra sourcehash=foo --extra oldhash=bar $ hg log -r . -T '{extras % "{extra}\n"}' branch=default @@ -661,11 +661,11 @@ verify pathauditor blocks evil filepaths #if windows $ hg co --clean tip abort: path contains illegal component: .h\xe2\x80\x8cg\\hgrc (esc) - [255] + [10] #else $ hg co --clean tip abort: path contains illegal component: .h\xe2\x80\x8cg/hgrc (esc) - [255] + [10] #endif $ hg rollback -f @@ -686,7 +686,7 @@ verify pathauditor blocks evil filepaths $ "$PYTHON" evil-commit.py $ hg co --clean tip abort: path contains illegal component: HG~1/hgrc - [255] + [10] $ hg rollback -f repository tip rolled back to revision 2 (undo commit) @@ -706,7 +706,7 @@ verify pathauditor blocks evil filepaths $ "$PYTHON" evil-commit.py $ hg co --clean tip abort: path contains illegal component: HG8B6C~2/hgrc - [255] + [10] $ cd .. diff --git a/tests/test-completion.t b/tests/test-completion.t --- a/tests/test-completion.t +++ b/tests/test-completion.t @@ -272,7 +272,7 @@ Show all commands + options debugantivirusrunning: debugapplystreamclonebundle: debugbackupbundle: recover, patch, git, limit, no-merges, stat, graph, style, template - debugbuilddag: mergeable-file, overwritten-file, new-file + debugbuilddag: mergeable-file, overwritten-file, new-file, from-existing debugbundle: all, part-type, spec debugcapabilities: debugchangedfiles: compute diff --git a/tests/test-conflict.t b/tests/test-conflict.t --- a/tests/test-conflict.t +++ b/tests/test-conflict.t @@ -267,7 +267,7 @@ internal:merge3 3 6 8 - ||||||| base + ||||||| common ancestor One Two Three @@ -308,7 +308,7 @@ internal:mergediff 2 3 <<<<<<< - ------- base + ------- working copy parent +++++++ working copy 4 +4.5 @@ -346,7 +346,7 @@ copy in full and the diff from base to d 3.5 4.5 5.5 - ------- base + ------- working copy parent +++++++ destination 3 -4 @@ -404,7 +404,7 @@ Now test :merge-other and :merge-local 1 other heads for branch "default" $ hg merge --tool :merge-local merging a - 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + 1 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) $ cat a Start of file diff --git a/tests/test-copies-chain-merge.t b/tests/test-copies-chain-merge.t --- a/tests/test-copies-chain-merge.t +++ b/tests/test-copies-chain-merge.t @@ -463,14 +463,14 @@ Subcase: new copy information on both si 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 + 0 files updated, 1 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 + 0 files updated, 1 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 @@ -626,14 +626,14 @@ consider history and rename on both bran 1 files updated, 0 files merged, 0 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 + 0 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) $ 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 + 0 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) $ hg ci -m "mGDm-0 $case_desc - the other way" created new head @@ -1649,22 +1649,10 @@ We upgrade a repository that is not usin > [format] > exp-use-copies-side-data-changeset = yes > EOF - $ hg debugformat -v - format-variant repo config default - fncache: yes yes yes - dirstate-v2: no no no - 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 !) + $ hg debugformat -v | egrep 'changelog-v2|revlog-v2|copies-sdc' copies-sdc: no yes no revlog-v2: no no no changelog-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: @@ -1689,22 +1677,10 @@ We upgrade a repository that is not usin > enabled=yes > numcpus=8 > EOF - $ hg debugformat -v - format-variant repo config default - fncache: yes yes yes - dirstate-v2: no no no - 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 !) + $ hg debugformat -v | egrep 'changelog-v2|revlog-v2|copies-sdc' copies-sdc: no yes no revlog-v2: no no no changelog-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: diff --git a/tests/test-copies-in-changeset.t b/tests/test-copies-in-changeset.t --- a/tests/test-copies-in-changeset.t +++ b/tests/test-copies-in-changeset.t @@ -32,41 +32,17 @@ Check that copies are recorded correctly $ hg init repo $ cd repo #if sidedata - $ hg debugformat -v + $ hg debugformat -v | egrep 'format-variant|revlog-v2|copies-sdc|changelog-v2' format-variant repo config default - fncache: yes yes yes - dirstate-v2: no no no - 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: yes yes no revlog-v2: no no no changelog-v2: yes yes no - plain-cl-delta: yes yes yes - compression: zlib zlib zlib (no-zstd !) - compression: zstd zstd zstd (zstd !) - compression-level: default default default #else - $ hg debugformat -v + $ hg debugformat -v | egrep 'format-variant|revlog-v2|copies-sdc|changelog-v2' format-variant repo config default - fncache: yes yes yes - dirstate-v2: no no no - 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 no no revlog-v2: no no no changelog-v2: no no no - plain-cl-delta: yes yes yes - compression: zlib zlib zlib (no-zstd !) - compression: zstd zstd zstd (zstd !) - compression-level: default default default #endif $ echo a > a $ hg add a @@ -425,23 +401,11 @@ Test upgrading/downgrading to sidedata s downgrading - $ hg debugformat -v + $ hg debugformat -v | egrep 'format-variant|revlog-v2|copies-sdc|changelog-v2' format-variant repo config default - fncache: yes yes yes - dirstate-v2: no no no - 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: yes yes no revlog-v2: no no no changelog-v2: yes yes no - plain-cl-delta: yes yes yes - compression: zlib zlib zlib (no-zstd !) - compression: zstd zstd zstd (zstd !) - compression-level: default default default $ hg debugsidedata -c -- 0 1 sidedata entries entry-0014 size 14 @@ -456,23 +420,11 @@ downgrading > revlogv2 = enable-unstable-format-and-corrupt-my-data > EOF $ hg debugupgraderepo --run --quiet --no-backup > /dev/null - $ hg debugformat -v + $ hg debugformat -v | egrep 'format-variant|revlog-v2|copies-sdc|changelog-v2' format-variant repo config default - fncache: yes yes yes - dirstate-v2: no no no - 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 no no revlog-v2: yes yes no changelog-v2: no no no - plain-cl-delta: yes yes yes - compression: zlib zlib zlib (no-zstd !) - compression: zstd zstd zstd (zstd !) - compression-level: default default default $ hg debugsidedata -c -- 0 $ hg debugsidedata -c -- 1 $ hg debugsidedata -m -- 0 @@ -484,23 +436,11 @@ upgrading > exp-use-copies-side-data-changeset = yes > EOF $ hg debugupgraderepo --run --quiet --no-backup > /dev/null - $ hg debugformat -v + $ hg debugformat -v | egrep 'format-variant|revlog-v2|copies-sdc|changelog-v2' format-variant repo config default - fncache: yes yes yes - dirstate-v2: no no no - 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: yes yes no revlog-v2: no no no changelog-v2: yes yes no - plain-cl-delta: yes yes yes - compression: zlib zlib zlib (no-zstd !) - compression: zstd zstd zstd (zstd !) - compression-level: default default default $ hg debugsidedata -c -- 0 1 sidedata entries entry-0014 size 14 diff --git a/tests/test-copy-move-merge.t b/tests/test-copy-move-merge.t --- a/tests/test-copy-move-merge.t +++ b/tests/test-copy-move-merge.t @@ -104,12 +104,12 @@ Test Simple Merge preserving a for resolve of b preserving a for resolve of c removing a - b: remote moved from a -> m (premerge) + b: remote moved from a -> m picked tool ':merge' for b (binary False symlink False changedelete False) merging a and b to b my b@add3f11052fa+ other b@17c05bb7fcb6 ancestor a@b8bf91eeebbc premerge successful - c: remote moved from a -> m (premerge) + c: remote moved from a -> m picked tool ':merge' for c (binary False symlink False changedelete False) merging a and c to c my c@add3f11052fa+ other c@17c05bb7fcb6 ancestor a@b8bf91eeebbc diff --git a/tests/test-debugcommands.t b/tests/test-debugcommands.t --- a/tests/test-debugcommands.t +++ b/tests/test-debugcommands.t @@ -649,16 +649,16 @@ Test debugpeer local: no pushable: yes +#if rust + $ hg --debug debugpeer ssh://user@dummy/debugrevlog running .* ".*[/\\]dummyssh" ['"]user@dummy['"] ['"]hg -R debugrevlog serve --stdio['"] (re) devel-peer-request: hello+between devel-peer-request: pairs: 81 bytes sending hello command sending between command - 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: 468 + remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlog-compression-zstd,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash remote: 1 devel-peer-request: protocaps devel-peer-request: caps: * bytes (glob) @@ -666,3 +666,45 @@ Test debugpeer url: ssh://user@dummy/debugrevlog local: no pushable: yes + +#endif + +#if no-rust zstd + + $ hg --debug debugpeer ssh://user@dummy/debugrevlog + running .* ".*[/\\]dummyssh" ['"]user@dummy['"] ['"]hg -R debugrevlog serve --stdio['"] (re) + devel-peer-request: hello+between + devel-peer-request: pairs: 81 bytes + sending hello command + sending between command + remote: 468 + remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlog-compression-zstd,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + remote: 1 + devel-peer-request: protocaps + devel-peer-request: caps: * bytes (glob) + sending protocaps command + url: ssh://user@dummy/debugrevlog + local: no + pushable: yes + +#endif + +#if no-rust no-zstd + + $ hg --debug debugpeer ssh://user@dummy/debugrevlog + running .* ".*[/\\]dummyssh" ['"]user@dummy['"] ['"]hg -R debugrevlog serve --stdio['"] (re) + devel-peer-request: hello+between + devel-peer-request: pairs: 81 bytes + sending hello command + sending between command + 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 + devel-peer-request: caps: * bytes (glob) + sending protocaps command + url: ssh://user@dummy/debugrevlog + local: no + pushable: yes + +#endif diff --git a/tests/test-devel-warnings.t b/tests/test-devel-warnings.t --- a/tests/test-devel-warnings.t +++ b/tests/test-devel-warnings.t @@ -282,12 +282,12 @@ Stripping from a transaction #if no-chg no-pyoxidizer $ hg blackbox -l 7 - 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated - 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping + 1970-01-01 00:00:00.000 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated + 1970-01-01 00:00:00.000 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping (compatibility will be dropped after Mercurial-42.1337, update your code.) at: $TESTTMP/buggylocking.py:* (oldanddeprecated) (glob) - 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated exited 0 after * seconds (glob) - 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback - 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping + 1970-01-01 00:00:00.000 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated exited 0 after * seconds (glob) + 1970-01-01 00:00:00.000 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback + 1970-01-01 00:00:00.000 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping (compatibility will be dropped after Mercurial-42.1337, update your code.) at: */hg:* in (glob) (?) */mercurial/dispatch.py:* in run (glob) @@ -303,17 +303,17 @@ Stripping from a transaction */mercurial/dispatch.py:* in (glob) */mercurial/util.py:* in check (glob) $TESTTMP/buggylocking.py:* in oldanddeprecated (glob) - 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback exited 0 after * seconds (glob) - 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> blackbox -l 7 + 1970-01-01 00:00:00.000 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback exited 0 after * seconds (glob) + 1970-01-01 00:00:00.000 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> blackbox -l 7 #endif #if chg no-pyoxidizer $ hg blackbox -l 7 - 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated - 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping + 1970-01-01 00:00:00.000 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated + 1970-01-01 00:00:00.000 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping (compatibility will be dropped after Mercurial-42.1337, update your code.) at: $TESTTMP/buggylocking.py:* (oldanddeprecated) (glob) - 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated exited 0 after * seconds (glob) - 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback - 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping + 1970-01-01 00:00:00.000 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated exited 0 after * seconds (glob) + 1970-01-01 00:00:00.000 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback + 1970-01-01 00:00:00.000 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping (compatibility will be dropped after Mercurial-42.1337, update your code.) at: */hg:* in (glob) */mercurial/dispatch.py:* in run (glob) @@ -352,17 +352,17 @@ Stripping from a transaction */mercurial/dispatch.py:* in (glob) */mercurial/util.py:* in check (glob) $TESTTMP/buggylocking.py:* in oldanddeprecated (glob) - 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback exited 0 after * seconds (glob) - 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> blackbox -l 7 + 1970-01-01 00:00:00.000 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback exited 0 after * seconds (glob) + 1970-01-01 00:00:00.000 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> blackbox -l 7 #endif #if pyoxidizer $ hg blackbox -l 7 - 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated - 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping + 1970-01-01 00:00:00.000 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated + 1970-01-01 00:00:00.000 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping (compatibility will be dropped after Mercurial-42.1337, update your code.) at: $TESTTMP/buggylocking.py:* (oldanddeprecated) (glob) - 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated exited 0 after * seconds (glob) - 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback - 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping + 1970-01-01 00:00:00.000 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated exited 0 after * seconds (glob) + 1970-01-01 00:00:00.000 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback + 1970-01-01 00:00:00.000 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping (compatibility will be dropped after Mercurial-42.1337, update your code.) at: :* in (glob) mercurial.dispatch:* in run (glob) @@ -378,8 +378,8 @@ Stripping from a transaction mercurial.dispatch:* in (glob) mercurial.util:* in check (glob) $TESTTMP/buggylocking.py:* in oldanddeprecated (glob) - 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback exited 0 after * seconds (glob) - 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> blackbox -l 7 + 1970-01-01 00:00:00.000 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback exited 0 after * seconds (glob) + 1970-01-01 00:00:00.000 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> blackbox -l 7 #endif Test programming error failure: diff --git a/tests/test-diff-change.t b/tests/test-diff-change.t --- a/tests/test-diff-change.t +++ b/tests/test-diff-change.t @@ -284,7 +284,7 @@ With diff.merge, it's a diff against the 7 -<<<<<<< local: fd1f17c90d7c - test: new file z - -||||||| base + -||||||| base: ae119d680c82 - test: lots of text -8 -======= -y diff --git a/tests/test-diff-unified.t b/tests/test-diff-unified.t --- a/tests/test-diff-unified.t +++ b/tests/test-diff-unified.t @@ -46,7 +46,7 @@ invalid --unified $ hg diff --nodates -U foo abort: diff context lines count must be an integer, not 'foo' - [255] + [10] $ hg diff --nodates -U 2 @@ -87,7 +87,7 @@ invalid diff.unified $ hg --config diff.unified=foo diff --nodates abort: diff context lines count must be an integer, not 'foo' - [255] + [10] noprefix config and option diff --git a/tests/test-dirstate-race.t b/tests/test-dirstate-race.t --- a/tests/test-dirstate-race.t +++ b/tests/test-dirstate-race.t @@ -18,7 +18,7 @@ Do we ever miss a sub-second change?: $ for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20; do - > hg co -qC 0 + > hg update -qC 0 > echo b > a > hg st > done @@ -66,11 +66,11 @@ confused with a file with the exec bit s > ) > def extsetup(ui): > extensions.wrapfunction(context.workingctx, '_checklookup', overridechecklookup) - > def overridechecklookup(orig, self, files): + > def overridechecklookup(orig, self, *args, **kwargs): > # make an update that changes the dirstate from underneath > self._repo.ui.system(br"sh '$TESTTMP/dirstaterace.sh'", > cwd=self._repo.root) - > return orig(self, files) + > return orig(self, *args, **kwargs) > EOF $ hg debugrebuilddirstate @@ -89,6 +89,7 @@ anyway. > rm b && rm -r dir1 && rm d && mkdir d && rm e && mkdir e > EOF + $ sleep 1 # ensure non-ambiguous mtime $ hg status --config extensions.dirstaterace=$TESTTMP/dirstaterace.py M d M e @@ -147,6 +148,8 @@ condition (see issue5584 for detail). > > hg update -q -C 0 > hg cat -r 1 b > b + > # make sure the timestamps is not ambiguous and a write will be issued + > touch -t 198606251012 b > EOF "hg status" below should excludes "e", of which exec flag is set, for diff --git a/tests/test-dirstate-race2.t b/tests/test-dirstate-race2.t --- a/tests/test-dirstate-race2.t +++ b/tests/test-dirstate-race2.t @@ -19,22 +19,34 @@ outside of hg's control. $ hg commit -qAm _ $ echo aa > a $ hg commit -m _ +# this sleep is there to ensure current time has -at-least- one second away +# from the current time. It ensure the mtime is not ambiguous. If the test +# "sleep" longer this will be fine. +# It is not used to synchronise parallele operation so it is "fine" to use it. + $ sleep 1 + $ hg status $ hg debugdirstate --no-dates n 644 3 (set |unset) a (re) $ cat >> $TESTTMP/dirstaterace.py << EOF + > import time > from mercurial import ( + > commit, > extensions, > merge, > ) > def extsetup(ui): - > extensions.wrapfunction(merge, 'applyupdates', wrap) - > def wrap(orig, *args, **kwargs): - > res = orig(*args, **kwargs) - > with open("a", "w"): - > pass # just truncate the file - > return res + > extensions.wrapfunction(merge, 'applyupdates', wrap(0)) + > extensions.wrapfunction(commit, 'commitctx', wrap(1)) + > def wrap(duration): + > def new(orig, *args, **kwargs): + > res = orig(*args, **kwargs) + > with open("a", "w"): + > pass # just truncate the file + > time.sleep(duration) + > return res + > return new > EOF Do an update where file 'a' is changed between hg writing it to disk @@ -46,3 +58,32 @@ so hg status correctly shows a as clean. $ hg debugdirstate --no-dates n 644 2 (set |unset) a (re) $ echo a > a; hg status; hg diff + +Do a commit where file 'a' is changed between hg committing its new +revision into the repository, and the writing of the dirstate. + +This used to results in a corrupted dirstate (size did not match committed size). + + $ echo aaa > a; hg commit -qm _ + $ hg merge -qr 1; hg resolve -m; rm a.orig + warning: conflicts while merging a! (edit, then use 'hg resolve --mark') + (no more unresolved files) + $ cat a + <<<<<<< working copy: be46f74ce38d - test: _ + aaa + ======= + aa + >>>>>>> merge rev: eb3fc6c17aa3 - test: _ + $ hg debugdirstate --no-dates + m 0 -2 (set |unset) a (re) + $ hg commit -m _ --config extensions.race=$TESTTMP/dirstaterace.py + $ hg debugdirstate --no-dates + n 0 -1 unset a + $ cat a | wc -c + *0 (re) + $ hg cat -r . a | wc -c + *105 (re) + $ hg status; hg diff --stat + M a + a | 5 ----- + 1 files changed, 0 insertions(+), 5 deletions(-) diff --git a/tests/test-dispatch.t b/tests/test-dispatch.t --- a/tests/test-dispatch.t +++ b/tests/test-dispatch.t @@ -84,7 +84,7 @@ However, we can't prevent it from loadin > raise Exception('bad') > EOF $ hg log -b '--config=extensions.bad=bad.py' default - *** failed to import extension bad from bad.py: bad + *** failed to import extension "bad" from bad.py: bad abort: option --config may not be abbreviated [10] @@ -127,20 +127,20 @@ Early options must come first if HGPLAIN #if no-chg $ HGPLAIN=+strictflags hg log -b --config='hooks.pre-log=false' default abort: unknown revision '--config=hooks.pre-log=false' - [255] + [10] $ HGPLAIN=+strictflags hg log -b -R. default abort: unknown revision '-R.' - [255] + [10] $ HGPLAIN=+strictflags hg log -b --cwd=. default abort: unknown revision '--cwd=.' - [255] + [10] #endif $ HGPLAIN=+strictflags hg log -b --debugger default abort: unknown revision '--debugger' - [255] + [10] $ HGPLAIN=+strictflags hg log -b --config='alias.log=!echo pwned' default abort: unknown revision '--config=alias.log=!echo pwned' - [255] + [10] $ HGPLAIN=+strictflags hg log --config='hooks.pre-log=false' -b default abort: option --config may not be abbreviated diff --git a/tests/test-double-merge.t b/tests/test-double-merge.t --- a/tests/test-double-merge.t +++ b/tests/test-double-merge.t @@ -38,12 +38,12 @@ we get conflicts that shouldn't be there starting 4 threads for background file closing (?) preserving foo for resolve of bar preserving foo for resolve of foo - bar: remote copied from foo -> m (premerge) + bar: remote copied from foo -> m picked tool ':merge' for bar (binary False symlink False changedelete False) merging foo and bar to bar my bar@6a0df1dad128+ other bar@484bf6903104 ancestor foo@e6dc8efe11cc premerge successful - foo: versions differ -> m (premerge) + foo: versions differ -> m picked tool ':merge' for foo (binary False symlink False changedelete False) merging foo my foo@6a0df1dad128+ other foo@484bf6903104 ancestor foo@e6dc8efe11cc diff --git a/tests/test-empty.t b/tests/test-empty.t --- a/tests/test-empty.t +++ b/tests/test-empty.t @@ -25,9 +25,10 @@ Check the basic files created: store wcache -Should be empty: +Should be empty (except for the "basic" requires): $ ls .hg/store + requires Poke at a clone: @@ -51,8 +52,9 @@ Poke at a clone: store wcache -Should be empty: +Should be empty (except for the "basic" requires): $ ls .hg/store + requires $ cd .. diff --git a/tests/test-extension.t b/tests/test-extension.t --- a/tests/test-extension.t +++ b/tests/test-extension.t @@ -649,7 +649,7 @@ Python 3's lazy importer verifies module module stub. Our custom lazy importer for Python 2 always returns a stub. $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.checkrelativity=$TESTTMP/checkrelativity.py checkrelativity) || true - *** failed to import extension checkrelativity from $TESTTMP/checkrelativity.py: No module named 'extlibroot.lsub1.lsub2.notexist' (py3 !) + *** failed to import extension "checkrelativity" from $TESTTMP/checkrelativity.py: No module named 'extlibroot.lsub1.lsub2.notexist' (py3 !) hg: unknown command 'checkrelativity' (py3 !) (use 'hg help' for a list of commands) (py3 !) @@ -1882,7 +1882,7 @@ Prohibit registration of commands that d > EOF $ hg deprecatedcmd > /dev/null - *** failed to import extension deprecatedcmd from $TESTTMP/deprecated/deprecatedcmd.py: missing attributes: norepo, optionalrepo, inferrepo + *** failed to import extension "deprecatedcmd" from $TESTTMP/deprecated/deprecatedcmd.py: missing attributes: norepo, optionalrepo, inferrepo *** (use @command decorator to register 'deprecatedcmd') hg: unknown command 'deprecatedcmd' (use 'hg help' for a list of commands) @@ -1891,7 +1891,7 @@ Prohibit registration of commands that d the extension shouldn't be loaded at all so the mq works: $ hg qseries --config extensions.mq= > /dev/null - *** failed to import extension deprecatedcmd from $TESTTMP/deprecated/deprecatedcmd.py: missing attributes: norepo, optionalrepo, inferrepo + *** failed to import extension "deprecatedcmd" from $TESTTMP/deprecated/deprecatedcmd.py: missing attributes: norepo, optionalrepo, inferrepo *** (use @command decorator to register 'deprecatedcmd') $ cd .. @@ -1939,8 +1939,117 @@ Prohibit the use of unicode strings as t > test_unicode_default_value = $TESTTMP/test_unicode_default_value.py > EOF $ hg -R $TESTTMP/opt-unicode-default dummy - *** failed to import extension test_unicode_default_value from $TESTTMP/test_unicode_default_value.py: unicode *'value' found in cmdtable.dummy (glob) + *** failed to import extension "test_unicode_default_value" from $TESTTMP/test_unicode_default_value.py: unicode 'value' found in cmdtable.dummy (py3 !) + *** failed to import extension "test_unicode_default_value" from $TESTTMP/test_unicode_default_value.py: unicode u'value' found in cmdtable.dummy (no-py3 !) *** (use b'' to make it byte string) hg: unknown command 'dummy' (did you mean summary?) [10] + +Check the mandatory extension feature +------------------------------------- + + $ hg init mandatory-extensions + $ cat > $TESTTMP/mandatory-extensions/.hg/good.py << EOF + > pass + > EOF + $ cat > $TESTTMP/mandatory-extensions/.hg/bad.py << EOF + > raise RuntimeError("babar") + > EOF + $ cat > $TESTTMP/mandatory-extensions/.hg/syntax.py << EOF + > def ( + > EOF + +Check that the good one load : + + $ cat > $TESTTMP/mandatory-extensions/.hg/hgrc << EOF + > [extensions] + > good = $TESTTMP/mandatory-extensions/.hg/good.py + > EOF + + $ hg -R mandatory-extensions id + 000000000000 tip + +Make it mandatory to load + + $ cat >> $TESTTMP/mandatory-extensions/.hg/hgrc << EOF + > good:required = yes + > EOF + + $ hg -R mandatory-extensions id + 000000000000 tip + +Check that the bad one does not load + + $ cat >> $TESTTMP/mandatory-extensions/.hg/hgrc << EOF + > bad = $TESTTMP/mandatory-extensions/.hg/bad.py + > EOF + + $ hg -R mandatory-extensions id + *** failed to import extension "bad" from $TESTTMP/mandatory-extensions/.hg/bad.py: babar + 000000000000 tip + +Make it mandatory to load + + $ cat >> $TESTTMP/mandatory-extensions/.hg/hgrc << EOF + > bad:required = yes + > EOF + + $ hg -R mandatory-extensions id + abort: failed to import extension "bad" from $TESTTMP/mandatory-extensions/.hg/bad.py: babar + (loading of this extension was required, see `hg help config.extensions` for details) + [255] + +Make it not mandatory to load + + $ cat >> $TESTTMP/mandatory-extensions/.hg/hgrc << EOF + > bad:required = no + > EOF + + $ hg -R mandatory-extensions id + *** failed to import extension "bad" from $TESTTMP/mandatory-extensions/.hg/bad.py: babar + 000000000000 tip + +Same check with the syntax error one + + $ cat >> $TESTTMP/mandatory-extensions/.hg/hgrc << EOF + > bad = ! + > syntax = $TESTTMP/mandatory-extensions/.hg/syntax.py + > syntax:required = yes + > EOF + + $ hg -R mandatory-extensions id + abort: failed to import extension "syntax" from $TESTTMP/mandatory-extensions/.hg/syntax.py: invalid syntax (*syntax.py, line 1) (glob) + (loading of this extension was required, see `hg help config.extensions` for details) + [255] + +Same check with a missing one + + $ cat >> $TESTTMP/mandatory-extensions/.hg/hgrc << EOF + > syntax = ! + > syntax:required = + > missing = foo/bar/baz/I/do/not/exist/ + > missing:required = yes + > EOF + + $ hg -R mandatory-extensions id + abort: failed to import extension "missing" from foo/bar/baz/I/do/not/exist/: [Errno 2] $ENOENT$: 'foo/bar/baz/I/do/not/exist' + (loading of this extension was required, see `hg help config.extensions` for details) + [255] + +Have a "default" setting for the suboption: + + $ cat > $TESTTMP/mandatory-extensions/.hg/hgrc << EOF + > [extensions] + > bad = $TESTTMP/mandatory-extensions/.hg/bad.py + > bad:required = no + > good = $TESTTMP/mandatory-extensions/.hg/good.py + > syntax = $TESTTMP/mandatory-extensions/.hg/syntax.py + > *:required = yes + > EOF + + $ hg -R mandatory-extensions id + *** failed to import extension "bad" from $TESTTMP/mandatory-extensions/.hg/bad.py: babar + abort: failed to import extension "syntax" from $TESTTMP/mandatory-extensions/.hg/syntax.py: invalid syntax (*syntax.py, line 1) (glob) + (loading of this extension was required, see `hg help config.extensions` for details) + [255] diff --git a/tests/test-fastannotate-hg.t b/tests/test-fastannotate-hg.t --- a/tests/test-fastannotate-hg.t +++ b/tests/test-fastannotate-hg.t @@ -165,16 +165,16 @@ annotate -nlf b a a a - <<<<<<< working copy: 5fbdc1152d97 - test: b2.1 + <<<<<<< working copy: 5fbdc1152d97 - test: b2.1 b4 c b5 - ||||||| base + ||||||| common ancestor: 3086dbafde1c - test: b ======= b4 b5 b6 - >>>>>>> merge rev: 37ec9f5c3d1f - test: b2 + >>>>>>> merge rev: 37ec9f5c3d1f - test: b2 $ cat < b > a > a @@ -789,16 +789,16 @@ Issue5360: Deleted chunk in p1 of a merg warning: conflicts while merging a! (edit, then use 'hg resolve --mark') [1] $ cat a - <<<<<<< working copy: 0a068f0261cf - test: 3 + <<<<<<< working copy: 0a068f0261cf - test: 3 1 2 3 - ||||||| base + ||||||| common ancestor: 1ed24be7e7a0 - test: 2 1 2 ======= a - >>>>>>> merge rev: 9409851bc20a - test: a + >>>>>>> merge rev: 9409851bc20a - test: a $ cat > a << EOF > b > 1 diff --git a/tests/test-fix.t b/tests/test-fix.t --- a/tests/test-fix.t +++ b/tests/test-fix.t @@ -1169,8 +1169,9 @@ obsolete revision. $ hg commit -Aqm "foo" $ hg ci --amend -m rewritten $ hg --hidden fix -r 0 - abort: fixing obsolete revision could cause divergence - [255] + abort: cannot fix b87e30dbf19b, as that creates content-divergence with 2e007a78dfb8 + (add --verbose for details or see 'hg help evolution.instability') + [10] $ hg --hidden fix -r 0 --config experimental.evolution.allowdivergence=true 2 new content-divergent changesets diff --git a/tests/test-fncache.t b/tests/test-fncache.t --- a/tests/test-fncache.t +++ b/tests/test-fncache.t @@ -153,6 +153,7 @@ Non fncache repo: .hg/store/data/tst.d.hg .hg/store/data/tst.d.hg/_foo.i .hg/store/phaseroots + .hg/store/requires .hg/store/undo .hg/store/undo.backupfiles .hg/store/undo.phaseroots diff --git a/tests/test-graft.t b/tests/test-graft.t --- a/tests/test-graft.t +++ b/tests/test-graft.t @@ -212,7 +212,7 @@ Graft out of order, skipping a merge and ancestor: 68795b066622, local: ef0ef43d49e7+, remote: 5d205f8b35b6 starting 4 threads for background file closing (?) preserving b for resolve of b - b: local copied/moved from a -> m (premerge) + b: local copied/moved from a -> m picked tool ':merge' for b (binary False symlink False changedelete False) merging b and a to b my b@ef0ef43d49e7+ other a@5d205f8b35b6 ancestor a@68795b066622 @@ -242,13 +242,10 @@ Graft out of order, skipping a merge and d: remote is newer -> g getting d preserving e for resolve of e - e: versions differ -> m (premerge) + e: versions differ -> m picked tool ':merge' for e (binary False symlink False changedelete False) merging e my e@1905859650ec+ other e@9c233e8e184d ancestor e@4c60f11aa304 - e: versions differ -> m (merge) - picked tool ':merge' for e (binary False symlink False changedelete False) - my e@1905859650ec+ other e@9c233e8e184d ancestor e@4c60f11aa304 warning: conflicts while merging e! (edit, then use 'hg resolve --mark') abort: unresolved conflicts, can't continue (use 'hg resolve' and 'hg graft --continue') @@ -855,8 +852,8 @@ graft --continue after --base with confl $ hg graft -r 6 --base 5 grafting 6:25a2b029d3ae "6" merging d + warning: conflicts while merging d! (edit, then use 'hg resolve --mark') merging e - warning: conflicts while merging d! (edit, then use 'hg resolve --mark') abort: unresolved conflicts, can't continue (use 'hg resolve' and 'hg graft --continue') [1] diff --git a/tests/test-grep.t b/tests/test-grep.t --- a/tests/test-grep.t +++ b/tests/test-grep.t @@ -1200,11 +1200,11 @@ follow file history from wdir parent, co $ hg log -f add0-cp4 abort: cannot follow nonexistent file: "add0-cp4" - [255] + [20] $ hg grep --diff -f data add0-cp4 abort: cannot follow nonexistent file: "add0-cp4" - [255] + [20] BROKEN: maybe better to abort $ hg grep -f data add0-cp4 @@ -1214,11 +1214,11 @@ follow file history from wdir parent, re $ hg log -f add0-cp1-mod1-rm3 abort: cannot follow file not in parent revision: "add0-cp1-mod1-rm3" - [255] + [20] $ hg grep --diff -f data add0-cp1-mod1-rm3 abort: cannot follow file not in parent revision: "add0-cp1-mod1-rm3" - [255] + [20] BROKEN: maybe better to abort $ hg grep -f data add0-cp1-mod1-rm3 @@ -1229,11 +1229,11 @@ follow file history from wdir parent (ex $ hg log -fr. add0-cp1-mod1-rm3 abort: cannot follow file not in any of the specified revisions: "add0-cp1-mod1-rm3" - [255] + [20] $ hg grep --diff -fr. data add0-cp1-mod1-rm3 abort: cannot follow file not in any of the specified revisions: "add0-cp1-mod1-rm3" - [255] + [20] BROKEN: should abort $ hg grep -fr. data add0-cp1-mod1-rm3 @@ -1244,11 +1244,11 @@ follow file history from wdir parent, re $ hg log -f add0-rm4 abort: cannot follow file not in parent revision: "add0-rm4" - [255] + [20] $ hg grep --diff -f data add0-rm4 abort: cannot follow file not in parent revision: "add0-rm4" - [255] + [20] BROKEN: should abort $ hg grep -f data add0-rm4 @@ -1340,11 +1340,11 @@ follow file history from specified revis $ hg log -fr2 add0-rm2 abort: cannot follow file not in any of the specified revisions: "add0-rm2" - [255] + [20] $ hg grep --diff -fr2 data add0-rm2 abort: cannot follow file not in any of the specified revisions: "add0-rm2" - [255] + [20] BROKEN: should abort $ hg grep -fr2 data add0-rm2 diff --git a/tests/test-hardlinks.t b/tests/test-hardlinks.t --- a/tests/test-hardlinks.t +++ b/tests/test-hardlinks.t @@ -52,6 +52,7 @@ Prepare repo r1: 1 r1/.hg/store/data/f1.i 1 r1/.hg/store/fncache (repofncache !) 1 r1/.hg/store/phaseroots + 1 r1/.hg/store/requires 1 r1/.hg/store/undo 1 r1/.hg/store/undo.backup.fncache (repofncache !) 1 r1/.hg/store/undo.backupfiles @@ -93,6 +94,7 @@ Repos r1 and r2 should now contain hardl 2 r1/.hg/store/data/f1.i 1 r1/.hg/store/fncache (repofncache !) 1 r1/.hg/store/phaseroots + 1 r1/.hg/store/requires 1 r1/.hg/store/undo 1 r1/.hg/store/undo.backup.fncache (repofncache !) 1 r1/.hg/store/undo.backupfiles @@ -104,6 +106,7 @@ Repos r1 and r2 should now contain hardl 2 r2/.hg/store/data/d1/f2.i 2 r2/.hg/store/data/f1.i 1 r2/.hg/store/fncache (repofncache !) + 1 r2/.hg/store/requires Repo r3 should not be hardlinked: @@ -114,6 +117,7 @@ Repo r3 should not be hardlinked: 1 r3/.hg/store/data/f1.i 1 r3/.hg/store/fncache (repofncache !) 1 r3/.hg/store/phaseroots + 1 r3/.hg/store/requires 1 r3/.hg/store/undo 1 r3/.hg/store/undo.backupfiles 1 r3/.hg/store/undo.phaseroots @@ -140,6 +144,7 @@ Create a non-inlined filelog in r3: 1 r3/.hg/store/data/f1.i 1 r3/.hg/store/fncache (repofncache !) 1 r3/.hg/store/phaseroots + 1 r3/.hg/store/requires 1 r3/.hg/store/undo 1 r3/.hg/store/undo.backup.fncache (repofncache !) 1 r3/.hg/store/undo.backup.phaseroots @@ -172,6 +177,7 @@ Push to repo r1 should break up most har 1 r2/.hg/store/data/d1/f2.i 2 r2/.hg/store/data/f1.i [12] r2/\.hg/store/fncache (re) (repofncache !) + 1 r2/.hg/store/requires #if hardlink-whitelisted repofncache $ nlinksdir r2/.hg/store/fncache @@ -202,6 +208,7 @@ Committing a change to f1 in r1 must bre 1 r2/.hg/store/data/d1/f2.i 1 r2/.hg/store/data/f1.i 1 r2/.hg/store/fncache (repofncache !) + 1 r2/.hg/store/requires #if hardlink-whitelisted repofncache $ nlinksdir r2/.hg/store/fncache @@ -261,6 +268,7 @@ r4 has hardlinks in the working dir (not 2 r4/.hg/store/data/f3.i 2 r4/.hg/store/fncache (repofncache !) 2 r4/.hg/store/phaseroots + 2 r4/.hg/store/requires 2 r4/.hg/store/undo 2 r4/.hg/store/undo.backup.fncache (repofncache !) 2 r4/.hg/store/undo.backup.phaseroots @@ -318,6 +326,7 @@ Update back to revision 12 in r4 should 2 r4/.hg/store/data/f3.i 2 r4/.hg/store/fncache 2 r4/.hg/store/phaseroots + 2 r4/.hg/store/requires 2 r4/.hg/store/undo 2 r4/.hg/store/undo.backup.fncache (repofncache !) 2 r4/.hg/store/undo.backup.phaseroots diff --git a/tests/test-help.t b/tests/test-help.t --- a/tests/test-help.t +++ b/tests/test-help.t @@ -1519,26 +1519,38 @@ Test section name with dot "commands.update.check" Determines what level of checking 'hg update' will perform before moving to a destination revision. Valid values are "abort", "none", - "linear", and "noconflict". "abort" always fails if the working - directory has uncommitted changes. "none" performs no checking, and - may result in a merge with uncommitted changes. "linear" allows any - update as long as it follows a straight line in the revision history, - and may trigger a merge with uncommitted changes. "noconflict" will - allow any update which would not trigger a merge with uncommitted - changes, if any are present. (default: "linear") + "linear", and "noconflict". + + - "abort" always fails if the working directory has uncommitted + changes. + - "none" performs no checking, and may result in a merge with + uncommitted changes. + - "linear" allows any update as long as it follows a straight line in + the revision history, and may trigger a merge with uncommitted + changes. + - "noconflict" will allow any update which would not trigger a merge + with uncommitted changes, if any are present. + + (default: "linear") $ hg help config.commands.update.check "commands.update.check" Determines what level of checking 'hg update' will perform before moving to a destination revision. Valid values are "abort", "none", - "linear", and "noconflict". "abort" always fails if the working - directory has uncommitted changes. "none" performs no checking, and - may result in a merge with uncommitted changes. "linear" allows any - update as long as it follows a straight line in the revision history, - and may trigger a merge with uncommitted changes. "noconflict" will - allow any update which would not trigger a merge with uncommitted - changes, if any are present. (default: "linear") + "linear", and "noconflict". + + - "abort" always fails if the working directory has uncommitted + changes. + - "none" performs no checking, and may result in a merge with + uncommitted changes. + - "linear" allows any update as long as it follows a straight line in + the revision history, and may trigger a merge with uncommitted + changes. + - "noconflict" will allow any update which would not trigger a merge + with uncommitted changes, if any are present. + + (default: "linear") $ hg help config.ommands.update.check @@ -1587,6 +1599,8 @@ Separate sections from subsections "use-dirstate-v2" + "use-dirstate-tracked-hint" + "use-persistent-nodemap" "use-share-safe" diff --git a/tests/test-hgignore.t b/tests/test-hgignore.t --- a/tests/test-hgignore.t +++ b/tests/test-hgignore.t @@ -59,9 +59,19 @@ Should display baz only: ? syntax $ echo "*.o" > .hgignore +#if no-rhg $ hg status abort: $TESTTMP/ignorerepo/.hgignore: invalid pattern (relre): *.o (glob) [255] +#endif +#if rhg + $ hg status + Unsupported syntax regex parse error: + ^(?:*.o) + ^ + error: repetition operator missing expression + [255] +#endif Ensure given files are relative to cwd diff --git a/tests/test-hgweb-commands.t b/tests/test-hgweb-commands.t --- a/tests/test-hgweb-commands.t +++ b/tests/test-hgweb-commands.t @@ -2193,8 +2193,7 @@ capabilities lookup pushkey stream-preferred - streamreqs=generaldelta,revlogv1,sparserevlog (no-rust !) - streamreqs=generaldelta,persistent-nodemap,revlogv1,sparserevlog (rust !) + streamreqs=*,* (glob) unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash diff --git a/tests/test-histedit-non-commute-abort.t b/tests/test-histedit-non-commute-abort.t --- a/tests/test-histedit-non-commute-abort.t +++ b/tests/test-histedit-non-commute-abort.t @@ -77,8 +77,8 @@ edit the history insert unsupported advisory merge record $ hg --config extensions.fakemergerecord=$TESTDIR/fakemergerecord.py fakemergerecord -x $ hg debugmergestate - local (local): 8f7551c7e4a2f2efe0bc8c741baf7f227d65d758 - other (histedit): e860deea161a2f77de56603b340ebbb4536308ae + local (already edited): 8f7551c7e4a2f2efe0bc8c741baf7f227d65d758 + other (current change): e860deea161a2f77de56603b340ebbb4536308ae file: e (state "u") local path: e (hash 58e6b3a414a1e090dfc6029add0f3555ccba127f, flags "") ancestor path: e (node 0000000000000000000000000000000000000000) diff --git a/tests/test-hook.t b/tests/test-hook.t --- a/tests/test-hook.t +++ b/tests/test-hook.t @@ -464,6 +464,7 @@ more there after fncache (repofncache !) journal.phaseroots phaseroots + requires undo undo.backup.fncache (repofncache !) undo.backupfiles diff --git a/tests/test-http-api-httpv2.t b/tests/test-http-api-httpv2.t deleted file mode 100644 --- a/tests/test-http-api-httpv2.t +++ /dev/null @@ -1,743 +0,0 @@ -#require no-chg - - $ . $TESTDIR/wireprotohelpers.sh - $ enabledummycommands - - $ hg init server - $ cat > server/.hg/hgrc << EOF - > [experimental] - > web.apiserver = true - > EOF - $ hg -R server serve -p $HGPORT -d --pid-file hg.pid - $ cat hg.pid > $DAEMON_PIDS - -HTTP v2 protocol not enabled by default - - $ sendhttpraw << EOF - > httprequest GET api/$HTTPV2 - > user-agent: test - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> GET /api/exp-http-v2-0003 HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> user-agent: test\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 404 Not Found\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: text/plain\r\n - s> Content-Length: 33\r\n - s> \r\n - s> API exp-http-v2-0003 not enabled\n - -Restart server with support for HTTP v2 API - - $ killdaemons.py - $ enablehttpv2 server - $ hg -R server serve -p $HGPORT -d --pid-file hg.pid - $ cat hg.pid > $DAEMON_PIDS - -Request to unknown command yields 404 - - $ sendhttpraw << EOF - > httprequest POST api/$HTTPV2/ro/badcommand - > user-agent: test - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> POST /api/exp-http-v2-0003/ro/badcommand HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> user-agent: test\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 404 Not Found\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: text/plain\r\n - s> Content-Length: 42\r\n - s> \r\n - s> unknown wire protocol command: badcommand\n - -GET to read-only command yields a 405 - - $ sendhttpraw << EOF - > httprequest GET api/$HTTPV2/ro/customreadonly - > user-agent: test - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> GET /api/exp-http-v2-0003/ro/customreadonly HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> user-agent: test\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 405 Method Not Allowed\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Allow: POST\r\n - s> Content-Length: 30\r\n - s> \r\n - s> commands require POST requests - -Missing Accept header results in 406 - - $ sendhttpraw << EOF - > httprequest POST api/$HTTPV2/ro/customreadonly - > user-agent: test - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> POST /api/exp-http-v2-0003/ro/customreadonly HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> user-agent: test\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 406 Not Acceptable\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: text/plain\r\n - s> Content-Length: 85\r\n - s> \r\n - s> client MUST specify Accept header with value: application/mercurial-exp-framing-0006\n - -Bad Accept header results in 406 - - $ sendhttpraw << EOF - > httprequest POST api/$HTTPV2/ro/customreadonly - > accept: invalid - > user-agent: test - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> POST /api/exp-http-v2-0003/ro/customreadonly HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> accept: invalid\r\n - s> user-agent: test\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 406 Not Acceptable\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: text/plain\r\n - s> Content-Length: 85\r\n - s> \r\n - s> client MUST specify Accept header with value: application/mercurial-exp-framing-0006\n - -Bad Content-Type header results in 415 - - $ sendhttpraw << EOF - > httprequest POST api/$HTTPV2/ro/customreadonly - > accept: $MEDIATYPE - > user-agent: test - > content-type: badmedia - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> POST /api/exp-http-v2-0003/ro/customreadonly HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> accept: application/mercurial-exp-framing-0006\r\n - s> content-type: badmedia\r\n - s> user-agent: test\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 415 Unsupported Media Type\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: text/plain\r\n - s> Content-Length: 88\r\n - s> \r\n - s> client MUST send Content-Type header with value: application/mercurial-exp-framing-0006\n - -Request to read-only command works out of the box - - $ sendhttpraw << EOF - > httprequest POST api/$HTTPV2/ro/customreadonly - > accept: $MEDIATYPE - > content-type: $MEDIATYPE - > user-agent: test - > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'} - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> POST /api/exp-http-v2-0003/ro/customreadonly HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> *\r\n (glob) - s> content-type: application/mercurial-exp-framing-0006\r\n - s> user-agent: test\r\n - s> content-length: 29\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly - s> makefile('rb', None) - s> HTTP/1.1 200 OK\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: application/mercurial-exp-framing-0006\r\n - s> Transfer-Encoding: chunked\r\n - s> \r\n - s> 11\r\n - s> \t\x00\x00\x01\x00\x02\x01\x92Hidentity - s> \r\n - s> 13\r\n - s> \x0b\x00\x00\x01\x00\x02\x041\xa1FstatusBok - s> \r\n - s> 27\r\n - s> \x1f\x00\x00\x01\x00\x02\x041X\x1dcustomreadonly bytes response - s> \r\n - s> 8\r\n - s> \x00\x00\x00\x01\x00\x02\x002 - s> \r\n - s> 0\r\n - s> \r\n - - $ sendhttpv2peerverbose << EOF - > command customreadonly - > EOF - creating http peer for wire protocol version 2 - sending customreadonly command - s> setsockopt(6, 1, 1) -> None (?) - s> POST /api/exp-http-v2-0003/ro/customreadonly HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> accept: application/mercurial-exp-framing-0006\r\n - s> content-type: application/mercurial-exp-framing-0006\r\n - s> content-length: 65\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> user-agent: Mercurial debugwireproto\r\n - s> \r\n - s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity\x15\x00\x00\x01\x00\x01\x00\x11\xa1DnameNcustomreadonly - s> makefile('rb', None) - s> HTTP/1.1 200 OK\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: application/mercurial-exp-framing-0006\r\n - s> Transfer-Encoding: chunked\r\n - s> \r\n - s> 11\r\n - s> \t\x00\x00\x01\x00\x02\x01\x92 - s> Hidentity - s> \r\n - s> 13\r\n - s> \x0b\x00\x00\x01\x00\x02\x041 - s> \xa1FstatusBok - s> \r\n - s> 27\r\n - s> \x1f\x00\x00\x01\x00\x02\x041 - s> X\x1dcustomreadonly bytes response - s> \r\n - s> 8\r\n - s> \x00\x00\x00\x01\x00\x02\x002 - s> \r\n - s> 0\r\n - s> \r\n - response: gen[ - b'customreadonly bytes response' - ] - (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob) - -Request to read-write command fails because server is read-only by default - -GET to read-write request yields 405 - - $ sendhttpraw << EOF - > httprequest GET api/$HTTPV2/rw/customreadonly - > user-agent: test - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> GET /api/exp-http-v2-0003/rw/customreadonly HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> user-agent: test\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 405 Method Not Allowed\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Allow: POST\r\n - s> Content-Length: 30\r\n - s> \r\n - s> commands require POST requests - -Even for unknown commands - - $ sendhttpraw << EOF - > httprequest GET api/$HTTPV2/rw/badcommand - > user-agent: test - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> GET /api/exp-http-v2-0003/rw/badcommand HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> user-agent: test\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 405 Method Not Allowed\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Allow: POST\r\n - s> Content-Length: 30\r\n - s> \r\n - s> commands require POST requests - -SSL required by default - - $ sendhttpraw << EOF - > httprequest POST api/$HTTPV2/rw/customreadonly - > user-agent: test - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> POST /api/exp-http-v2-0003/rw/customreadonly HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> user-agent: test\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 403 ssl required\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Length: 17\r\n - s> \r\n - s> permission denied - -Restart server to allow non-ssl read-write operations - - $ killdaemons.py - $ cat > server/.hg/hgrc << EOF - > [experimental] - > web.apiserver = true - > web.api.http-v2 = true - > [web] - > push_ssl = false - > allow-push = * - > EOF - - $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log - $ cat hg.pid > $DAEMON_PIDS - -Authorized request for valid read-write command works - - $ sendhttpraw << EOF - > httprequest POST api/$HTTPV2/rw/customreadonly - > user-agent: test - > accept: $MEDIATYPE - > content-type: $MEDIATYPE - > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'} - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> POST /api/exp-http-v2-0003/rw/customreadonly HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> accept: application/mercurial-exp-framing-0006\r\n - s> content-type: application/mercurial-exp-framing-0006\r\n - s> user-agent: test\r\n - s> content-length: 29\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly - s> makefile('rb', None) - s> HTTP/1.1 200 OK\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: application/mercurial-exp-framing-0006\r\n - s> Transfer-Encoding: chunked\r\n - s> \r\n - s> 11\r\n - s> \t\x00\x00\x01\x00\x02\x01\x92Hidentity - s> \r\n - s> 13\r\n - s> \x0b\x00\x00\x01\x00\x02\x041\xa1FstatusBok - s> \r\n - s> 27\r\n - s> \x1f\x00\x00\x01\x00\x02\x041X\x1dcustomreadonly bytes response - s> \r\n - s> 8\r\n - s> \x00\x00\x00\x01\x00\x02\x002 - s> \r\n - s> 0\r\n - s> \r\n - -Authorized request for unknown command is rejected - - $ sendhttpraw << EOF - > httprequest POST api/$HTTPV2/rw/badcommand - > user-agent: test - > accept: $MEDIATYPE - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> POST /api/exp-http-v2-0003/rw/badcommand HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> accept: application/mercurial-exp-framing-0006\r\n - s> user-agent: test\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 404 Not Found\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: text/plain\r\n - s> Content-Length: 42\r\n - s> \r\n - s> unknown wire protocol command: badcommand\n - -debugreflect isn't enabled by default - - $ sendhttpraw << EOF - > httprequest POST api/$HTTPV2/ro/debugreflect - > user-agent: test - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> POST /api/exp-http-v2-0003/ro/debugreflect HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> user-agent: test\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 404 Not Found\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: text/plain\r\n - s> Content-Length: 34\r\n - s> \r\n - s> debugreflect service not available - -Restart server to get debugreflect endpoint - - $ killdaemons.py - $ cat > server/.hg/hgrc << EOF - > [experimental] - > web.apiserver = true - > web.api.debugreflect = true - > web.api.http-v2 = true - > [web] - > push_ssl = false - > allow-push = * - > EOF - - $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log - $ cat hg.pid > $DAEMON_PIDS - -Command frames can be reflected via debugreflect - - $ sendhttpraw << EOF - > httprequest POST api/$HTTPV2/ro/debugreflect - > accept: $MEDIATYPE - > content-type: $MEDIATYPE - > user-agent: test - > frame 1 1 stream-begin command-request new cbor:{b'name': b'command1', b'args': {b'foo': b'val1', b'bar1': b'val'}} - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> POST /api/exp-http-v2-0003/ro/debugreflect HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> accept: application/mercurial-exp-framing-0006\r\n - s> content-type: application/mercurial-exp-framing-0006\r\n - s> user-agent: test\r\n - s> content-length: 47\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> \'\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Dbar1CvalCfooDval1DnameHcommand1 - s> makefile('rb', None) - s> HTTP/1.1 200 OK\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: text/plain\r\n - s> Content-Length: 223\r\n - s> \r\n - s> received: 1 1 1 \xa2Dargs\xa2Dbar1CvalCfooDval1DnameHcommand1\n - s> ["runcommand", {"args": {"bar1": "val", "foo": "val1"}, "command": "command1", "data": null, "redirect": null, "requestid": 1}]\n - s> received: \n - s> {"action": "noop"} - -Multiple requests to regular command URL are not allowed - - $ sendhttpraw << EOF - > httprequest POST api/$HTTPV2/ro/customreadonly - > accept: $MEDIATYPE - > content-type: $MEDIATYPE - > user-agent: test - > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'} - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> POST /api/exp-http-v2-0003/ro/customreadonly HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> accept: application/mercurial-exp-framing-0006\r\n - s> content-type: application/mercurial-exp-framing-0006\r\n - s> user-agent: test\r\n - s> content-length: 29\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly - s> makefile('rb', None) - s> HTTP/1.1 200 OK\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: application/mercurial-exp-framing-0006\r\n - s> Transfer-Encoding: chunked\r\n - s> \r\n - s> 11\r\n - s> \t\x00\x00\x01\x00\x02\x01\x92Hidentity - s> \r\n - s> 13\r\n - s> \x0b\x00\x00\x01\x00\x02\x041\xa1FstatusBok - s> \r\n - s> 27\r\n - s> \x1f\x00\x00\x01\x00\x02\x041X\x1dcustomreadonly bytes response - s> \r\n - s> 8\r\n - s> \x00\x00\x00\x01\x00\x02\x002 - s> \r\n - s> 0\r\n - s> \r\n - -Multiple requests to "multirequest" URL are allowed - - $ sendhttpraw << EOF - > httprequest POST api/$HTTPV2/ro/multirequest - > accept: $MEDIATYPE - > content-type: $MEDIATYPE - > user-agent: test - > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'} - > frame 3 1 0 command-request new cbor:{b'name': b'customreadonly'} - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> POST /api/exp-http-v2-0003/ro/multirequest HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> *\r\n (glob) - s> *\r\n (glob) - s> user-agent: test\r\n - s> content-length: 58\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly\x15\x00\x00\x03\x00\x01\x00\x11\xa1DnameNcustomreadonly - s> makefile('rb', None) - s> HTTP/1.1 200 OK\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: application/mercurial-exp-framing-0006\r\n - s> Transfer-Encoding: chunked\r\n - s> \r\n - s> 11\r\n - s> \t\x00\x00\x01\x00\x02\x01\x92Hidentity - s> \r\n - s> 13\r\n - s> \x0b\x00\x00\x01\x00\x02\x041\xa1FstatusBok - s> \r\n - s> 27\r\n - s> \x1f\x00\x00\x01\x00\x02\x041X\x1dcustomreadonly bytes response - s> \r\n - s> 8\r\n - s> \x00\x00\x00\x01\x00\x02\x002 - s> \r\n - s> 13\r\n - s> \x0b\x00\x00\x03\x00\x02\x041\xa1FstatusBok - s> \r\n - s> 27\r\n - s> \x1f\x00\x00\x03\x00\x02\x041X\x1dcustomreadonly bytes response - s> \r\n - s> 8\r\n - s> \x00\x00\x00\x03\x00\x02\x002 - s> \r\n - s> 0\r\n - s> \r\n - -Interleaved requests to "multirequest" are processed - - $ sendhttpraw << EOF - > httprequest POST api/$HTTPV2/ro/multirequest - > accept: $MEDIATYPE - > content-type: $MEDIATYPE - > user-agent: test - > frame 1 1 stream-begin command-request new|more \xa2Dargs\xa1Inamespace - > frame 3 1 0 command-request new|more \xa2Dargs\xa1Inamespace - > frame 3 1 0 command-request continuation JnamespacesDnameHlistkeys - > frame 1 1 0 command-request continuation IbookmarksDnameHlistkeys - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> POST /api/exp-http-v2-0003/ro/multirequest HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> accept: application/mercurial-exp-framing-0006\r\n - s> content-type: application/mercurial-exp-framing-0006\r\n - s> user-agent: test\r\n - s> content-length: 115\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> \x11\x00\x00\x01\x00\x01\x01\x15\xa2Dargs\xa1Inamespace\x11\x00\x00\x03\x00\x01\x00\x15\xa2Dargs\xa1Inamespace\x19\x00\x00\x03\x00\x01\x00\x12JnamespacesDnameHlistkeys\x18\x00\x00\x01\x00\x01\x00\x12IbookmarksDnameHlistkeys - s> makefile('rb', None) - s> HTTP/1.1 200 OK\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: application/mercurial-exp-framing-0006\r\n - s> Transfer-Encoding: chunked\r\n - s> \r\n - s> 11\r\n - s> \t\x00\x00\x03\x00\x02\x01\x92Hidentity - s> \r\n - s> 13\r\n - s> \x0b\x00\x00\x03\x00\x02\x041\xa1FstatusBok - s> \r\n - s> 28\r\n - s> \x00\x00\x03\x00\x02\x041\xa3Ibookmarks@Jnamespaces@Fphases@ - s> \r\n - s> 8\r\n - s> \x00\x00\x00\x03\x00\x02\x002 - s> \r\n - s> 13\r\n - s> \x0b\x00\x00\x01\x00\x02\x041\xa1FstatusBok - s> \r\n - s> 9\r\n - s> \x01\x00\x00\x01\x00\x02\x041\xa0 - s> \r\n - s> 8\r\n - s> \x00\x00\x00\x01\x00\x02\x002 - s> \r\n - s> 0\r\n - s> \r\n - -Restart server to disable read-write access - - $ killdaemons.py - $ cat > server/.hg/hgrc << EOF - > [experimental] - > web.apiserver = true - > web.api.debugreflect = true - > web.api.http-v2 = true - > [web] - > push_ssl = false - > EOF - - $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log - $ cat hg.pid > $DAEMON_PIDS - -Attempting to run a read-write command via multirequest on read-only URL is not allowed - - $ sendhttpraw << EOF - > httprequest POST api/$HTTPV2/ro/multirequest - > accept: $MEDIATYPE - > content-type: $MEDIATYPE - > user-agent: test - > frame 1 1 stream-begin command-request new cbor:{b'name': b'pushkey'} - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> POST /api/exp-http-v2-0003/ro/multirequest HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> accept: application/mercurial-exp-framing-0006\r\n - s> content-type: application/mercurial-exp-framing-0006\r\n - s> user-agent: test\r\n - s> content-length: 22\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> \x0e\x00\x00\x01\x00\x01\x01\x11\xa1DnameGpushkey - s> makefile('rb', None) - s> HTTP/1.1 403 Forbidden\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: text/plain\r\n - s> Content-Length: 52\r\n - s> \r\n - s> insufficient permissions to execute command: pushkey - -Defining an invalid content encoding results in warning - - $ hg --config experimental.httppeer.v2-encoder-order=identity,badencoder --verbose debugwireproto --nologhandshake --peer http2 http://$LOCALIP:$HGPORT/ << EOF - > command heads - > EOF - creating http peer for wire protocol version 2 - sending heads command - wire protocol version 2 encoder referenced in config (badencoder) is not known; ignoring - s> setsockopt(6, 1, 1) -> None (?) - s> POST /api/exp-http-v2-0003/ro/heads HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> accept: application/mercurial-exp-framing-0006\r\n - s> content-type: application/mercurial-exp-framing-0006\r\n - s> content-length: 56\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> user-agent: Mercurial debugwireproto\r\n - s> \r\n - s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity\x0c\x00\x00\x01\x00\x01\x00\x11\xa1DnameEheads - s> makefile('rb', None) - s> HTTP/1.1 200 OK\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: application/mercurial-exp-framing-0006\r\n - s> Transfer-Encoding: chunked\r\n - s> \r\n - s> 11\r\n - s> \t\x00\x00\x01\x00\x02\x01\x92 - s> Hidentity - s> \r\n - s> 13\r\n - s> \x0b\x00\x00\x01\x00\x02\x041 - s> \xa1FstatusBok - s> \r\n - s> 1e\r\n - s> \x16\x00\x00\x01\x00\x02\x041 - s> \x81T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 - s> \r\n - s> 8\r\n - s> \x00\x00\x00\x01\x00\x02\x002 - s> \r\n - s> 0\r\n - s> \r\n - response: [ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ] - (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob) - -#if zstd - - $ hg --verbose debugwireproto --nologhandshake --peer http2 http://$LOCALIP:$HGPORT/ << EOF - > command heads - > EOF - creating http peer for wire protocol version 2 - sending heads command - s> setsockopt(6, 1, 1) -> None (?) - s> POST /api/exp-http-v2-0003/ro/heads HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> accept: application/mercurial-exp-framing-0006\r\n - s> content-type: application/mercurial-exp-framing-0006\r\n - s> content-length: 70\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> user-agent: Mercurial debugwireproto\r\n - s> \r\n - s> *\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x83Hzstd-8mbDzlibHidentity\x0c\x00\x00\x01\x00\x01\x00\x11\xa1DnameEheads - s> makefile('rb', None) - s> HTTP/1.1 200 OK\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: application/mercurial-exp-framing-0006\r\n - s> Transfer-Encoding: chunked\r\n - s> \r\n - s> 11\r\n - s> \t\x00\x00\x01\x00\x02\x01\x92 - s> Hzstd-8mb - s> \r\n - s> 25\r\n - s> \x1d\x00\x00\x01\x00\x02\x042 - s> (\xb5/\xfd\x00X\xa4\x00\x00p\xa1FstatusBok\x81T\x00\x01\x00\tP\x02 - s> \r\n - s> 0\r\n - s> \r\n - response: [ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ] - (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob) - -#endif - - $ cat error.log diff --git a/tests/test-http-api.t b/tests/test-http-api.t deleted file mode 100644 --- a/tests/test-http-api.t +++ /dev/null @@ -1,299 +0,0 @@ -#require no-chg - - $ send() { - > hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/ - > } - - $ hg init server - $ hg -R server serve -p $HGPORT -d --pid-file hg.pid - $ cat hg.pid > $DAEMON_PIDS - -Request to /api fails unless web.apiserver is enabled - - $ get-with-headers.py $LOCALIP:$HGPORT api - 400 no such method: api - - - - - - - - - - $TESTTMP/server: error - - - -
- - -
- - -

error

- - - - -
-

- An error occurred while processing your request: -

-

- no such method: api -

-
-
-
- - - - - - - [1] - - $ get-with-headers.py $LOCALIP:$HGPORT api/ - 400 no such method: api - - - - - - - - - - $TESTTMP/server: error - - - -
- - -
- - -

error

- - - - -
-

- An error occurred while processing your request: -

-

- no such method: api -

-
-
-
- - - - - - - [1] - -Restart server with support for API server - - $ killdaemons.py - $ cat > server/.hg/hgrc << EOF - > [experimental] - > web.apiserver = true - > EOF - - $ hg -R server serve -p $HGPORT -d --pid-file hg.pid - $ cat hg.pid > $DAEMON_PIDS - -/api lists available APIs (empty since none are available by default) - - $ send << EOF - > httprequest GET api - > user-agent: test - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> GET /api HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> user-agent: test\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 200 OK\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: text/plain\r\n - s> Content-Length: 100\r\n - s> \r\n - s> APIs can be accessed at /api/, where can be one of the following:\n - s> \n - s> (no available APIs)\n - - $ send << EOF - > httprequest GET api/ - > user-agent: test - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> GET /api/ HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> user-agent: test\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 200 OK\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: text/plain\r\n - s> Content-Length: 100\r\n - s> \r\n - s> APIs can be accessed at /api/, where can be one of the following:\n - s> \n - s> (no available APIs)\n - -Accessing an unknown API yields a 404 - - $ send << EOF - > httprequest GET api/unknown - > user-agent: test - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> GET /api/unknown HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> user-agent: test\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 404 Not Found\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: text/plain\r\n - s> Content-Length: 33\r\n - s> \r\n - s> Unknown API: unknown\n - s> Known APIs: - -Accessing a known but not enabled API yields a different error - - $ send << EOF - > httprequest GET api/exp-http-v2-0003 - > user-agent: test - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> GET /api/exp-http-v2-0003 HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> user-agent: test\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 404 Not Found\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: text/plain\r\n - s> Content-Length: 33\r\n - s> \r\n - s> API exp-http-v2-0003 not enabled\n - -Restart server with support for HTTP v2 API - - $ killdaemons.py - $ cat > server/.hg/hgrc << EOF - > [experimental] - > web.apiserver = true - > web.api.http-v2 = true - > EOF - - $ hg -R server serve -p $HGPORT -d --pid-file hg.pid - $ cat hg.pid > $DAEMON_PIDS - -/api lists the HTTP v2 protocol as available - - $ send << EOF - > httprequest GET api - > user-agent: test - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> GET /api HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> user-agent: test\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 200 OK\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: text/plain\r\n - s> Content-Length: 96\r\n - s> \r\n - s> APIs can be accessed at /api/, where can be one of the following:\n - s> \n - s> exp-http-v2-0003 - - $ send << EOF - > httprequest GET api/ - > user-agent: test - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> GET /api/ HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> user-agent: test\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 200 OK\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: text/plain\r\n - s> Content-Length: 96\r\n - s> \r\n - s> APIs can be accessed at /api/, where can be one of the following:\n - s> \n - s> exp-http-v2-0003 diff --git a/tests/test-http-bad-server.t b/tests/test-http-bad-server.t --- a/tests/test-http-bad-server.t +++ b/tests/test-http-bad-server.t @@ -30,14 +30,15 @@ and because debugging partial responses $ cat > .hg/hgrc << EOF > [extensions] - > badserver = $TESTDIR/badserverext.py + > badserver = $TESTDIR/testlib/badserverext.py > [server] > compressionengines = none > EOF Failure to accept() socket should result in connection related error message +---------------------------------------------------------------------------- - $ hg serve --config badserver.closebeforeaccept=true -p $HGPORT -d --pid-file=hg.pid + $ hg serve --config badserver.close-before-accept=true -p $HGPORT -d --pid-file=hg.pid $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone @@ -50,8 +51,9 @@ So ensure the process is dead.) $ killdaemons.py $DAEMON_PIDS Failure immediately after accept() should yield connection related error message +-------------------------------------------------------------------------------- - $ hg serve --config badserver.closeafteraccept=true -p $HGPORT -d --pid-file=hg.pid + $ hg serve --config badserver.close-after-accept=true -p $HGPORT -d --pid-file=hg.pid $ cat hg.pid > $DAEMON_PIDS TODO: this usually outputs good results, but sometimes emits abort: @@ -69,8 +71,9 @@ The flakiness in this output was observa $ killdaemons.py $DAEMON_PIDS Failure to read all bytes in initial HTTP request should yield connection related error message +----------------------------------------------------------------------------------------------- - $ hg serve --config badserver.closeafterrecvbytes=1 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve --config badserver.close-after-recv-bytes=1 -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone @@ -80,31 +83,18 @@ Failure to read all bytes in initial HTT $ killdaemons.py $DAEMON_PIDS $ cat error.log - readline(1 from 65537) -> (1) G + readline(1 from ~) -> (1) G read limit reached; closing socket $ rm -f error.log Same failure, but server reads full HTTP request line - - $ hg serve --config badserver.closeafterrecvbytes=40 -p $HGPORT -d --pid-file=hg.pid -E error.log - $ cat hg.pid > $DAEMON_PIDS - $ hg clone http://localhost:$HGPORT/ clone - abort: error: bad HTTP status line: * (glob) - [100] - - $ killdaemons.py $DAEMON_PIDS +----------------------------------------------------- - $ cat error.log - readline(40 from 65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n - readline(7 from *) -> (7) Accept- (glob) - read limit reached; closing socket - - $ rm -f error.log - -Failure on subsequent HTTP request on the same socket (cmd?batch) - - $ hg serve --config badserver.closeafterrecvbytes=210,223 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve \ + > --config badserver.close-after-recv-patterns="GET /\?cmd=capabilities" \ + > --config badserver.close-after-recv-bytes=7 \ + > -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone abort: error: bad HTTP status line: * (glob) @@ -113,40 +103,65 @@ Failure on subsequent HTTP request on th $ killdaemons.py $DAEMON_PIDS $ cat error.log - readline(210 from 65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n - readline(177 from *) -> (27) Accept-Encoding: identity\r\n (glob) - readline(150 from *) -> (35) accept: application/mercurial-0.1\r\n (glob) - 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: 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 !) + readline(~) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n + readline(7 from *) -> (7) Accept- (glob) + read limit reached; closing socket + + $ rm -f error.log + +Failure on subsequent HTTP request on the same socket (cmd?batch) +----------------------------------------------------------------- + + $ hg serve \ + > --config badserver.close-after-recv-patterns="GET /\?cmd=batch,GET /\?cmd=batch" \ + > --config badserver.close-after-recv-bytes=15,197 \ + > -p $HGPORT -d --pid-file=hg.pid -E error.log + $ cat hg.pid > $DAEMON_PIDS + $ hg clone http://localhost:$HGPORT/ clone + abort: error: bad HTTP status line: * (glob) + [100] + + $ killdaemons.py $DAEMON_PIDS + + $ cat error.log + readline(~) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n + readline(*) -> (27) Accept-Encoding: identity\r\n (glob) + readline(*) -> (35) accept: application/mercurial-0.1\r\n (glob) + readline(*) -> (*) 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) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: *\r\n\r\n (glob) (py36 !) + sendall(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (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: *\r\n\r\n (glob) (py3 no-py36 !) + write(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (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: 431\r\n (no-py3 !) + write(21) -> Content-Length: *\r\n (glob) (no-py3 !) write(2) -> \r\n (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) + write(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (no-py3 !) + readline(~) -> (26) GET /?cmd=batch HTTP/1.1\r\n (glob) + readline(*) -> (1?) Accept-Encoding* (glob) read limit reached; closing socket - readline(223 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n - readline(197 from *) -> (27) Accept-Encoding: identity\r\n (glob) - readline(170 from *) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n (glob) - readline(141 from *) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n (glob) - readline(100 from *) -> (61) x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n (glob) - readline(39 from *) -> (35) accept: application/mercurial-0.1\r\n (glob) + readline(~) -> (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) + readline(*) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\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(4 from *) -> (4) host (glob) read limit reached; closing socket $ rm -f error.log Failure to read getbundle HTTP request +-------------------------------------- - $ hg serve --config badserver.closeafterrecvbytes=308,317,304 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve \ + > --config badserver.close-after-recv-patterns="GET /\?cmd=batch,user-agent: mercurial/proto-1.0,GET /\?cmd=getbundle" \ + > --config badserver.close-after-recv-bytes=110,26,274 \ + > -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone requesting all changes @@ -158,38 +173,38 @@ Failure to read getbundle HTTP request $ cat error.log readline(1 from -1) -> (1) x (?) readline(1 from -1) -> (1) x (?) - readline(308 from 65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n - readline(275 from *) -> (27) Accept-Encoding: identity\r\n (glob) - readline(248 from *) -> (35) accept: application/mercurial-0.1\r\n (glob) - 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: 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 !) + readline(~) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n + readline(*) -> (27) Accept-Encoding: identity\r\n (glob) + readline(*) -> (35) accept: application/mercurial-0.1\r\n (glob) + readline(*) -> (*) 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) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: *\r\n\r\n (glob) (py36 !) + sendall(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (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: *\r\n\r\n (glob) (py3 no-py36 !) + write(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (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: 431\r\n (no-py3 !) + write(21) -> Content-Length: *\r\n (glob) (no-py3 !) write(2) -> \r\n (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) - readline(5? from *) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n (glob) - readline(1? from *) -> (1?) x-hgproto-1:* (glob) + write(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (no-py3 !) + readline(~) -> (26) GET /?cmd=batch HTTP/1.1\r\n (glob) + readline(*) -> (27) Accept-Encoding: identity\r\n (glob) + readline(*) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n (glob) + readline(*) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n (glob) + readline(*) -> (1?) x-hgproto-1:* (glob) read limit reached; closing socket - readline(317 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n - readline(291 from *) -> (27) Accept-Encoding: identity\r\n (glob) - readline(264 from *) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n (glob) - readline(235 from *) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n (glob) - readline(194 from *) -> (61) x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n (glob) - readline(133 from *) -> (35) accept: application/mercurial-0.1\r\n (glob) - readline(98 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) + readline(~) -> (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) + readline(*) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\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(*) -> (*) 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(159) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 42\r\n\r\n (py36 !) sendall(42) -> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n; (py36 !) write(159) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 42\r\n\r\n (py3 no-py36 !) @@ -201,9 +216,9 @@ Failure to read getbundle HTTP request write(20) -> Content-Length: 42\r\n (no-py3 !) write(2) -> \r\n (no-py3 !) write(42) -> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n; (no-py3 !) - readline(* from 65537) -> (*) GET /?cmd=getbundle HTTP* (glob) + readline(24 from ~) -> (*) GET /?cmd=getbundle HTTP* (glob) read limit reached; closing socket - readline(304 from 65537) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n + readline(~) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n readline(274 from *) -> (27) Accept-Encoding: identity\r\n (glob) readline(247 from *) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n (glob) readline(218 from *) -> (218) x-hgarg-1: bookmarks=1&bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtag (glob) @@ -212,8 +227,13 @@ Failure to read getbundle HTTP request $ rm -f error.log Now do a variation using POST to send arguments +=============================================== - $ hg serve --config experimental.httppostargs=true --config badserver.closeafterrecvbytes=329,344 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve \ + > --config badserver.close-after-recv-patterns="x-hgargs-post:,user-agent: mercurial/proto-1.0" \ + > --config badserver.close-after-recv-bytes="14,26" \ + > --config experimental.httppostargs=true \ + > -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone @@ -223,57 +243,58 @@ Now do a variation using POST to send ar $ killdaemons.py $DAEMON_PIDS $ cat error.log | "$PYTHON" $TESTDIR/filtertraceback.py - readline(329 from 65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n - readline(296 from *) -> (27) Accept-Encoding: identity\r\n (glob) - readline(269 from *) -> (35) accept: application/mercurial-0.1\r\n (glob) - 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: 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 !) + readline(~) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n + readline(*) -> (27) Accept-Encoding: identity\r\n (glob) + readline(*) -> (35) accept: application/mercurial-0.1\r\n (glob) + 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) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: *\r\n\r\n (glob) (py36 !) + sendall(*) -> 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=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (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: *\r\n\r\n (glob) (py3 no-py36 !) + write(*) -> 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=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (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: 444\r\n (no-py3 !) + write(21) -> Content-Length: *\r\n (glob) (no-py3 !) write(2) -> \r\n (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) - readline(6? from *) -> (33) vary: X-HgArgs-Post,X-HgProto-1\r\n (glob) - readline(3? from *) -> (19) x-hgargs-post: 28\r\n (glob) - readline(1? from *) -> (1?) x-hgproto-1: * (glob) + write(*) -> 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=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (no-py3 !) + readline(~) -> (27) POST /?cmd=batch HTTP/1.1\r\n (glob) + readline(*) -> (27) Accept-Encoding: identity\r\n (glob) + readline(*) -> (41) content-type: application/mercurial-0.1\r\n (glob) + readline(*) -> (33) vary: X-HgArgs-Post,X-HgProto-1\r\n (glob) + readline(*) -> (19) x-hgargs-post: 28\r\n (glob) + readline(*) -> (1?) x-hgproto-1: * (glob) read limit reached; closing socket - readline(344 from 65537) -> (27) POST /?cmd=batch HTTP/1.1\r\n - readline(317 from *) -> (27) Accept-Encoding: identity\r\n (glob) - readline(290 from *) -> (41) content-type: application/mercurial-0.1\r\n (glob) - readline(249 from *) -> (33) vary: X-HgArgs-Post,X-HgProto-1\r\n (glob) - readline(216 from *) -> (19) x-hgargs-post: 28\r\n (glob) - readline(197 from *) -> (61) x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n (glob) - readline(136 from *) -> (35) accept: application/mercurial-0.1\r\n (glob) - readline(101 from *) -> (20) content-length: 28\r\n (glob) - readline(81 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) - read(* from 28) -> (*) cmds=* (glob) - read limit reached, closing socket + readline(~) -> (27) POST /?cmd=batch HTTP/1.1\r\n + readline(*) -> (27) Accept-Encoding: identity\r\n (glob) + readline(*) -> (41) content-type: application/mercurial-0.1\r\n (glob) + readline(*) -> (33) vary: X-HgArgs-Post,X-HgProto-1\r\n (glob) + readline(*) -> (19) x-hgargs-post: 28\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(*) -> (20) content-length: 28\r\n (glob) + readline(*) -> (*) 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) + read(24 from 28) -> (*) cmds=* (glob) + read limit reached; closing socket $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=batch': (glob) Traceback (most recent call last): Exception: connection closed after receiving N bytes write(126) -> HTTP/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 Now move on to partial server responses +======================================= Server sends a single character from the HTTP response line +----------------------------------------------------------- - $ hg serve --config badserver.closeaftersendbytes=1 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve --config badserver.close-after-send-bytes=1 -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone @@ -283,7 +304,7 @@ Server sends a single character from the $ killdaemons.py $DAEMON_PIDS $ cat error.log | "$PYTHON" $TESTDIR/filtertraceback.py - readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n + readline(~) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n readline(*) -> (27) Accept-Encoding: identity\r\n (glob) readline(*) -> (35) accept: application/mercurial-0.1\r\n (glob) readline(*) -> (2?) host: localhost:$HGPORT\r\n (glob) @@ -297,41 +318,43 @@ Server sends a single character from the 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: 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 !) + 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: *\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 (glob) (py3 no-py36 !) $ rm -f error.log Server sends an incomplete capabilities response body +----------------------------------------------------- - $ hg serve --config badserver.closeaftersendbytes=180 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve \ + > --config badserver.close-after-send-patterns='batch branchmap bund' \ + > -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone - abort: HTTP request error (incomplete response; expected 431 bytes got 20) + abort: HTTP request error (incomplete response; expected * bytes got 20) (glob) (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator) [255] $ killdaemons.py $DAEMON_PIDS $ cat error.log | "$PYTHON" $TESTDIR/filtertraceback.py - readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n + readline(~) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n readline(*) -> (27) Accept-Encoding: identity\r\n (glob) readline(*) -> (35) accept: application/mercurial-0.1\r\n (glob) 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: 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: 431\r\n (no-py3 !) - write(2 from 2) -> (20) \r\n (no-py3 !) - write(20 from 431) -> (0) batch branchmap bund (no-py3 !) + 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: *\r\n\r\n (glob) (py36 !) + sendall(20 from *) -> (0) batch branchmap bund (glob) (py36 !) + write(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: *\r\n\r\n (glob) (py3 no-py36 !) + write(20 from *) -> (0) batch branchmap bund (glob) (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: *\r\n (glob) (no-py3 !) + write(2) -> \r\n (no-py3 !) + write(20 from *) -> (0) batch branchmap bund (glob) (no-py3 !) write limit reached; closing socket $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=capabilities': (glob) Traceback (most recent call last): @@ -341,8 +364,11 @@ Server sends an incomplete capabilities $ rm -f error.log Server sends incomplete headers for batch request +------------------------------------------------- - $ hg serve --config badserver.closeaftersendbytes=709 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve \ + > --config badserver.close-after-send-patterns='(.*Content-Type: applicat){2}' \ + > -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS TODO this output is horrible @@ -358,24 +384,24 @@ TODO this output is horrible $ killdaemons.py $DAEMON_PIDS $ cat error.log | "$PYTHON" $TESTDIR/filtertraceback.py - readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n + readline(~) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n readline(*) -> (27) Accept-Encoding: identity\r\n (glob) readline(*) -> (35) accept: application/mercurial-0.1\r\n (glob) 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) -> (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 + 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: *\r\n\r\n (glob) (py36 !) + sendall(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (py36 !) + write(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: *\r\n\r\n (glob) (py3 no-py36 !) + write(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (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: *\r\n (glob) (no-py3 !) + write(2) -> \r\n (no-py3 !) + write(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (no-py3 !) + readline(~) -> (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) readline(*) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n (glob) @@ -386,9 +412,9 @@ TODO this output is horrible readline(*) -> (2) \r\n (glob) sendall(118 from 159) -> (0) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: applicat (py36 !) write(118 from 159) -> (0) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: applicat (py3 no-py36 !) - write(36 from 36) -> (82) HTTP/1.1 200 Script output follows\r\n (no-py3 !) - write(23 from 23) -> (59) Server: badhttpserver\r\n (no-py3 !) - write(37 from 37) -> (22) Date: $HTTP_DATE$\r\n (no-py3 !) + 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(22 from 41) -> (0) Content-Type: applicat (no-py3 !) write limit reached; closing socket $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=batch': (glob) @@ -396,13 +422,15 @@ TODO this output is horrible Exception: connection closed after sending N bytes write(285) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 42\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 Server sends an incomplete HTTP response body to batch request +-------------------------------------------------------------- - $ hg serve --config badserver.closeaftersendbytes=774 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve \ + > --config badserver.close-after-send-patterns=96ee1d7354c4ad7372047672 \ + > -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() @@ -417,24 +445,24 @@ TODO client spews a stack due to uncaugh $ killdaemons.py $DAEMON_PIDS $ cat error.log | "$PYTHON" $TESTDIR/filtertraceback.py - readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n + readline(~) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n readline(*) -> (27) Accept-Encoding: identity\r\n (glob) readline(*) -> (35) accept: application/mercurial-0.1\r\n (glob) 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) -> (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 + 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: *\r\n\r\n (glob) (py36 !) + sendall(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (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: *\r\n\r\n (glob) (py3 no-py36 !) + write(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (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: *\r\n (glob) (no-py3 !) + write(2) -> \r\n (no-py3 !) + write(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (no-py3 !) + readline(~) -> (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) readline(*) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n (glob) @@ -443,16 +471,16 @@ TODO client spews a stack due to uncaugh 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(159 from 159) -> (24) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 42\r\n\r\n (py36 !) + sendall(159) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 42\r\n\r\n (py36 !) sendall(24 from 42) -> (0) 96ee1d7354c4ad7372047672 (py36 !) - write(159 from 159) -> (24) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 42\r\n\r\n (py3 no-py36 !) + write(159) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 42\r\n\r\n (py3 no-py36 !) write(24 from 42) -> (0) 96ee1d7354c4ad7372047672 (py3 no-py36 !) - write(36 from 36) -> (147) HTTP/1.1 200 Script output follows\r\n (no-py3 !) - write(23 from 23) -> (124) Server: badhttpserver\r\n (no-py3 !) - write(37 from 37) -> (87) Date: $HTTP_DATE$\r\n (no-py3 !) - write(41 from 41) -> (46) Content-Type: application/mercurial-0.1\r\n (no-py3 !) - write(20 from 20) -> (26) Content-Length: 42\r\n (no-py3 !) - write(2 from 2) -> (24) \r\n (no-py3 !) + 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(20) -> Content-Length: 42\r\n (no-py3 !) + write(2) -> \r\n (no-py3 !) write(24 from 42) -> (0) 96ee1d7354c4ad7372047672 (no-py3 !) write limit reached; closing socket $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=batch': (glob) @@ -463,8 +491,11 @@ TODO client spews a stack due to uncaugh $ rm -f error.log Server sends incomplete headers for getbundle response +------------------------------------------------------ - $ hg serve --config badserver.closeaftersendbytes=921 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve \ + > --config badserver.close-after-send-patterns='(.*Content-Type: application/mercuri){3}' \ + > -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS TODO this output is terrible @@ -481,24 +512,24 @@ TODO this output is terrible $ killdaemons.py $DAEMON_PIDS $ cat error.log | "$PYTHON" $TESTDIR/filtertraceback.py - readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n + readline(~) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n readline(*) -> (27) Accept-Encoding: identity\r\n (glob) readline(*) -> (35) accept: application/mercurial-0.1\r\n (glob) 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) -> (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 + 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: *\r\n\r\n (glob) (py36 !) + sendall(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (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: *\r\n\r\n (glob) (py3 no-py36 !) + write(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (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: *\r\n (glob) (no-py3 !) + write(2) -> \r\n (no-py3 !) + write(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (no-py3 !) + readline(~) -> (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) readline(*) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n (glob) @@ -507,18 +538,18 @@ TODO this output is terrible 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(159 from 159) -> (171) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 42\r\n\r\n (py36 !) - sendall(42 from 42) -> (129) 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n; (py36 !) - write(159 from 159) -> (171) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 42\r\n\r\n (py3 no-py36 !) - write(42 from 42) -> (129) 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n; (py3 no-py36 !) - write(36 from 36) -> (294) HTTP/1.1 200 Script output follows\r\n (no-py3 !) - write(23 from 23) -> (271) Server: badhttpserver\r\n (no-py3 !) - write(37 from 37) -> (234) Date: $HTTP_DATE$\r\n (no-py3 !) - write(41 from 41) -> (193) Content-Type: application/mercurial-0.1\r\n (no-py3 !) - write(20 from 20) -> (173) Content-Length: 42\r\n (no-py3 !) - write(2 from 2) -> (171) \r\n (no-py3 !) - write(42 from 42) -> (129) 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n; (no-py3 !) - readline(65537) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n + sendall(159) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 42\r\n\r\n (py36 !) + sendall(42) -> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n; (py36 !) + write(159) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 42\r\n\r\n (py3 no-py36 !) + write(42) -> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n; (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(20) -> Content-Length: 42\r\n (no-py3 !) + write(2) -> \r\n (no-py3 !) + write(42) -> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n; (no-py3 !) + readline(~) -> (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(*) -> (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) @@ -529,9 +560,9 @@ TODO this output is terrible readline(*) -> (2) \r\n (glob) sendall(129 from 167) -> (0) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercuri (py36 !) write(129 from 167) -> (0) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercuri (py3 no-py36 !) - write(36 from 36) -> (93) HTTP/1.1 200 Script output follows\r\n (no-py3 !) - write(23 from 23) -> (70) Server: badhttpserver\r\n (no-py3 !) - write(37 from 37) -> (33) Date: $HTTP_DATE$\r\n (no-py3 !) + 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(33 from 41) -> (0) Content-Type: application/mercuri (no-py3 !) write limit reached; closing socket $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=getbundle': (glob) @@ -539,13 +570,15 @@ TODO this output is terrible Exception: connection closed after sending N bytes write(293) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\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 Server stops before it sends transfer encoding +---------------------------------------------- - $ hg serve --config badserver.closeaftersendbytes=954 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve \ + > --config badserver.close-after-send-patterns="Transfer-Encoding: chunke" \ + > -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone @@ -556,25 +589,34 @@ Server stops before it sends transfer en $ killdaemons.py $DAEMON_PIDS #if py36 - $ "$PYTHON" $TESTDIR/filtertraceback.py < error.log | tail -3 + $ "$PYTHON" $TESTDIR/filtertraceback.py < error.log | tail -6 + sendall(162 from 167) -> (0) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunke + write limit reached; closing socket + $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=getbundle': (glob) Traceback (most recent call last): Exception: connection closed after sending N bytes #else - $ "$PYTHON" $TESTDIR/filtertraceback.py < error.log | tail -4 + $ "$PYTHON" $TESTDIR/filtertraceback.py < error.log | tail -7 + write(41) -> Content-Type: application/mercurial-0.2\r\n + write(25 from 28) -> (0) Transfer-Encoding: chunke + write limit reached; closing socket + $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=getbundle': (glob) Traceback (most recent call last): Exception: connection closed after sending N bytes + write(293) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\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 !) - write(293) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\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 !) - write(36) -> HTTP/1.1 500 Internal Server Error\r\n (no-py3 !) #endif $ rm -f error.log Server sends empty HTTP body for getbundle +------------------------------------------ - $ hg serve --config badserver.closeaftersendbytes=959 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve \ + > --config badserver.close-after-send-patterns='Transfer-Encoding: chunked\r\n\r\n' \ + > -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone @@ -586,24 +628,24 @@ Server sends empty HTTP body for getbund $ killdaemons.py $DAEMON_PIDS $ cat error.log | "$PYTHON" $TESTDIR/filtertraceback.py - readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n + readline(~) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n readline(*) -> (27) Accept-Encoding: identity\r\n (glob) readline(*) -> (35) accept: application/mercurial-0.1\r\n (glob) 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) -> (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 + 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: *\r\n\r\n (glob) (py36 !) + sendall(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (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: *\r\n\r\n (glob) (py3 no-py36 !) + write(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (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: *\r\n (glob) (no-py3 !) + write(2) -> \r\n (no-py3 !) + write(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (no-py3 !) + readline(~) -> (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) readline(*) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n (glob) @@ -612,18 +654,18 @@ Server sends empty HTTP body for getbund 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(159 from 159) -> (209) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 42\r\n\r\n (py36 !) - sendall(42 from 42) -> (167) 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n; (py36 !) - write(159 from 159) -> (209) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 42\r\n\r\n (py3 no-py36 !) - write(42 from 42) -> (167) 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n; (py3 no-py36 !) - write(36 from 36) -> (332) HTTP/1.1 200 Script output follows\r\n (no-py3 !) - write(23 from 23) -> (309) Server: badhttpserver\r\n (no-py3 !) - write(37 from 37) -> (272) Date: $HTTP_DATE$\r\n (no-py3 !) - write(41 from 41) -> (231) Content-Type: application/mercurial-0.1\r\n (no-py3 !) - write(20 from 20) -> (211) Content-Length: 42\r\n (no-py3 !) - write(2 from 2) -> (209) \r\n (no-py3 !) - write(42 from 42) -> (167) 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n; (no-py3 !) - readline(65537) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n + sendall(159) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 42\r\n\r\n (py36 !) + sendall(42) -> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n; (py36 !) + write(159) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 42\r\n\r\n (py3 no-py36 !) + write(42) -> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n; (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(20) -> Content-Length: 42\r\n (no-py3 !) + write(2) -> \r\n (no-py3 !) + write(42) -> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n; (no-py3 !) + readline(~) -> (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(*) -> (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) @@ -634,11 +676,11 @@ Server sends empty HTTP body for getbund readline(*) -> (2) \r\n (glob) sendall(167 from 167) -> (0) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n (py36 !) write(167 from 167) -> (0) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n (py3 no-py36 !) - write(36 from 36) -> (131) HTTP/1.1 200 Script output follows\r\n (no-py3 !) - write(23 from 23) -> (108) Server: badhttpserver\r\n (no-py3 !) - write(37 from 37) -> (71) Date: $HTTP_DATE$\r\n (no-py3 !) - write(41 from 41) -> (30) Content-Type: application/mercurial-0.2\r\n (no-py3 !) - write(28 from 28) -> (2) Transfer-Encoding: chunked\r\n (no-py3 !) + 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.2\r\n (no-py3 !) + write(28) -> Transfer-Encoding: chunked\r\n (no-py3 !) write(2 from 2) -> (0) \r\n (no-py3 !) write limit reached; closing socket $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=getbundle': (glob) @@ -646,13 +688,15 @@ Server sends empty HTTP body for getbund Exception: connection closed after sending N bytes write(293) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\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 Server sends partial compression string +--------------------------------------- - $ hg serve --config badserver.closeaftersendbytes=983 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve \ + > --config badserver.close-after-send-patterns='4\r\nHG20\r\n' \ + > -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone @@ -664,24 +708,24 @@ Server sends partial compression string $ killdaemons.py $DAEMON_PIDS $ cat error.log | "$PYTHON" $TESTDIR/filtertraceback.py - readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n + readline(~) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n readline(*) -> (27) Accept-Encoding: identity\r\n (glob) readline(*) -> (35) accept: application/mercurial-0.1\r\n (glob) 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) -> (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 + 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: *\r\n\r\n (glob) (py36 !) + sendall(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (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: *\r\n\r\n (glob) (py3 no-py36 !) + write(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (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: *\r\n (glob) (no-py3 !) + write(2) -> \r\n (no-py3 !) + write(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (no-py3 !) + readline(~) -> (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) readline(*) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n (glob) @@ -690,17 +734,17 @@ Server sends partial compression string 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(159 from 159) -> (233) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 42\r\n\r\n (py36 !) - sendall(42 from 42) -> (191) 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n; (py36 !) - write(159 from 159) -> (233) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 42\r\n\r\n (py3 no-py36 !) - write(36 from 36) -> (356) HTTP/1.1 200 Script output follows\r\n (no-py3 !) - write(23 from 23) -> (333) Server: badhttpserver\r\n (no-py3 !) - write(37 from 37) -> (296) Date: $HTTP_DATE$\r\n (no-py3 !) - write(41 from 41) -> (255) Content-Type: application/mercurial-0.1\r\n (no-py3 !) - write(20 from 20) -> (235) Content-Length: 42\r\n (no-py3 !) - write(2 from 2) -> (233) \r\n (no-py3 !) - write(42 from 42) -> (191) 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n; (no-py3 !) - readline(65537) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n + sendall(159) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 42\r\n\r\n (py36 !) + sendall(42) -> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n; (py36 !) + write(159) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 42\r\n\r\n (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(20) -> Content-Length: 42\r\n (no-py3 !) + write(2) -> \r\n (no-py3 !) + write(42) -> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n; (no-py3 !) + readline(~) -> (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(*) -> (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) @@ -709,32 +753,34 @@ Server sends partial compression string 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(167 from 167) -> (24) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n (py36 !) - sendall(6 from 6) -> (18) 1\\r\\n\x04\\r\\n (esc) (py36 !) - sendall(9 from 9) -> (9) 4\r\nnone\r\n (py36 !) + sendall(167) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n (py36 !) + sendall(6) -> 1\\r\\n\x04\\r\\n (esc) (py36 !) + sendall(9) -> 4\r\nnone\r\n (py36 !) sendall(9 from 9) -> (0) 4\r\nHG20\r\n (py36 !) - write(167 from 167) -> (24) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n (py3 no-py36 !) - write(36 from 36) -> (155) HTTP/1.1 200 Script output follows\r\n (no-py3 !) - write(23 from 23) -> (132) Server: badhttpserver\r\n (no-py3 !) - write(37 from 37) -> (95) Date: $HTTP_DATE$\r\n (no-py3 !) - write(41 from 41) -> (54) Content-Type: application/mercurial-0.2\r\n (no-py3 !) - write(28 from 28) -> (26) Transfer-Encoding: chunked\r\n (no-py3 !) - write(2 from 2) -> (24) \r\n (no-py3 !) - write(6 from 6) -> (18) 1\\r\\n\x04\\r\\n (esc) (no-py3 !) - write(9 from 9) -> (9) 4\r\nnone\r\n (no-py3 !) + write(167) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n (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.2\r\n (no-py3 !) + write(28) -> Transfer-Encoding: chunked\r\n (no-py3 !) + write(2) -> \r\n (no-py3 !) + write(6) -> 1\\r\\n\x04\\r\\n (esc) (no-py3 !) + write(9) -> 4\r\nnone\r\n (no-py3 !) write(9 from 9) -> (0) 4\r\nHG20\r\n (no-py3 !) write limit reached; closing socket $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=getbundle': (glob) Traceback (most recent call last): Exception: connection closed after sending N bytes - write(27) -> 15\r\nInternal Server Error\r\n (no-py3 !) $ rm -f error.log Server sends partial bundle2 header magic +----------------------------------------- - $ hg serve --config badserver.closeaftersendbytes=980 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve \ + > --config badserver.close-after-send-patterns='4\r\nHG2' \ + > -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone @@ -748,9 +794,9 @@ Server sends partial bundle2 header magi #if py36 $ "$PYTHON" $TESTDIR/filtertraceback.py < error.log | tail -9 - sendall(167 from 167) -> (21) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n - sendall(6 from 6) -> (15) 1\\r\\n\x04\\r\\n (esc) - sendall(9 from 9) -> (6) 4\r\nnone\r\n + sendall(167) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n + sendall(6) -> 1\\r\\n\x04\\r\\n (esc) + sendall(9) -> 4\r\nnone\r\n sendall(6 from 9) -> (0) 4\r\nHG2 write limit reached; closing socket $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=getbundle': (glob) @@ -760,26 +806,29 @@ Server sends partial bundle2 header magi #else $ "$PYTHON" $TESTDIR/filtertraceback.py < error.log | tail -11 - readline(65537) -> (2) \r\n (py3 !) - write(167 from 167) -> (21) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n (py3 !) - write(28 from 28) -> (23) Transfer-Encoding: chunked\r\n (no-py3 !) - write(2 from 2) -> (21) \r\n (no-py3 !) - write(6 from 6) -> (15) 1\\r\\n\x04\\r\\n (esc) - write(9 from 9) -> (6) 4\r\nnone\r\n + readline(~) -> (2) \r\n (py3 !) + write(167) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n (py3 !) + write(41) -> Content-Type: application/mercurial-0.2\r\n (no-py3 !) + write(28) -> Transfer-Encoding: chunked\r\n (no-py3 !) + write(2) -> \r\n (no-py3 !) + write(6) -> 1\\r\\n\x04\\r\\n (esc) + write(9) -> 4\r\nnone\r\n write(6 from 9) -> (0) 4\r\nHG2 write limit reached; closing socket $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=getbundle': (glob) Traceback (most recent call last): Exception: connection closed after sending N bytes - write(27) -> 15\r\nInternal Server Error\r\n #endif $ rm -f error.log Server sends incomplete bundle2 stream params length +---------------------------------------------------- - $ hg serve --config badserver.closeaftersendbytes=989 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve \ + > --config badserver.close-after-send-patterns='4\r\n\0\0\0' \ + > -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone @@ -793,10 +842,10 @@ Server sends incomplete bundle2 stream p #if py36 $ "$PYTHON" $TESTDIR/filtertraceback.py < error.log | tail -10 - sendall(167 from 167) -> (30) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n - sendall(6 from 6) -> (24) 1\\r\\n\x04\\r\\n (esc) - sendall(9 from 9) -> (15) 4\r\nnone\r\n - sendall(9 from 9) -> (6) 4\r\nHG20\r\n + sendall(167) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n + sendall(6) -> 1\\r\\n\x04\\r\\n (esc) + sendall(9) -> 4\r\nnone\r\n + sendall(9) -> 4\r\nHG20\r\n sendall(6 from 9) -> (0) 4\\r\\n\x00\x00\x00 (esc) write limit reached; closing socket $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=getbundle': (glob) @@ -806,27 +855,30 @@ Server sends incomplete bundle2 stream p #else $ "$PYTHON" $TESTDIR/filtertraceback.py < error.log | tail -12 - readline(65537) -> (2) \r\n (py3 !) - write(167 from 167) -> (30) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n (py3 !) - write(28 from 28) -> (32) Transfer-Encoding: chunked\r\n (no-py3 !) - write(2 from 2) -> (30) \r\n (no-py3 !) - write(6 from 6) -> (24) 1\\r\\n\x04\\r\\n (esc) - write(9 from 9) -> (15) 4\r\nnone\r\n - write(9 from 9) -> (6) 4\r\nHG20\r\n + readline(~) -> (2) \r\n (py3 !) + write(167) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n (py3 !) + write(41) -> Content-Type: application/mercurial-0.2\r\n + write(28) -> Transfer-Encoding: chunked\r\n (no-py3 !) + write(2) -> \r\n (no-py3 !) + write(6) -> 1\\r\\n\x04\\r\\n (esc) + write(9) -> 4\r\nnone\r\n + write(9) -> 4\r\nHG20\r\n write(6 from 9) -> (0) 4\\r\\n\x00\x00\x00 (esc) write limit reached; closing socket $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=getbundle': (glob) Traceback (most recent call last): Exception: connection closed after sending N bytes - write(27) -> 15\r\nInternal Server Error\r\n #endif $ rm -f error.log Servers stops after bundle2 stream params header +------------------------------------------------ - $ hg serve --config badserver.closeaftersendbytes=992 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve \ + > --config badserver.close-after-send-patterns='4\r\n\0\0\0\0\r\n' \ + > -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone @@ -839,10 +891,10 @@ Servers stops after bundle2 stream param #if py36 $ "$PYTHON" $TESTDIR/filtertraceback.py < error.log | tail -10 - sendall(167 from 167) -> (33) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n - sendall(6 from 6) -> (27) 1\\r\\n\x04\\r\\n (esc) - sendall(9 from 9) -> (18) 4\r\nnone\r\n - sendall(9 from 9) -> (9) 4\r\nHG20\r\n + sendall(167) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n + sendall(6) -> 1\\r\\n\x04\\r\\n (esc) + sendall(9) -> 4\r\nnone\r\n + sendall(9) -> 4\r\nHG20\r\n sendall(9 from 9) -> (0) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) write limit reached; closing socket $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=getbundle': (glob) @@ -852,27 +904,30 @@ Servers stops after bundle2 stream param #else $ "$PYTHON" $TESTDIR/filtertraceback.py < error.log | tail -12 - readline(65537) -> (2) \r\n (py3 !) - write(167 from 167) -> (33) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n (py3 !) - write(28 from 28) -> (35) Transfer-Encoding: chunked\r\n (no-py3 !) - write(2 from 2) -> (33) \r\n (no-py3 !) - write(6 from 6) -> (27) 1\\r\\n\x04\\r\\n (esc) - write(9 from 9) -> (18) 4\r\nnone\r\n - write(9 from 9) -> (9) 4\r\nHG20\r\n + readline(~) -> (2) \r\n (py3 !) + write(167) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n (py3 !) + write(41) -> Content-Type: application/mercurial-0.2\r\n + write(28) -> Transfer-Encoding: chunked\r\n (no-py3 !) + write(2) -> \r\n (no-py3 !) + write(6) -> 1\\r\\n\x04\\r\\n (esc) + write(9) -> 4\r\nnone\r\n + write(9) -> 4\r\nHG20\r\n write(9 from 9) -> (0) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) write limit reached; closing socket $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=getbundle': (glob) Traceback (most recent call last): Exception: connection closed after sending N bytes - write(27) -> 15\r\nInternal Server Error\r\n #endif $ rm -f error.log Server stops sending after bundle2 part header length +----------------------------------------------------- - $ hg serve --config badserver.closeaftersendbytes=1001 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve \ + > --config badserver.close-after-send-patterns='4\r\n\0\0\0\)\r\n' \ + > -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone @@ -885,11 +940,11 @@ Server stops sending after bundle2 part #if py36 $ "$PYTHON" $TESTDIR/filtertraceback.py < error.log | tail -11 - sendall(167 from 167) -> (42) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n - sendall(6 from 6) -> (36) 1\\r\\n\x04\\r\\n (esc) - sendall(9 from 9) -> (27) 4\r\nnone\r\n - sendall(9 from 9) -> (18) 4\r\nHG20\r\n - sendall(9 from 9) -> (9) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + sendall(167) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n + sendall(6) -> 1\\r\\n\x04\\r\\n (esc) + sendall(9) -> 4\r\nnone\r\n + sendall(9) -> 4\r\nHG20\r\n + sendall(9) -> 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) sendall(9 from 9) -> (0) 4\\r\\n\x00\x00\x00)\\r\\n (esc) write limit reached; closing socket $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=getbundle': (glob) @@ -900,28 +955,31 @@ Server stops sending after bundle2 part #else $ "$PYTHON" $TESTDIR/filtertraceback.py < error.log | tail -13 - readline(65537) -> (2) \r\n (py3 !) - write(167 from 167) -> (42) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n (py3 !) - write(28 from 28) -> (44) Transfer-Encoding: chunked\r\n (no-py3 !) - write(2 from 2) -> (42) \r\n (no-py3 !) - write(6 from 6) -> (36) 1\\r\\n\x04\\r\\n (esc) - write(9 from 9) -> (27) 4\r\nnone\r\n - write(9 from 9) -> (18) 4\r\nHG20\r\n - write(9 from 9) -> (9) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + readline(~) -> (2) \r\n (py3 !) + write(167) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n (py3 !) + write(41) -> Content-Type: application/mercurial-0.2\r\n + write(28) -> Transfer-Encoding: chunked\r\n (no-py3 !) + write(2) -> \r\n (no-py3 !) + write(6) -> 1\\r\\n\x04\\r\\n (esc) + write(9) -> 4\r\nnone\r\n + write(9) -> 4\r\nHG20\r\n + write(9) -> 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) write(9 from 9) -> (0) 4\\r\\n\x00\x00\x00)\\r\\n (esc) write limit reached; closing socket $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=getbundle': (glob) Traceback (most recent call last): Exception: connection closed after sending N bytes - write(27) -> 15\r\nInternal Server Error\r\n #endif $ rm -f error.log Server stops sending after bundle2 part header +---------------------------------------------- - $ hg serve --config badserver.closeaftersendbytes=1048 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve \ + > --config badserver.close-after-send-patterns="version02nbchanges1\\r\\n" \ + > -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone @@ -937,12 +995,12 @@ Server stops sending after bundle2 part #if py36 $ "$PYTHON" $TESTDIR/filtertraceback.py < error.log | tail -12 - sendall(167 from 167) -> (89) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n - sendall(6 from 6) -> (83) 1\\r\\n\x04\\r\\n (esc) - sendall(9 from 9) -> (74) 4\r\nnone\r\n - sendall(9 from 9) -> (65) 4\r\nHG20\r\n - sendall(9 from 9) -> (56) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) - sendall(9 from 9) -> (47) 4\\r\\n\x00\x00\x00)\\r\\n (esc) + sendall(167) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n + sendall(6) -> 1\\r\\n\x04\\r\\n (esc) + sendall(9) -> 4\r\nnone\r\n + sendall(9) -> 4\r\nHG20\r\n + sendall(9) -> 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + sendall(9) -> 4\\r\\n\x00\x00\x00)\\r\\n (esc) sendall(47 from 47) -> (0) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc) write limit reached; closing socket $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=getbundle': (glob) @@ -952,29 +1010,32 @@ Server stops sending after bundle2 part #else $ "$PYTHON" $TESTDIR/filtertraceback.py < error.log | tail -14 - readline(65537) -> (2) \r\n (py3 !) - write(167 from 167) -> (89) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n (py3 !) - write(28 from 28) -> (91) Transfer-Encoding: chunked\r\n (no-py3 !) - write(2 from 2) -> (89) \r\n (no-py3 !) - write(6 from 6) -> (83) 1\\r\\n\x04\\r\\n (esc) - write(9 from 9) -> (74) 4\r\nnone\r\n - write(9 from 9) -> (65) 4\r\nHG20\r\n - write(9 from 9) -> (56) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) - write(9 from 9) -> (47) 4\\r\\n\x00\x00\x00)\\r\\n (esc) + readline(~) -> (2) \r\n (py3 !) + write(167) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n (py3 !) + write(41) -> Content-Type: application/mercurial-0.2\r\n + write(28) -> Transfer-Encoding: chunked\r\n (no-py3 !) + write(2) -> \r\n (no-py3 !) + write(6) -> 1\\r\\n\x04\\r\\n (esc) + write(9) -> 4\r\nnone\r\n + write(9) -> 4\r\nHG20\r\n + write(9) -> 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + write(9) -> 4\\r\\n\x00\x00\x00)\\r\\n (esc) write(47 from 47) -> (0) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc) write limit reached; closing socket $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=getbundle': (glob) Traceback (most recent call last): Exception: connection closed after sending N bytes - write(27) -> 15\r\nInternal Server Error\r\n #endif $ rm -f error.log Server stops after bundle2 part payload chunk size +-------------------------------------------------- - $ hg serve --config badserver.closeaftersendbytes=1069 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve \ + > --config badserver.close-after-send-patterns='1d2\r\n.......' \ + > -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone @@ -991,14 +1052,14 @@ Server stops after bundle2 part payload #if py36 $ "$PYTHON" $TESTDIR/filtertraceback.py < error.log | tail -14 - sendall(167 from 167) -> (110) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n - sendall(6 from 6) -> (104) 1\\r\\n\x04\\r\\n (esc) - sendall(9 from 9) -> (95) 4\r\nnone\r\n - sendall(9 from 9) -> (86) 4\r\nHG20\r\n - sendall(9 from 9) -> (77) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) - sendall(9 from 9) -> (68) 4\\r\\n\x00\x00\x00)\\r\\n (esc) - sendall(47 from 47) -> (21) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc) - sendall(9 from 9) -> (12) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc) + sendall(167) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n + sendall(6) -> 1\\r\\n\x04\\r\\n (esc) + sendall(9) -> 4\r\nnone\r\n + sendall(9) -> 4\r\nHG20\r\n + sendall(9) -> 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + sendall(9) -> 4\\r\\n\x00\x00\x00)\\r\\n (esc) + sendall(47) -> 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc) + sendall(9) -> 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc) sendall(12 from 473) -> (0) 1d2\\r\\n\x00\x00\x00\xb2\x96\xee\x1d (esc) write limit reached; closing socket $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=getbundle': (glob) @@ -1008,29 +1069,32 @@ Server stops after bundle2 part payload #else $ "$PYTHON" $TESTDIR/filtertraceback.py < error.log | tail -15 - write(167 from 167) -> (110) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n (py3 !) - write(2 from 2) -> (110) \r\n (no-py3 !) - write(6 from 6) -> (104) 1\\r\\n\x04\\r\\n (esc) - write(9 from 9) -> (95) 4\r\nnone\r\n - write(9 from 9) -> (86) 4\r\nHG20\r\n - write(9 from 9) -> (77) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) - write(9 from 9) -> (68) 4\\r\\n\x00\x00\x00)\\r\\n (esc) - write(47 from 47) -> (21) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc) - write(9 from 9) -> (12) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc) + write(167) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n (py3 !) + write(28) -> Transfer-Encoding: chunked\r\n + write(2) -> \r\n (no-py3 !) + write(6) -> 1\\r\\n\x04\\r\\n (esc) + write(9) -> 4\r\nnone\r\n + write(9) -> 4\r\nHG20\r\n + write(9) -> 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + write(9) -> 4\\r\\n\x00\x00\x00)\\r\\n (esc) + write(47) -> 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc) + write(9) -> 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc) write(12 from 473) -> (0) 1d2\\r\\n\x00\x00\x00\xb2\x96\xee\x1d (esc) write limit reached; closing socket $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=getbundle': (glob) Traceback (most recent call last): Exception: connection closed after sending N bytes - write(27) -> 15\r\nInternal Server Error\r\n #endif $ rm -f error.log Server stops sending in middle of bundle2 payload chunk +------------------------------------------------------- - $ hg serve --config badserver.closeaftersendbytes=1530 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve \ + > --config badserver.close-after-send-patterns=':jL\0\0\x00\0\0\0\0\0\r\n' \ + > -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone @@ -1046,14 +1110,14 @@ Server stops sending in middle of bundle #if py36 $ "$PYTHON" $TESTDIR/filtertraceback.py < error.log | tail -14 - sendall(167 from 167) -> (571) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n - sendall(6 from 6) -> (565) 1\\r\\n\x04\\r\\n (esc) - sendall(9 from 9) -> (556) 4\r\nnone\r\n - sendall(9 from 9) -> (547) 4\r\nHG20\r\n - sendall(9 from 9) -> (538) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) - sendall(9 from 9) -> (529) 4\\r\\n\x00\x00\x00)\\r\\n (esc) - sendall(47 from 47) -> (482) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc) - sendall(9 from 9) -> (473) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc) + sendall(167) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n + sendall(6) -> 1\\r\\n\x04\\r\\n (esc) + sendall(9) -> 4\r\nnone\r\n + sendall(9) -> 4\r\nHG20\r\n + sendall(9) -> 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + sendall(9) -> 4\\r\\n\x00\x00\x00)\\r\\n (esc) + sendall(47) -> 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc) + sendall(9) -> 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc) sendall(473 from 473) -> (0) 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 (2) \r\n (py3 !) - write(167 from 167) -> (571) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n (py3 !) - write(28 from 28) -> (573) Transfer-Encoding: chunked\r\n (no-py3 !) - write(2 from 2) -> (571) \r\n (no-py3 !) - write(6 from 6) -> (565) 1\\r\\n\x04\\r\\n (esc) - write(9 from 9) -> (556) 4\r\nnone\r\n - write(9 from 9) -> (547) 4\r\nHG20\r\n - write(9 from 9) -> (538) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) - write(9 from 9) -> (529) 4\\r\\n\x00\x00\x00)\\r\\n (esc) - write(47 from 47) -> (482) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc) - write(9 from 9) -> (473) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc) + readline(~) -> (2) \r\n (py3 !) + write(167) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n (py3 !) + write(41) -> Content-Type: application/mercurial-0.2\r\n + write(28) -> Transfer-Encoding: chunked\r\n (no-py3 !) + write(2) -> \r\n (no-py3 !) + write(6) -> 1\\r\\n\x04\\r\\n (esc) + write(9) -> 4\r\nnone\r\n + write(9) -> 4\r\nHG20\r\n + write(9) -> 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + write(9) -> 4\\r\\n\x00\x00\x00)\\r\\n (esc) + write(47) -> 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc) + write(9) -> 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc) write(473 from 473) -> (0) 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 15\r\nInternal Server Error\r\n #endif $ rm -f error.log Server stops sending after 0 length payload chunk size +------------------------------------------------------ - $ hg serve --config badserver.closeaftersendbytes=1561 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve \ + > --config badserver.close-after-send-patterns=LISTKEYS \ + > -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone @@ -1106,16 +1173,16 @@ Server stops sending after 0 length payl #if py36 $ "$PYTHON" $TESTDIR/filtertraceback.py < error.log | tail -16 - sendall(6 from 6) -> (596) 1\\r\\n\x04\\r\\n (esc) - sendall(9 from 9) -> (587) 4\r\nnone\r\n - sendall(9 from 9) -> (578) 4\r\nHG20\r\n - sendall(9 from 9) -> (569) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) - sendall(9 from 9) -> (560) 4\\r\\n\x00\x00\x00)\\r\\n (esc) - sendall(47 from 47) -> (513) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc) - sendall(9 from 9) -> (504) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc) - sendall(473 from 473) -> (31) 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 (22) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) - sendall(9 from 9) -> (13) 4\\r\\n\x00\x00\x00 \\r\\n (esc) + sendall(6) -> 1\\r\\n\x04\\r\\n (esc) + sendall(9) -> 4\r\nnone\r\n + sendall(9) -> 4\r\nHG20\r\n + sendall(9) -> 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + sendall(9) -> 4\\r\\n\x00\x00\x00)\\r\\n (esc) + sendall(47) -> 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc) + sendall(9) -> 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc) + sendall(473) -> 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 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + sendall(9) -> 4\\r\\n\x00\x00\x00 \\r\\n (esc) sendall(13 from 38) -> (0) 20\\r\\n\x08LISTKEYS (esc) write limit reached; closing socket $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=getbundle': (glob) @@ -1125,31 +1192,35 @@ Server stops sending after 0 length payl #else $ "$PYTHON" $TESTDIR/filtertraceback.py < error.log | tail -17 - write(6 from 6) -> (596) 1\\r\\n\x04\\r\\n (esc) - write(9 from 9) -> (587) 4\r\nnone\r\n - write(9 from 9) -> (578) 4\r\nHG20\r\n - write(9 from 9) -> (569) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) - write(9 from 9) -> (560) 4\\r\\n\x00\x00\x00)\\r\\n (esc) - write(47 from 47) -> (513) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc) - write(9 from 9) -> (504) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc) - write(473 from 473) -> (31) 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 (22) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) - write(9 from 9) -> (13) 4\\r\\n\x00\x00\x00 \\r\\n (esc) + write(2) -> \r\n + write(6) -> 1\\r\\n\x04\\r\\n (esc) + write(9) -> 4\r\nnone\r\n + write(9) -> 4\r\nHG20\r\n + write(9) -> 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + write(9) -> 4\\r\\n\x00\x00\x00)\\r\\n (esc) + write(47) -> 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc) + write(9) -> 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc) + write(473) -> 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 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + write(9) -> 4\\r\\n\x00\x00\x00 \\r\\n (esc) write(13 from 38) -> (0) 20\\r\\n\x08LISTKEYS (esc) write limit reached; closing socket $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=getbundle': (glob) Traceback (most recent call last): Exception: connection closed after sending N bytes - write(27) -> 15\r\nInternal Server Error\r\n #endif $ rm -f error.log 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=1736 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve \ + > --config badserver.close-after-send-patterns='(.*4\r\n\0\0\0\0\r\n){5}' \ + > -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone @@ -1166,20 +1237,20 @@ This is before the 0 size chunked transf #if py36 $ "$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) -> 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + sendall(9) -> 4\\r\\n\x00\x00\x00)\\r\\n (esc) + sendall(47) -> 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc) + sendall(9) -> 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc) + sendall(473) -> 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 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + sendall(9) -> 4\\r\\n\x00\x00\x00 \\r\\n (esc) + sendall(38) -> 20\\r\\n\x08LISTKEYS\x00\x00\x00\x01\x01\x00 \x06namespacephases\\r\\n (esc) + sendall(9) -> 4\\r\\n\x00\x00\x00:\\r\\n (esc) + sendall(64) -> 3a\r\n96ee1d7354c4ad7372047672c36a1f561e3a6a4c 1\npublishing True\r\n + sendall(9) -> 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + sendall(9) -> 4\\r\\n\x00\x00\x00#\\r\\n (esc) + sendall(41) -> 23\\r\\n\x08LISTKEYS\x00\x00\x00\x02\x01\x00 namespacebookmarks\\r\\n (esc) + sendall(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 $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=getbundle': (glob) @@ -1189,35 +1260,38 @@ This is before the 0 size chunked transf #else $ "$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) -> 4\r\nHG20\r\n + write(9) -> 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + write(9) -> 4\\r\\n\x00\x00\x00)\\r\\n (esc) + write(47) -> 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc) + write(9) -> 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc) + write(473) -> 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 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + write(9) -> 4\\r\\n\x00\x00\x00 \\r\\n (esc) + write(38) -> 20\\r\\n\x08LISTKEYS\x00\x00\x00\x01\x01\x00 \x06namespacephases\\r\\n (esc) + write(9) -> 4\\r\\n\x00\x00\x00:\\r\\n (esc) + write(64) -> 3a\r\n96ee1d7354c4ad7372047672c36a1f561e3a6a4c 1\npublishing True\r\n + write(9) -> 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + write(9) -> 4\\r\\n\x00\x00\x00#\\r\\n (esc) + write(41) -> 23\\r\\n\x08LISTKEYS\x00\x00\x00\x02\x01\x00 namespacebookmarks\\r\\n (esc) + write(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 $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=getbundle': (glob) Traceback (most recent call last): Exception: connection closed after sending N bytes - write(27) -> 15\r\nInternal Server Error\r\n #endif $ rm -f error.log $ rm -rf clone Server sends a size 0 chunked-transfer size without terminating \r\n +-------------------------------------------------------------------- - $ hg serve --config badserver.closeaftersendbytes=1739 -p $HGPORT -d --pid-file=hg.pid -E error.log + $ hg serve \ + > --config badserver.close-after-send-patterns="(.*4\\r\\n\0\0\0\0\\r\\n0\r\n)" \ + > -p $HGPORT -d --pid-file=hg.pid -E error.log $ cat hg.pid > $DAEMON_PIDS $ hg clone http://localhost:$HGPORT/ clone @@ -1234,21 +1308,21 @@ Server sends a size 0 chunked-transfer s #if py36 $ "$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(9) -> 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + sendall(9) -> 4\\r\\n\x00\x00\x00)\\r\\n (esc) + sendall(47) -> 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc) + sendall(9) -> 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc) + sendall(473) -> 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 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + sendall(9) -> 4\\r\\n\x00\x00\x00 \\r\\n (esc) + sendall(38) -> 20\\r\\n\x08LISTKEYS\x00\x00\x00\x01\x01\x00 \x06namespacephases\\r\\n (esc) + sendall(9) -> 4\\r\\n\x00\x00\x00:\\r\\n (esc) + sendall(64) -> 3a\r\n96ee1d7354c4ad7372047672c36a1f561e3a6a4c 1\npublishing True\r\n + sendall(9) -> 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + sendall(9) -> 4\\r\\n\x00\x00\x00#\\r\\n (esc) + sendall(41) -> 23\\r\\n\x08LISTKEYS\x00\x00\x00\x02\x01\x00 namespacebookmarks\\r\\n (esc) + sendall(9) -> 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + sendall(9) -> 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) sendall(3 from 5) -> (0) 0\r\n write limit reached; closing socket $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=getbundle': (glob) @@ -1258,28 +1332,28 @@ Server sends a size 0 chunked-transfer s #else $ "$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(9) -> 4\r\nHG20\r\n + write(9) -> 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + write(9) -> 4\\r\\n\x00\x00\x00)\\r\\n (esc) + write(47) -> 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02 \x01version02nbchanges1\\r\\n (esc) + write(9) -> 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc) + write(473) -> 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 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + write(9) -> 4\\r\\n\x00\x00\x00 \\r\\n (esc) + write(38) -> 20\\r\\n\x08LISTKEYS\x00\x00\x00\x01\x01\x00 \x06namespacephases\\r\\n (esc) + write(9) -> 4\\r\\n\x00\x00\x00:\\r\\n (esc) + write(64) -> 3a\r\n96ee1d7354c4ad7372047672c36a1f561e3a6a4c 1\npublishing True\r\n + write(9) -> 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + write(9) -> 4\\r\\n\x00\x00\x00#\\r\\n (esc) + write(41) -> 23\\r\\n\x08LISTKEYS\x00\x00\x00\x02\x01\x00 namespacebookmarks\\r\\n (esc) + write(9) -> 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) + write(9) -> 4\\r\\n\x00\x00\x00\x00\\r\\n (esc) write(3 from 5) -> (0) 0\r\n write limit reached; closing socket $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=getbundle': (glob) Traceback (most recent call last): Exception: connection closed after sending N bytes - write(27) -> 15\r\nInternal Server Error\r\n #endif $ rm -f error.log diff --git a/tests/test-http-protocol.t b/tests/test-http-protocol.t --- a/tests/test-http-protocol.t +++ b/tests/test-http-protocol.t @@ -198,7 +198,7 @@ Test listkeys for listing namespaces s> Content-Type: application/mercurial-0.1\r\n s> Content-Length: *\r\n (glob) 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 + s> batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset compression=\$BUNDLE2_COMPRESSIONS\$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (re) sending listkeys command s> setsockopt(6, 1, 1) -> None (?) s> GET /?cmd=listkeys HTTP/1.1\r\n @@ -252,121 +252,6 @@ Same thing, but with "httprequest" comma s> bookmarks\t\n s> namespaces\t\n s> phases\t - -Client with HTTPv2 enabled advertises that and gets old capabilities response from old server - - $ hg --config experimental.httppeer.advertise-v2=true --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF - > command heads - > EOF - s> setsockopt(6, 1, 1) -> None (?) - s> GET /?cmd=capabilities HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> vary: X-HgProto-1,X-HgUpgrade-1\r\n - s> x-hgproto-1: cbor\r\n - s> x-hgupgrade-1: exp-http-v2-0003\r\n - s> accept: application/mercurial-0.1\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> user-agent: Mercurial debugwireproto\r\n - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 200 Script output follows\r\n - 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: *\r\n (glob) - 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 - sending heads command - s> setsockopt(6, 1, 1) -> None (?) - s> GET /?cmd=heads HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> vary: X-HgProto-1\r\n - s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n - s> accept: application/mercurial-0.1\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> user-agent: Mercurial debugwireproto\r\n - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 200 Script output follows\r\n - 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: 41\r\n - s> \r\n - s> 0000000000000000000000000000000000000000\n - response: [ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ] - (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob) - - $ killdaemons.py - $ enablehttpv2 empty - $ hg --config server.compressionengines=zlib -R empty serve -p $HGPORT -d --pid-file hg.pid - $ cat hg.pid > $DAEMON_PIDS - -Client with HTTPv2 enabled automatically upgrades if the server supports it - - $ hg --config experimental.httppeer.advertise-v2=true --config experimental.httppeer.v2-encoder-order=identity --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF - > command heads - > EOF - s> setsockopt(6, 1, 1) -> None (?) - s> GET /?cmd=capabilities HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> vary: X-HgProto-1,X-HgUpgrade-1\r\n - s> x-hgproto-1: cbor\r\n - s> x-hgupgrade-1: exp-http-v2-0003\r\n - s> accept: application/mercurial-0.1\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> user-agent: Mercurial debugwireproto\r\n - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 200 OK\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - 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\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 - s> Accept-Encoding: identity\r\n - s> accept: application/mercurial-exp-framing-0006\r\n - s> content-type: application/mercurial-exp-framing-0006\r\n - s> content-length: 56\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> user-agent: Mercurial debugwireproto\r\n - s> \r\n - s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity\x0c\x00\x00\x01\x00\x01\x00\x11\xa1DnameEheads - s> makefile('rb', None) - s> HTTP/1.1 200 OK\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: application/mercurial-exp-framing-0006\r\n - s> Transfer-Encoding: chunked\r\n - s> \r\n - s> 11\r\n - s> \t\x00\x00\x01\x00\x02\x01\x92 - s> Hidentity - s> \r\n - s> 13\r\n - s> \x0b\x00\x00\x01\x00\x02\x041 - s> \xa1FstatusBok - s> \r\n - s> 1e\r\n - s> \x16\x00\x00\x01\x00\x02\x041 - s> \x81T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 - s> \r\n - s> 8\r\n - s> \x00\x00\x00\x01\x00\x02\x002 - s> \r\n - s> 0\r\n - s> \r\n - response: [ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ] - (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob) - $ killdaemons.py HTTP client follows HTTP redirect on handshake to new repo @@ -442,9 +327,9 @@ Verify our HTTP 301 is served properly 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: 484\r\n + s> Content-Length: \d+\\r\\n (re) 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 + s> batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset compression=\$BUNDLE2_COMPRESSIONS\$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (re) Test with the HTTP peer @@ -479,10 +364,10 @@ Test with the HTTP peer 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: 484\r\n + s> Content-Length: \d+\\r\\n (re) 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 + s> batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset compression=\$BUNDLE2_COMPRESSIONS\$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (re) sending heads command s> setsockopt(6, 1, 1) -> None (?) s> GET /redirected?cmd=heads HTTP/1.1\r\n @@ -750,10 +635,10 @@ Now test a variation where we strip the 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: 484\r\n + s> Content-Length: \d+\\r\\n (re) 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 + s> batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset compression=\$BUNDLE2_COMPRESSIONS\$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (re) sending heads command s> setsockopt(6, 1, 1) -> None (?) s> GET /redirected?cmd=heads HTTP/1.1\r\n diff --git a/tests/test-import-bypass.t b/tests/test-import-bypass.t --- a/tests/test-import-bypass.t +++ b/tests/test-import-bypass.t @@ -43,7 +43,7 @@ Test failure without --exact unable to find 'a' for patching (use '--prefix' to apply patch relative to the current directory) abort: patch failed to apply - [255] + [20] $ hg st $ shortlog o 1:4e322f7ce8e3 test 0 0 - foo - changea @@ -234,7 +234,7 @@ commit message is explicitly specified, patching file a Hunk #1 FAILED at 0 abort: patch failed to apply - [255] + [20] $ hg --config patch.eol=auto import -d '0 0' -m 'test patch.eol' --bypass ../test.diff applying ../test.diff $ shortlog diff --git a/tests/test-import-git.t b/tests/test-import-git.t --- a/tests/test-import-git.t +++ b/tests/test-import-git.t @@ -519,7 +519,8 @@ Invalid base85 content > EOF applying patch from stdin abort: could not decode "binary2" binary patch: bad base85 character at position 6 - [255] + (check that whitespace in the patch has not been mangled) + [10] $ hg revert -aq $ hg import -d "1000000 0" -m rename-as-binary - <<"EOF" @@ -534,7 +535,8 @@ Invalid base85 content > EOF applying patch from stdin abort: "binary2" length is 5 bytes, should be 6 - [255] + (check that whitespace in the patch has not been mangled) + [10] $ hg revert -aq $ hg import -d "1000000 0" -m rename-as-binary - <<"EOF" @@ -548,7 +550,8 @@ Invalid base85 content > EOF applying patch from stdin abort: could not extract "binary2" binary data - [255] + (check that whitespace in the patch has not been mangled) + [10] Simulate a copy/paste turning LF into CRLF (issue2870) @@ -748,7 +751,7 @@ Pure copy with existing destination > EOF applying patch from stdin abort: cannot create b: destination already exists - [255] + [20] $ cat b b @@ -768,7 +771,7 @@ Copy and changes with existing destinati cannot create b: destination already exists 1 out of 1 hunks FAILED -- saving rejects to file b.rej abort: patch failed to apply - [255] + [20] $ cat b b @@ -791,7 +794,7 @@ Copy and changes with existing destinati Hunk #1 FAILED at 0 1 out of 1 hunks FAILED -- saving rejects to file linkb.rej abort: patch failed to apply - [255] + [20] $ hg st ? b.rej ? linkb.rej diff --git a/tests/test-import-unknown.t b/tests/test-import-unknown.t --- a/tests/test-import-unknown.t +++ b/tests/test-import-unknown.t @@ -29,7 +29,7 @@ Test adding on top of an unknown file file added already exists 1 out of 1 hunks FAILED -- saving rejects to file added.rej abort: patch failed to apply - [255] + [20] Test modifying an unknown file @@ -41,7 +41,7 @@ Test modifying an unknown file $ hg import --no-commit ../unknown.diff applying ../unknown.diff abort: cannot patch changed: file is not tracked - [255] + [20] Test removing an unknown file @@ -54,7 +54,7 @@ Test removing an unknown file $ hg import --no-commit ../unknown.diff applying ../unknown.diff abort: cannot patch removed: file is not tracked - [255] + [20] Test copying onto an unknown file @@ -64,6 +64,6 @@ Test copying onto an unknown file $ hg import --no-commit ../unknown.diff applying ../unknown.diff abort: cannot create copied: destination already exists - [255] + [20] $ cd .. diff --git a/tests/test-import.t b/tests/test-import.t --- a/tests/test-import.t +++ b/tests/test-import.t @@ -234,7 +234,8 @@ import of malformed plain diff should fa $ hg --cwd b import -mpatch ../broken.patch applying ../broken.patch abort: bad hunk #1 - [255] + (check that whitespace in the patch has not been mangled) + [10] $ rm -r b hg -R repo import @@ -834,7 +835,7 @@ Test fuzziness (ambiguous patch location Hunk #1 FAILED at 0 1 out of 1 hunks FAILED -- saving rejects to file a.rej abort: patch failed to apply - [255] + [20] $ hg import --no-commit -v fuzzy-tip.patch applying fuzzy-tip.patch patching file a @@ -853,7 +854,7 @@ Test --exact failure Hunk #1 FAILED at 0 1 out of 1 hunks FAILED -- saving rejects to file a.rej abort: patch failed to apply - [255] + [20] $ hg up -qC $ hg import --config patch.fuzz=2 --exact fuzzy-reparent.patch applying fuzzy-reparent.patch @@ -1084,7 +1085,7 @@ test paths outside repo root > EOF applying patch from stdin abort: path contains illegal component: ../outside/foo - [255] + [10] $ cd .. @@ -2054,7 +2055,7 @@ repository when file not found for patch (use '--prefix' to apply patch relative to the current directory) 1 out of 1 hunks FAILED -- saving rejects to file file1.rej abort: patch failed to apply - [255] + [20] test import crash (issue5375) $ cd .. @@ -2064,7 +2065,7 @@ test import crash (issue5375) applying patch from stdin a not tracked! abort: source file 'a' does not exist - [255] + [20] test immature end of hunk @@ -2076,7 +2077,8 @@ test immature end of hunk > EOF applying patch from stdin abort: bad hunk #1: incomplete hunk - [255] + (check that whitespace in the patch has not been mangled) + [10] $ hg import - <<'EOF' > diff --git a/foo b/foo @@ -2087,4 +2089,5 @@ test immature end of hunk > EOF applying patch from stdin abort: bad hunk #1: incomplete hunk - [255] + (check that whitespace in the patch has not been mangled) + [10] diff --git a/tests/test-infinitepush-ci.t b/tests/test-infinitepush-ci.t --- a/tests/test-infinitepush-ci.t +++ b/tests/test-infinitepush-ci.t @@ -204,7 +204,7 @@ client side wrapping $ hg pull -r b4e4bce660512ad3e71189e14588a70ac8e31fef pulling from $TESTTMP/repo abort: unknown revision 'b4e4bce660512ad3e71189e14588a70ac8e31fef' - [255] + [10] $ hg glog o 1:6cb0989601f1 added a | public diff --git a/tests/test-inherit-mode.t b/tests/test-inherit-mode.t --- a/tests/test-inherit-mode.t +++ b/tests/test-inherit-mode.t @@ -54,6 +54,7 @@ store is setgid 00770 ./.hg/cache/ 00600 ./.hg/requires 00770 ./.hg/store/ + 00600 ./.hg/store/requires 00770 ./.hg/wcache/ $ mkdir dir @@ -92,6 +93,7 @@ new directories are setgid 00660 ./.hg/store/data/foo/index (reposimplestore !) 00660 ./.hg/store/fncache (repofncache !) 00660 ./.hg/store/phaseroots + 00600 ./.hg/store/requires 00660 ./.hg/store/undo 00660 ./.hg/store/undo.backupfiles 00660 ./.hg/store/undo.phaseroots @@ -121,6 +123,7 @@ group can write everything 00770 ../push/.hg/cache/ 00660 ../push/.hg/requires 00770 ../push/.hg/store/ + 00660 ../push/.hg/store/requires 00770 ../push/.hg/wcache/ $ umask 077 @@ -152,6 +155,7 @@ group can still write everything 00660 ../push/.hg/store/data/foo/b80de5d138758541c5f05265ad144ab9fa86d1db (reposimplestore !) 00660 ../push/.hg/store/data/foo/index (reposimplestore !) 00660 ../push/.hg/store/fncache (repofncache !) + 00660 ../push/.hg/store/requires 00660 ../push/.hg/store/undo 00660 ../push/.hg/store/undo.backupfiles 00660 ../push/.hg/store/undo.phaseroots diff --git a/tests/test-init.t b/tests/test-init.t --- a/tests/test-init.t +++ b/tests/test-init.t @@ -9,7 +9,7 @@ This test tries to exercise the ssh func > if [ -f "$name"/.hg/00changelog.i ]; then > echo 00changelog.i created > fi - > cat "$name"/.hg/requires + > hg debugrequires -R "$name" > } creating 'local' @@ -25,6 +25,7 @@ creating 'local' persistent-nodemap (rust !) revlog-compression-zstd (zstd !) revlogv1 + share-safe sparserevlog store testonly-simplestore (reposimplestore !) @@ -80,6 +81,7 @@ creating repo with format.usefncache=fal persistent-nodemap (rust !) revlog-compression-zstd (zstd !) revlogv1 + share-safe sparserevlog store testonly-simplestore (reposimplestore !) @@ -96,6 +98,7 @@ creating repo with format.dotencode=fals persistent-nodemap (rust !) revlog-compression-zstd (zstd !) revlogv1 + share-safe sparserevlog store testonly-simplestore (reposimplestore !) @@ -112,6 +115,7 @@ creating repo with format.dotencode=fals persistent-nodemap (rust !) revlog-compression-zstd (zstd !) revlogv1 + share-safe store testonly-simplestore (reposimplestore !) @@ -232,6 +236,7 @@ creating 'local/sub/repo' persistent-nodemap (rust !) revlog-compression-zstd (zstd !) revlogv1 + share-safe sparserevlog store testonly-simplestore (reposimplestore !) @@ -255,6 +260,7 @@ init should (for consistency with clone) persistent-nodemap (rust !) revlog-compression-zstd (zstd !) revlogv1 + share-safe sparserevlog store testonly-simplestore (reposimplestore !) @@ -274,6 +280,7 @@ verify that clone also expand urls persistent-nodemap (rust !) revlog-compression-zstd (zstd !) revlogv1 + share-safe sparserevlog store testonly-simplestore (reposimplestore !) diff --git a/tests/test-issue6528.t b/tests/test-issue6528.t --- a/tests/test-issue6528.t +++ b/tests/test-issue6528.t @@ -187,6 +187,11 @@ only since some versions of tar don't ha #endif Check that the issue is present +(It is currently not present with rhg but will be when optimizations are added +to resolve ambiguous files at the end of status without reading their content +if the size differs, and reading the expected size without resolving filelog +deltas where possible.) + $ hg st M D.txt M b.txt diff --git a/tests/test-issue672.t b/tests/test-issue672.t --- a/tests/test-issue672.t +++ b/tests/test-issue672.t @@ -65,7 +65,7 @@ https://bz.mercurial-scm.org/672 ancestor: c64f439569a9, local: f4a9cff3cd0b+, remote: 746e9549ea96 starting 4 threads for background file closing (?) preserving 1a for resolve of 1a - 1a: local copied/moved from 1 -> m (premerge) + 1a: local copied/moved from 1 -> m picked tool ':merge' for 1a (binary False symlink False changedelete False) merging 1a and 1 to 1a my 1a@f4a9cff3cd0b+ other 1@746e9549ea96 ancestor 1@c64f439569a9 @@ -89,7 +89,7 @@ https://bz.mercurial-scm.org/672 starting 4 threads for background file closing (?) preserving 1 for resolve of 1a removing 1 - 1a: remote moved from 1 -> m (premerge) + 1a: remote moved from 1 -> m picked tool ':merge' for 1a (binary False symlink False changedelete False) merging 1 and 1a to 1a my 1a@746e9549ea96+ other 1a@f4a9cff3cd0b ancestor 1@c64f439569a9 diff --git a/tests/test-largefiles-misc.t b/tests/test-largefiles-misc.t --- a/tests/test-largefiles-misc.t +++ b/tests/test-largefiles-misc.t @@ -41,7 +41,7 @@ common commands affecting largefile. > EOF $ hg config extensions - \*\*\* failed to import extension largefiles from missing.py: [Errno *] $ENOENT$: 'missing.py' (glob) + \*\*\* failed to import extension "largefiles" from missing.py: [Errno *] $ENOENT$: 'missing.py' (glob) abort: repository requires features unknown to this Mercurial: largefiles (see https://mercurial-scm.org/wiki/MissingRequirement for more information) [255] @@ -267,7 +267,7 @@ sharing a largefile repo automatically e getting changed largefiles 1 largefiles updated, 0 removed 3 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ grep largefiles ../shared_lfrepo/.hg/requires + $ hg debugrequires -R ../shared_lfrepo | grep largefiles largefiles verify that large files in subrepos handled properly @@ -962,7 +962,7 @@ Merge conflicts: what do you want to do? l getting changed largefiles 1 largefiles updated, 0 removed - 0 files updated, 4 files merged, 0 files removed, 0 files unresolved + 1 files updated, 3 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) $ cat f-different 1 diff --git a/tests/test-largefiles-update.t b/tests/test-largefiles-update.t --- a/tests/test-largefiles-update.t +++ b/tests/test-largefiles-update.t @@ -68,20 +68,39 @@ Wait to make sure we get a tick so the m A linear merge will update standins before performing the actual merge. It will do a lfdirstate status walk and find 'unset'/'unsure' files, hash them, and update the corresponding standins. + Verify that it actually marks the clean files as clean in lfdirstate so we don't have to hash them again next time we update. +# note: +# We do this less agressively now, to avoid race condition, however the +# cache +# is properly set after the next status +# +# The "changed" output is marked as missing-correct-output/known-bad-output +# for clarify + $ hg up 0 files updated, 0 files merged, 0 files removed, 0 files unresolved updated to "f74e50bd9e55: #2" 1 other heads for branch "default" $ hg debugdirstate --large --nodate + n 644 7 set large1 (missing-correct-output !) + n 644 13 set large2 (missing-correct-output !) + n 0 -1 unset large1 (known-bad-output !) + n 0 -1 unset large2 (known-bad-output !) + $ sleep 1 # so that mtime are not ambiguous + $ hg status + $ hg debugdirstate --large --nodate n 644 7 set large1 n 644 13 set large2 Test that lfdirstate keeps track of last modification of largefiles and prevents unnecessary hashing of content - also after linear/noop update +(XXX Since there is a possible race during update, we only do this after the next +status call, this is slower, but more correct) + $ sleep 1 $ hg st $ hg debugdirstate --large --nodate @@ -92,6 +111,13 @@ prevents unnecessary hashing of content updated to "f74e50bd9e55: #2" 1 other heads for branch "default" $ hg debugdirstate --large --nodate + n 644 7 set large1 (missing-correct-output !) + n 644 13 set large2 (missing-correct-output !) + n 0 -1 unset large1 (known-bad-output !) + n 0 -1 unset large2 (known-bad-output !) + $ sleep 1 # so that mtime are not ambiguous + $ hg status + $ hg debugdirstate --large --nodate n 644 7 set large1 n 644 13 set large2 diff --git a/tests/test-largefiles-wireproto.t b/tests/test-largefiles-wireproto.t --- a/tests/test-largefiles-wireproto.t +++ b/tests/test-largefiles-wireproto.t @@ -1,13 +1,3 @@ -#testcases sshv1 sshv2 - -#if sshv2 - $ cat >> $HGRCPATH << EOF - > [experimental] - > sshpeer.advertise-v2 = true - > sshserver.support-v2 = true - > EOF -#endif - This file contains testcases that tend to be related to the wire protocol part of largefiles. diff --git a/tests/test-lfconvert.t b/tests/test-lfconvert.t --- a/tests/test-lfconvert.t +++ b/tests/test-lfconvert.t @@ -94,7 +94,7 @@ Test link+rename largefile codepath 1276481102f218c981e0324180bafd9f sub/maybelarge.dat "lfconvert" adds 'largefiles' to .hg/requires. - $ cat .hg/requires + $ hg debugrequires dotencode dirstate-v2 (dirstate-v2 !) fncache @@ -103,6 +103,7 @@ Test link+rename largefile codepath persistent-nodemap (rust !) revlog-compression-zstd (zstd !) revlogv1 + share-safe sparserevlog store testonly-simplestore (reposimplestore !) diff --git a/tests/test-lfs-largefiles.t b/tests/test-lfs-largefiles.t --- a/tests/test-lfs-largefiles.t +++ b/tests/test-lfs-largefiles.t @@ -288,17 +288,8 @@ parameters are available, but not --auth The requirement is added to the destination repo. - $ cat .hg/requires - dotencode - dirstate-v2 (dirstate-v2 !) - fncache - generaldelta + $ hg debugrequires | grep lfs lfs - persistent-nodemap (rust !) - revlog-compression-zstd (zstd !) - revlogv1 - sparserevlog - store $ hg log -r 'all()' -G -T '{rev} {join(lfs_files, ", ")} ({desc})\n' o 8 large_by_size.bin (remove large_by_size.bin) @@ -345,7 +336,7 @@ This `head -n 20` looks dumb (since we e breaks you can get 1048576 lines of +y in the output, which takes a looooooong time to print. $ hg diff -r 2:3 | head -n 20 - $ hg diff -r 2:6 + $ hg diff -r 2:6 | head -n 20 diff -r e989d0fa3764 -r 752e3a0d8488 large.bin --- a/large.bin Thu Jan 01 00:00:00 1970 +0000 +++ b/large.bin Thu Jan 01 00:00:00 1970 +0000 diff --git a/tests/test-lfs-serve.t b/tests/test-lfs-serve.t --- a/tests/test-lfs-serve.t +++ b/tests/test-lfs-serve.t @@ -34,6 +34,7 @@ make command server magic visible $ hg init server $ SERVER_REQUIRES="$TESTTMP/server/.hg/requires" + $ SERVER_PATH="$TESTTMP/server/" $ cat > $TESTTMP/debugprocessors.py < from mercurial import ( @@ -85,7 +86,9 @@ missing processor for flag '0x2000'!" if $ cat hg.pid >> $DAEMON_PIDS $ hg clone -q http://localhost:$HGPORT client - $ grep 'lfs' client/.hg/requires $SERVER_REQUIRES + $ hg debugrequires -R client | grep 'lfs' + [1] + $ hg debugrequires -R $SERVER_PATH | grep 'lfs' [1] This trivial repo will force commandserver to load the extension, but not call @@ -129,24 +132,27 @@ non-lfs content, and the extension enabl +non-lfs *** runcommand debugupgraderepo -q --run - $ grep 'lfs' .hg/requires $SERVER_REQUIRES + $ hg debugrequires -R $SERVER_PATH | grep 'lfs' [1] #if lfsremote-on $ hg push -q - $ grep 'lfs' .hg/requires $SERVER_REQUIRES + $ hg debugrequires -R $SERVER_PATH | grep 'lfs' [1] $ hg clone -q http://localhost:$HGPORT $TESTTMP/client1_clone - $ grep 'lfs' $TESTTMP/client1_clone/.hg/requires $SERVER_REQUIRES + $ hg debugrequires -R $TESTTMP/client1_clone/ | grep 'lfs' + [1] + $ hg debugrequires -R $SERVER_PATH | grep 'lfs' [1] $ hg init $TESTTMP/client1_pull $ hg -R $TESTTMP/client1_pull pull -q http://localhost:$HGPORT - $ grep 'lfs' $TESTTMP/client1_pull/.hg/requires $SERVER_REQUIRES + $ hg debugrequires -R $TESTTMP/client1_pull/ | grep 'lfs' [1] - + $ hg debugrequires -R $SERVER_PATH | grep 'lfs' + [1] $ hg identify http://localhost:$HGPORT d437e1d24fbd @@ -167,16 +173,22 @@ Since no lfs content has been added yet, extension is not enabled remotely. $ hg push -q - $ grep 'lfs' .hg/requires $SERVER_REQUIRES + $ hg debugrequires | grep 'lfs' + [1] + $ hg debugrequires -R $SERVER_PATH | grep 'lfs' [1] $ hg clone -q http://localhost:$HGPORT $TESTTMP/client2_clone - $ grep 'lfs' $TESTTMP/client2_clone/.hg/requires $SERVER_REQUIRES + $ hg debugrequires -R $TESTTMP/client2_clone/ | grep 'lfs' + [1] + $ hg debugrequires -R $SERVER_PATH | grep 'lfs' [1] $ hg init $TESTTMP/client2_pull $ hg -R $TESTTMP/client2_pull pull -q http://localhost:$HGPORT - $ grep 'lfs' $TESTTMP/client2_pull/.hg/requires $SERVER_REQUIRES + $ hg debugrequires -R $TESTTMP/client2_pull/ | grep 'lfs' + [1] + $ hg debugrequires -R $SERVER_PATH | grep 'lfs' [1] $ hg identify http://localhost:$HGPORT @@ -189,8 +201,10 @@ should have an 'lfs' requirement after i $ echo 'this is a big lfs file' > lfs.bin $ hg ci -Aqm 'lfs' - $ grep 'lfs' .hg/requires $SERVER_REQUIRES - .hg/requires:lfs + $ hg debugrequires | grep 'lfs' + lfs + $ hg debugrequires -R $SERVER_PATH | grep 'lfs' + [1] #if lfsremote-off $ hg push -q @@ -200,20 +214,24 @@ should have an 'lfs' requirement after i #else $ hg push -q #endif - $ grep 'lfs' .hg/requires $SERVER_REQUIRES - .hg/requires:lfs - $TESTTMP/server/.hg/requires:lfs (lfsremote-on !) + $ hg debugrequires | grep 'lfs' + lfs + $ hg debugrequires -R $SERVER_PATH | grep 'lfs' || true + lfs (lfsremote-on !) $ hg clone -q http://localhost:$HGPORT $TESTTMP/client3_clone - $ grep 'lfs' $TESTTMP/client3_clone/.hg/requires $SERVER_REQUIRES || true - $TESTTMP/client3_clone/.hg/requires:lfs (lfsremote-on !) - $TESTTMP/server/.hg/requires:lfs (lfsremote-on !) + + $ hg debugrequires -R $TESTTMP/client3_clone/ | grep 'lfs' || true + lfs (lfsremote-on !) + $ hg debugrequires -R $SERVER_PATH | grep 'lfs' || true + lfs (lfsremote-on !) $ hg init $TESTTMP/client3_pull $ hg -R $TESTTMP/client3_pull pull -q http://localhost:$HGPORT - $ grep 'lfs' $TESTTMP/client3_pull/.hg/requires $SERVER_REQUIRES || true - $TESTTMP/client3_pull/.hg/requires:lfs (lfsremote-on !) - $TESTTMP/server/.hg/requires:lfs (lfsremote-on !) + $ hg debugrequires -R $TESTTMP/client3_pull/ | grep 'lfs' || true + lfs (lfsremote-on !) + $ hg debugrequires -R $SERVER_PATH | grep 'lfs' || true + lfs (lfsremote-on !) Test that the commit/changegroup requirement check hook can be run multiple times. @@ -267,23 +285,24 @@ lfs content, and the extension enabled. > EOF $ echo 'non-lfs' > nonlfs2.txt $ hg ci -Aqm 'non-lfs' - $ grep 'lfs' .hg/requires $SERVER_REQUIRES - $TESTTMP/server/.hg/requires:lfs + $ hg debugrequires -R $SERVER_PATH --config extensions.lfs= | grep 'lfs' + lfs $ hg push -q --force warning: repository is unrelated - $ grep 'lfs' .hg/requires $SERVER_REQUIRES - $TESTTMP/server/.hg/requires:lfs + $ hg debugrequires -R $SERVER_PATH --config extensions.lfs= | grep 'lfs' + lfs $ hg clone http://localhost:$HGPORT $TESTTMP/client4_clone (remote is using large file support (lfs), but it is explicitly disabled in the local configuration) abort: repository requires features unknown to this Mercurial: lfs (see https://mercurial-scm.org/wiki/MissingRequirement for more information) [255] - $ grep 'lfs' $TESTTMP/client4_clone/.hg/requires $SERVER_REQUIRES - grep: $TESTTMP/client4_clone/.hg/requires: $ENOENT$ - $TESTTMP/server/.hg/requires:lfs - [2] + $ hg debugrequires -R $TESTTMP/client4_clone/ | grep 'lfs' + abort: repository $TESTTMP/client4_clone/ not found + [1] + $ hg debugrequires -R $SERVER_PATH --config extensions.lfs= | grep 'lfs' + lfs TODO: fail more gracefully. @@ -294,8 +313,10 @@ TODO: fail more gracefully. remote: abort: no common changegroup version abort: pull failed on remote [100] - $ grep 'lfs' $TESTTMP/client4_pull/.hg/requires $SERVER_REQUIRES - $TESTTMP/server/.hg/requires:lfs + $ hg debugrequires -R $TESTTMP/client4_pull/ | grep 'lfs' + [1] + $ hg debugrequires -R $SERVER_PATH --config extensions.lfs= | grep 'lfs' + lfs $ hg identify http://localhost:$HGPORT 03b080fa9d93 @@ -312,19 +333,21 @@ lfs content, and the extension enabled. $ hg ci -Aqm 'non-lfs file with lfs client' $ hg push -q - $ grep 'lfs' .hg/requires $SERVER_REQUIRES - $TESTTMP/server/.hg/requires:lfs + $ hg debugrequires -R $SERVER_PATH | grep 'lfs' + lfs $ hg clone -q http://localhost:$HGPORT $TESTTMP/client5_clone - $ grep 'lfs' $TESTTMP/client5_clone/.hg/requires $SERVER_REQUIRES - $TESTTMP/client5_clone/.hg/requires:lfs - $TESTTMP/server/.hg/requires:lfs + $ hg debugrequires -R $TESTTMP/client5_clone/ | grep 'lfs' + lfs + $ hg debugrequires -R $SERVER_PATH | grep 'lfs' + lfs $ hg init $TESTTMP/client5_pull $ hg -R $TESTTMP/client5_pull pull -q http://localhost:$HGPORT - $ grep 'lfs' $TESTTMP/client5_pull/.hg/requires $SERVER_REQUIRES - $TESTTMP/client5_pull/.hg/requires:lfs - $TESTTMP/server/.hg/requires:lfs + $ hg debugrequires -R $TESTTMP/client5_pull/ | grep 'lfs' + lfs + $ hg debugrequires -R $SERVER_PATH | grep 'lfs' + lfs $ hg identify http://localhost:$HGPORT c729025cc5e3 @@ -463,14 +486,16 @@ lfs content, and the extension enabled. 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 + $ hg debugrequires | grep lfs + lfs + $ hg debugrequires -R $SERVER_PATH | grep lfs + lfs $ hg clone -q http://localhost:$HGPORT $TESTTMP/client6_clone - $ grep 'lfs' $TESTTMP/client6_clone/.hg/requires $SERVER_REQUIRES - $TESTTMP/client6_clone/.hg/requires:lfs - $TESTTMP/server/.hg/requires:lfs + $ hg debugrequires -R $TESTTMP/client6_clone/ | grep lfs + lfs + $ hg debugrequires -R $SERVER_PATH | grep lfs + lfs $ hg init $TESTTMP/client6_pull $ hg -R $TESTTMP/client6_pull pull -u -v http://localhost:$HGPORT @@ -495,9 +520,10 @@ lfs content, and the extension enabled. updated to "d3b84d50eacb: lfs file with lfs client" 1 other heads for branch "default" (sent 3 HTTP requests and * bytes; received * bytes in responses) (glob) - $ grep 'lfs' $TESTTMP/client6_pull/.hg/requires $SERVER_REQUIRES - $TESTTMP/client6_pull/.hg/requires:lfs - $TESTTMP/server/.hg/requires:lfs + $ hg debugrequires -R $TESTTMP/client6_pull/ | grep lfs + lfs + $ hg debugrequires -R $SERVER_PATH | grep lfs + lfs $ hg identify http://localhost:$HGPORT d3b84d50eacb diff --git a/tests/test-lfs.t b/tests/test-lfs.t --- a/tests/test-lfs.t +++ b/tests/test-lfs.t @@ -40,7 +40,7 @@ > EOF $ hg config extensions - \*\*\* failed to import extension lfs from missing.py: [Errno *] $ENOENT$: 'missing.py' (glob) + \*\*\* failed to import extension "lfs" from missing.py: [Errno *] $ENOENT$: 'missing.py' (glob) abort: repository requires features unknown to this Mercurial: lfs (see https://mercurial-scm.org/wiki/MissingRequirement for more information) [255] @@ -75,10 +75,10 @@ # Commit large file $ echo $LONG > largefile - $ grep lfs .hg/requires + $ hg debugrequires | grep lfs [1] $ hg commit --traceback -Aqm "add large file" - $ grep lfs .hg/requires + $ hg debugrequires | grep lfs lfs # Ensure metadata is stored @@ -114,7 +114,7 @@ Push to a local non-lfs repo with the extension enabled will add the lfs requirement - $ grep lfs $TESTTMP/server/.hg/requires + $ hg debugrequires -R $TESTTMP/server/ | grep lfs [1] $ hg push -v | egrep -v '^(uncompressed| )' pushing to $TESTTMP/server @@ -126,7 +126,7 @@ lfs requirement adding file changes calling hook pretxnchangegroup.lfs: hgext.lfs.checkrequireslfs added 2 changesets with 3 changes to 3 files - $ grep lfs $TESTTMP/server/.hg/requires + $ hg debugrequires -R $TESTTMP/server/ | grep lfs lfs # Unknown URL scheme @@ -150,8 +150,9 @@ lfs requirement Pulling a local lfs repo into a local non-lfs repo with the extension enabled adds the lfs requirement - $ grep lfs .hg/requires $TESTTMP/server/.hg/requires - $TESTTMP/server/.hg/requires:lfs + $ hg debugrequires | grep lfs || true + $ hg debugrequires -R $TESTTMP/server/ | grep lfs + lfs $ hg pull default pulling from $TESTTMP/server requesting all changes @@ -161,9 +162,10 @@ enabled adds the lfs requirement added 2 changesets with 3 changes to 3 files new changesets 0ead593177f7:b88141481348 (run 'hg update' to get a working copy) - $ grep lfs .hg/requires $TESTTMP/server/.hg/requires - .hg/requires:lfs - $TESTTMP/server/.hg/requires:lfs + $ hg debugrequires | grep lfs + lfs + $ hg debugrequires -R $TESTTMP/server/ | grep lfs + lfs # Check the blobstore is not yet populated $ [ -d .hg/store/lfs/objects ] @@ -314,7 +316,7 @@ enabled adds the lfs requirement $ hg --config extensions.share= share repo7 sharedrepo updating working directory 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ grep lfs sharedrepo/.hg/requires + $ hg debugrequires -R sharedrepo/ | grep lfs lfs # Test rename and status @@ -1002,7 +1004,7 @@ there's no 'lfs' destination repo requir 2 a 1 b 0 meta - $ grep 'lfs' convert_normal/.hg/requires + $ hg debugrequires -R convert_normal | grep 'lfs' [1] $ hg --cwd convert_normal cat a1 -r 0 -T '{rawdata}' THIS-IS-LFS-BECAUSE-10-BYTES @@ -1044,7 +1046,7 @@ there's no 'lfs' destination repo requir 1: a2: 5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024 2: a2: 876dadc86a8542f9798048f2c47f51dbf8e4359aed883e8ec80c5db825f0d943 - $ grep 'lfs' convert_lfs/.hg/requires + $ hg debugrequires -R convert_lfs | grep 'lfs' lfs The hashes in all stages of the conversion are unchanged. @@ -1075,7 +1077,7 @@ This convert is trickier, because it con 2 large to small, small to large 1 random modifications 0 switch large and small again - $ grep 'lfs' convert_normal2/.hg/requires + $ hg debugrequires -R convert_normal2 | grep 'lfs' [1] $ hg --cwd convert_normal2 debugdata large 0 LONGER-THAN-TEN-BYTES-WILL-TRIGGER-LFS @@ -1091,7 +1093,7 @@ This convert is trickier, because it con 2 large to small, small to large 1 random modifications 0 switch large and small again - $ grep 'lfs' convert_lfs2/.hg/requires + $ hg debugrequires -R convert_lfs2 | grep 'lfs' lfs $ hg --cwd convert_lfs2 debugdata large 0 version https://git-lfs.github.com/spec/v1 @@ -1202,10 +1204,10 @@ Unbundling adds a requirement to a non-l $ hg bundle -R convert_lfs2 -qr tip --base null lfs.hg $ hg init unbundle $ hg pull -R unbundle -q nolfs.hg - $ grep lfs unbundle/.hg/requires + $ hg debugrequires -R unbundle | grep lfs [1] $ hg pull -R unbundle -q lfs.hg - $ grep lfs unbundle/.hg/requires + $ hg debugrequires -R unbundle | grep lfs lfs $ hg init no_lfs @@ -1224,7 +1226,7 @@ with lfs disabled, fails. pushing to no_lfs abort: required features are not supported in the destination: lfs [255] - $ grep lfs no_lfs/.hg/requires + $ hg debugrequires -R no_lfs/ | grep lfs [1] Pulling from a local lfs repo to a local repo without an lfs requirement and @@ -1234,5 +1236,5 @@ with lfs disabled, fails. pulling from convert_lfs2 abort: required features are not supported in the destination: lfs [255] - $ grep lfs no_lfs2/.hg/requires + $ hg debugrequires -R no_lfs2/ | grep lfs [1] diff --git a/tests/test-log-bookmark.t b/tests/test-log-bookmark.t --- a/tests/test-log-bookmark.t +++ b/tests/test-log-bookmark.t @@ -189,10 +189,10 @@ Unknown bookmark: $ hg log -B unknown abort: bookmark 'unknown' does not exist - [255] + [10] Shouldn't accept string-matcher syntax: $ hg log -B 're:.*' abort: bookmark 're:.*' does not exist - [255] + [10] diff --git a/tests/test-log-linerange.t b/tests/test-log-linerange.t --- a/tests/test-log-linerange.t +++ b/tests/test-log-linerange.t @@ -1150,4 +1150,4 @@ We get an error for removed files. $ hg ci -m 'remove baz' --quiet $ hg log -f -L dir/baz,5:7 -p abort: cannot follow file not in parent revision: "dir/baz" - [255] + [20] diff --git a/tests/test-log.t b/tests/test-log.t --- a/tests/test-log.t +++ b/tests/test-log.t @@ -122,13 +122,13 @@ log -f empty path (or repo root) shouldn $ hg log -qfl1 '' inexistent abort: cannot follow file not in parent revision: "inexistent" - [255] + [20] $ hg log -qfl1 . inexistent abort: cannot follow file not in parent revision: "inexistent" - [255] + [20] $ hg log -qfl1 "`pwd`" inexistent abort: cannot follow file not in parent revision: "inexistent" - [255] + [20] $ hg log -qfl1 '' e 4:7e4639b4691b @@ -145,7 +145,7 @@ log -f empty path (or repo root) shouldn $ hg log -f dir abort: cannot follow file not in parent revision: "dir" - [255] + [20] -f, directory @@ -552,7 +552,7 @@ follow files from the specified revision $ hg log -T '{rev}\n' -fr4 e x abort: cannot follow file not in any of the specified revisions: "x" - [255] + [20] follow files from the specified revisions with directory patterns (BROKEN: should follow copies from dir/b@2) @@ -1417,7 +1417,7 @@ are specified (issue5100): $ hg log -b 're:.*' abort: unknown revision 're:.*' - [255] + [10] $ hg log -k 're:.*' $ hg log -u 're:.*' @@ -1544,7 +1544,7 @@ log -b dummy $ hg log -b dummy abort: unknown revision 'dummy' - [255] + [10] log -b . @@ -2422,7 +2422,7 @@ follow files from wdir $ hg log -T '== {rev} ==\n' -fr'wdir()' --git --stat notfound abort: cannot follow file not in any of the specified revisions: "notfound" - [255] + [20] follow files from wdir and non-wdir revision: @@ -2435,15 +2435,15 @@ follow added/removed files from wdir par $ hg log -T '{rev}\n' -f d1/f2 abort: cannot follow nonexistent file: "d1/f2" - [255] + [20] $ hg log -T '{rev}\n' -f f1-copy abort: cannot follow nonexistent file: "f1-copy" - [255] + [20] $ hg log -T '{rev}\n' -f .d6/f1 abort: cannot follow file not in parent revision: ".d6/f1" - [255] + [20] $ hg revert -aqC diff --git a/tests/test-merge-changedelete.t b/tests/test-merge-changedelete.t --- a/tests/test-merge-changedelete.t +++ b/tests/test-merge-changedelete.t @@ -680,12 +680,12 @@ Choose to merge all files changed --- file3 --- 3 - <<<<<<< working copy: 13910f48cf7b - test: changed file1, removed file2, chan... + <<<<<<< working copy: 13910f48cf7b - test: changed file1, removed file2, c... changed2 - ||||||| base + ||||||| common ancestor: ab57bf49aa27 - test: added files ======= changed1 - >>>>>>> merge rev: 10f9a0a634e8 - test: removed file1, changed file2, chan... + >>>>>>> merge rev: 10f9a0a634e8 - test: removed file1, changed file2, c... Exercise transitions between local, other, fail and prompt, and make sure the dirstate stays consistent. (Compare with each other and to the above diff --git a/tests/test-merge-commit.t b/tests/test-merge-commit.t --- a/tests/test-merge-commit.t +++ b/tests/test-merge-commit.t @@ -72,7 +72,7 @@ This should use bar@rev2 as the ancestor ancestor: 0f2ff26688b9, local: 2263c1be0967+, remote: 0555950ead28 starting 4 threads for background file closing (?) preserving bar for resolve of bar - bar: versions differ -> m (premerge) + bar: versions differ -> m picked tool ':merge' for bar (binary False symlink False changedelete False) merging bar my bar@2263c1be0967+ other bar@0555950ead28 ancestor bar@0f2ff26688b9 @@ -159,7 +159,7 @@ This should use bar@rev2 as the ancestor ancestor: 0f2ff26688b9, local: 2263c1be0967+, remote: 3ffa6b9e35f0 starting 4 threads for background file closing (?) preserving bar for resolve of bar - bar: versions differ -> m (premerge) + bar: versions differ -> m picked tool ':merge' for bar (binary False symlink False changedelete False) merging bar my bar@2263c1be0967+ other bar@3ffa6b9e35f0 ancestor bar@0f2ff26688b9 diff --git a/tests/test-merge-criss-cross.t b/tests/test-merge-criss-cross.t --- a/tests/test-merge-criss-cross.t +++ b/tests/test-merge-criss-cross.t @@ -93,13 +93,10 @@ Criss cross merging f1: remote is newer -> g getting f1 preserving f2 for resolve of f2 - f2: versions differ -> m (premerge) + f2: versions differ -> m picked tool ':dump' for f2 (binary False symlink False changedelete False) merging f2 my f2@3b08d01b0ab5+ other f2@adfe50279922 ancestor f2@0f6b37dbe527 - f2: versions differ -> m (merge) - picked tool ':dump' for f2 (binary False symlink False changedelete False) - my f2@3b08d01b0ab5+ other f2@adfe50279922 ancestor f2@0f6b37dbe527 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] diff --git a/tests/test-merge-exec.t b/tests/test-merge-exec.t --- a/tests/test-merge-exec.t +++ b/tests/test-merge-exec.t @@ -4,7 +4,6 @@ Testing merge involving change to the ex #require execbit - Initial setup ============== diff --git a/tests/test-merge-force.t b/tests/test-merge-force.t --- a/tests/test-merge-force.t +++ b/tests/test-merge-force.t @@ -218,27 +218,27 @@ Merge with remote You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved. What do you want to do? u merging content1_content2_content1_content4-tracked + warning: conflicts while merging content1_content2_content1_content4-tracked! (edit, then use 'hg resolve --mark') merging content1_content2_content2_content1-tracked merging content1_content2_content2_content4-tracked + warning: conflicts while merging content1_content2_content2_content4-tracked! (edit, then use 'hg resolve --mark') merging content1_content2_content3_content1-tracked merging content1_content2_content3_content3-tracked + warning: conflicts while merging content1_content2_content3_content3-tracked! (edit, then use 'hg resolve --mark') merging content1_content2_content3_content4-tracked + warning: conflicts while merging content1_content2_content3_content4-tracked! (edit, then use 'hg resolve --mark') merging content1_content2_missing_content1-tracked merging content1_content2_missing_content4-tracked + warning: conflicts while merging content1_content2_missing_content4-tracked! (edit, then use 'hg resolve --mark') merging missing_content2_content2_content4-tracked + warning: conflicts while merging missing_content2_content2_content4-tracked! (edit, then use 'hg resolve --mark') merging missing_content2_content3_content3-tracked + warning: conflicts while merging missing_content2_content3_content3-tracked! (edit, then use 'hg resolve --mark') merging missing_content2_content3_content4-tracked + warning: conflicts while merging missing_content2_content3_content4-tracked! (edit, then use 'hg resolve --mark') merging missing_content2_missing_content4-tracked + warning: conflicts while merging missing_content2_missing_content4-tracked! (edit, then use 'hg resolve --mark') merging missing_content2_missing_content4-untracked - warning: conflicts while merging content1_content2_content1_content4-tracked! (edit, then use 'hg resolve --mark') - warning: conflicts while merging content1_content2_content2_content4-tracked! (edit, then use 'hg resolve --mark') - warning: conflicts while merging content1_content2_content3_content3-tracked! (edit, then use 'hg resolve --mark') - warning: conflicts while merging content1_content2_content3_content4-tracked! (edit, then use 'hg resolve --mark') - warning: conflicts while merging content1_content2_missing_content4-tracked! (edit, then use 'hg resolve --mark') - warning: conflicts while merging missing_content2_content2_content4-tracked! (edit, then use 'hg resolve --mark') - warning: conflicts while merging missing_content2_content3_content3-tracked! (edit, then use 'hg resolve --mark') - warning: conflicts while merging missing_content2_content3_content4-tracked! (edit, then use 'hg resolve --mark') - warning: conflicts while merging missing_content2_missing_content4-tracked! (edit, then use 'hg resolve --mark') warning: conflicts while merging missing_content2_missing_content4-untracked! (edit, then use 'hg resolve --mark') 18 files updated, 3 files merged, 8 files removed, 35 files unresolved use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon @@ -398,13 +398,13 @@ the remote side did not touch the file content2 M content1_content2_content1_content4-tracked - <<<<<<< working copy: 0447570f1af6 - test: local + <<<<<<< working copy: 0447570f1af6 - test: local content4 - ||||||| base + ||||||| common ancestor: 8ef80617fa20 - test: base content1 ======= content2 - >>>>>>> merge rev: 85100b8c675b - test: remote + >>>>>>> merge rev: 85100b8c675b - test: remote M content1_content2_content1_content4-untracked content2 @@ -428,13 +428,13 @@ the remote side did not touch the file content2 M content1_content2_content2_content4-tracked - <<<<<<< working copy: 0447570f1af6 - test: local + <<<<<<< working copy: 0447570f1af6 - test: local content4 - ||||||| base + ||||||| common ancestor: 8ef80617fa20 - test: base content1 ======= content2 - >>>>>>> merge rev: 85100b8c675b - test: remote + >>>>>>> merge rev: 85100b8c675b - test: remote M content1_content2_content2_content4-untracked content2 @@ -458,25 +458,25 @@ the remote side did not touch the file content2 M content1_content2_content3_content3-tracked - <<<<<<< working copy: 0447570f1af6 - test: local + <<<<<<< working copy: 0447570f1af6 - test: local content3 - ||||||| base + ||||||| common ancestor: 8ef80617fa20 - test: base content1 ======= content2 - >>>>>>> merge rev: 85100b8c675b - test: remote + >>>>>>> merge rev: 85100b8c675b - test: remote M content1_content2_content3_content3-untracked content2 M content1_content2_content3_content4-tracked - <<<<<<< working copy: 0447570f1af6 - test: local + <<<<<<< working copy: 0447570f1af6 - test: local content4 - ||||||| base + ||||||| common ancestor: 8ef80617fa20 - test: base content1 ======= content2 - >>>>>>> merge rev: 85100b8c675b - test: remote + >>>>>>> merge rev: 85100b8c675b - test: remote M content1_content2_content3_content4-untracked content2 @@ -500,13 +500,13 @@ the remote side did not touch the file content2 M content1_content2_missing_content4-tracked - <<<<<<< working copy: 0447570f1af6 - test: local + <<<<<<< working copy: 0447570f1af6 - test: local content4 - ||||||| base + ||||||| common ancestor: 8ef80617fa20 - test: base content1 ======= content2 - >>>>>>> merge rev: 85100b8c675b - test: remote + >>>>>>> merge rev: 85100b8c675b - test: remote M content1_content2_missing_content4-untracked content2 @@ -584,12 +584,12 @@ the remote side did not touch the file content2 M missing_content2_content2_content4-tracked - <<<<<<< working copy: 0447570f1af6 - test: local + <<<<<<< working copy: 0447570f1af6 - test: local content4 - ||||||| base + ||||||| common ancestor: 000000000000 - : ======= content2 - >>>>>>> merge rev: 85100b8c675b - test: remote + >>>>>>> merge rev: 85100b8c675b - test: remote M missing_content2_content2_content4-untracked content2 @@ -607,23 +607,23 @@ the remote side did not touch the file content2 M missing_content2_content3_content3-tracked - <<<<<<< working copy: 0447570f1af6 - test: local + <<<<<<< working copy: 0447570f1af6 - test: local content3 - ||||||| base + ||||||| common ancestor: 000000000000 - : ======= content2 - >>>>>>> merge rev: 85100b8c675b - test: remote + >>>>>>> merge rev: 85100b8c675b - test: remote M missing_content2_content3_content3-untracked content2 M missing_content2_content3_content4-tracked - <<<<<<< working copy: 0447570f1af6 - test: local + <<<<<<< working copy: 0447570f1af6 - test: local content4 - ||||||| base + ||||||| common ancestor: 000000000000 - : ======= content2 - >>>>>>> merge rev: 85100b8c675b - test: remote + >>>>>>> merge rev: 85100b8c675b - test: remote M missing_content2_content3_content4-untracked content2 @@ -641,20 +641,20 @@ the remote side did not touch the file content2 M missing_content2_missing_content4-tracked - <<<<<<< working copy: 0447570f1af6 - test: local + <<<<<<< working copy: 0447570f1af6 - test: local content4 - ||||||| base + ||||||| common ancestor: 000000000000 - : ======= content2 - >>>>>>> merge rev: 85100b8c675b - test: remote + >>>>>>> merge rev: 85100b8c675b - test: remote M missing_content2_missing_content4-untracked - <<<<<<< working copy: 0447570f1af6 - test: local + <<<<<<< working copy: 0447570f1af6 - test: local content4 - ||||||| base + ||||||| common ancestor: 000000000000 - : ======= content2 - >>>>>>> merge rev: 85100b8c675b - test: remote + >>>>>>> merge rev: 85100b8c675b - test: remote M missing_content2_missing_missing-tracked content2 @@ -735,6 +735,7 @@ Re-resolve and check status You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved. What do you want to do? u merging content1_content2_content1_content4-tracked + warning: conflicts while merging content1_content2_content1_content4-tracked! (edit, then use 'hg resolve --mark') file 'content1_content2_content1_content4-untracked' was deleted in local [working copy] but was modified in other [merge rev]. You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved. What do you want to do? u @@ -752,6 +753,7 @@ Re-resolve and check status You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved. What do you want to do? u merging content1_content2_content2_content4-tracked + warning: conflicts while merging content1_content2_content2_content4-tracked! (edit, then use 'hg resolve --mark') file 'content1_content2_content2_content4-untracked' was deleted in local [working copy] but was modified in other [merge rev]. You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved. What do you want to do? u @@ -769,10 +771,12 @@ Re-resolve and check status You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved. What do you want to do? u merging content1_content2_content3_content3-tracked + warning: conflicts while merging content1_content2_content3_content3-tracked! (edit, then use 'hg resolve --mark') file 'content1_content2_content3_content3-untracked' was deleted in local [working copy] but was modified in other [merge rev]. You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved. What do you want to do? u merging content1_content2_content3_content4-tracked + warning: conflicts while merging content1_content2_content3_content4-tracked! (edit, then use 'hg resolve --mark') file 'content1_content2_content3_content4-untracked' was deleted in local [working copy] but was modified in other [merge rev]. You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved. What do you want to do? u @@ -790,6 +794,7 @@ Re-resolve and check status You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved. What do you want to do? u merging content1_content2_missing_content4-tracked + warning: conflicts while merging content1_content2_missing_content4-tracked! (edit, then use 'hg resolve --mark') file 'content1_content2_missing_content4-untracked' was deleted in local [working copy] but was modified in other [merge rev]. You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved. What do you want to do? u @@ -812,19 +817,14 @@ Re-resolve and check status You can use (c)hanged version, (d)elete, or leave (u)nresolved. What do you want to do? u merging missing_content2_content2_content4-tracked + warning: conflicts while merging missing_content2_content2_content4-tracked! (edit, then use 'hg resolve --mark') merging missing_content2_content3_content3-tracked + warning: conflicts while merging missing_content2_content3_content3-tracked! (edit, then use 'hg resolve --mark') merging missing_content2_content3_content4-tracked + warning: conflicts while merging missing_content2_content3_content4-tracked! (edit, then use 'hg resolve --mark') merging missing_content2_missing_content4-tracked + warning: conflicts while merging missing_content2_missing_content4-tracked! (edit, then use 'hg resolve --mark') merging missing_content2_missing_content4-untracked - warning: conflicts while merging content1_content2_content1_content4-tracked! (edit, then use 'hg resolve --mark') - warning: conflicts while merging content1_content2_content2_content4-tracked! (edit, then use 'hg resolve --mark') - warning: conflicts while merging content1_content2_content3_content3-tracked! (edit, then use 'hg resolve --mark') - warning: conflicts while merging content1_content2_content3_content4-tracked! (edit, then use 'hg resolve --mark') - warning: conflicts while merging content1_content2_missing_content4-tracked! (edit, then use 'hg resolve --mark') - warning: conflicts while merging missing_content2_content2_content4-tracked! (edit, then use 'hg resolve --mark') - warning: conflicts while merging missing_content2_content3_content3-tracked! (edit, then use 'hg resolve --mark') - warning: conflicts while merging missing_content2_content3_content4-tracked! (edit, then use 'hg resolve --mark') - warning: conflicts while merging missing_content2_missing_content4-tracked! (edit, then use 'hg resolve --mark') warning: conflicts while merging missing_content2_missing_content4-untracked! (edit, then use 'hg resolve --mark') [1] $ checkstatus > $TESTTMP/status2 2>&1 diff --git a/tests/test-merge-halt.t b/tests/test-merge-halt.t --- a/tests/test-merge-halt.t +++ b/tests/test-merge-halt.t @@ -24,8 +24,8 @@ Testing on-failure=continue $ hg rebase -s 1 -d 2 --tool false rebasing 1:1f28a51c3c9b "c" merging a + merging a failed! merging b - merging a failed! merging b failed! unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') [240] @@ -42,7 +42,6 @@ Testing on-failure=halt $ hg rebase -s 1 -d 2 --tool false rebasing 1:1f28a51c3c9b "c" merging a - merging b merging a failed! unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') [240] @@ -67,9 +66,9 @@ Testing on-failure=prompt > EOS rebasing 1:1f28a51c3c9b "c" merging a - merging b merging a failed! continue merge operation (yn)? y + merging b merging b failed! continue merge operation (yn)? n unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') @@ -94,9 +93,9 @@ Check that successful tool with failed p > EOS rebasing 1:1f28a51c3c9b "c" merging a - merging b output file a appears unchanged was merge successful (yn)? y + merging b output file b appears unchanged was merge successful (yn)? n merging b failed! @@ -122,7 +121,6 @@ Check that conflicts with conflict check $ hg rebase -s 1 -d 2 --tool true rebasing 1:1f28a51c3c9b "c" merging a - merging b merging a failed! unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') [240] @@ -141,8 +139,8 @@ Check that always-prompt also can halt t > EOS rebasing 1:1f28a51c3c9b "c" merging a + was merge of 'a' successful (yn)? y merging b - was merge of 'a' successful (yn)? y was merge of 'b' successful (yn)? n merging b failed! unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') @@ -159,8 +157,8 @@ Check that successful tool otherwise all $ hg rebase -s 1 -d 2 --tool echo --keep --config merge-tools.echo.premerge=keep rebasing 1:1f28a51c3c9b "c" merging a + $TESTTMP/repo/a *a~base* *a~other* (glob) merging b - $TESTTMP/repo/a *a~base* *a~other* (glob) $TESTTMP/repo/b *b~base* *b~other* (glob) Check that unshelve isn't broken by halting the merge @@ -187,7 +185,6 @@ Check that unshelve isn't broken by halt unshelving change 'default' rebasing shelved changes merging shelve_file1 - merging shelve_file2 merging shelve_file1 failed! unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue') [240] @@ -195,7 +192,6 @@ Check that unshelve isn't broken by halt M shelve_file1 M shelve_file2 ? shelve_file1.orig - ? shelve_file2.orig # The repository is in an unfinished *unshelve* state. # Unresolved merge conflicts: @@ -210,7 +206,6 @@ Check that unshelve isn't broken by halt $ hg resolve --tool false --all --re-merge merging shelve_file1 - merging shelve_file2 merging shelve_file1 failed! merge halted after failed merge (see hg resolve) [240] diff --git a/tests/test-merge-internal-tools-pattern.t b/tests/test-merge-internal-tools-pattern.t --- a/tests/test-merge-internal-tools-pattern.t +++ b/tests/test-merge-internal-tools-pattern.t @@ -130,7 +130,7 @@ Merge using internal:union tool: $ hg merge 3 merging f - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + 0 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) $ cat f diff --git a/tests/test-merge-tools.t b/tests/test-merge-tools.t --- a/tests/test-merge-tools.t +++ b/tests/test-merge-tools.t @@ -578,7 +578,6 @@ merge-patterns specifies executable not $ hg merge -r 2 --config merge-patterns.f=true --config merge-tools.true.executable=nonexistentmergetool couldn't find merge tool true (for pattern f) merging f - couldn't find merge tool true (for pattern f) merging f failed! 0 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 @@ -604,7 +603,6 @@ merge-patterns specifies executable with $ hg merge -r 2 --config merge-patterns.f=true --config merge-tools.true.executable=/nonexistent/mergetool couldn't find merge tool true (for pattern f) merging f - couldn't find merge tool true (for pattern f) merging f failed! 0 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 @@ -1225,15 +1223,15 @@ premerge=keep-merge3 keeps conflict mark # hg update -C 1 $ hg merge -r 4 --config merge-tools.true.premerge=keep-merge3 merging f - <<<<<<< working copy: ef83787e2614 - test: revision 1 + <<<<<<< working copy: ef83787e2614 - test: revision 1 revision 1 space - ||||||| base + ||||||| common ancestor: ffd2bda21d6e - test: revision 0 revision 0 space ======= revision 4 - >>>>>>> merge rev: 81448d39c9a0 - test: revision 4 + >>>>>>> merge rev: 81448d39c9a0 - test: revision 4 revision 0 space revision 4 @@ -1241,15 +1239,15 @@ premerge=keep-merge3 keeps conflict mark (branch merge, don't forget to commit) $ aftermerge # cat f - <<<<<<< working copy: ef83787e2614 - test: revision 1 + <<<<<<< working copy: ef83787e2614 - test: revision 1 revision 1 space - ||||||| base + ||||||| common ancestor: ffd2bda21d6e - test: revision 0 revision 0 space ======= revision 4 - >>>>>>> merge rev: 81448d39c9a0 - test: revision 4 + >>>>>>> merge rev: 81448d39c9a0 - test: revision 4 # hg stat M f # hg resolve --list @@ -1266,12 +1264,12 @@ premerge=keep-mergediff keeps conflict m $ hg merge -r 4 --config merge-tools.true.premerge=keep-mergediff merging f <<<<<<< - ------- base - +++++++ working copy: ef83787e2614 - test: revision 1 + ------- common ancestor: ffd2bda21d6e - test: revision 0 + +++++++ working copy: ef83787e2614 - test: revision 1 -revision 0 +revision 1 space - ======= merge rev: 81448d39c9a0 - test: revision 4 + ======= merge rev: 81448d39c9a0 - test: revision 4 revision 4 >>>>>>> revision 0 @@ -1282,12 +1280,12 @@ premerge=keep-mergediff keeps conflict m $ aftermerge # cat f <<<<<<< - ------- base - +++++++ working copy: ef83787e2614 - test: revision 1 + ------- common ancestor: ffd2bda21d6e - test: revision 0 + +++++++ working copy: ef83787e2614 - test: revision 1 -revision 0 +revision 1 space - ======= merge rev: 81448d39c9a0 - test: revision 4 + ======= merge rev: 81448d39c9a0 - test: revision 4 revision 4 >>>>>>> # hg stat @@ -1582,7 +1580,7 @@ mergemarkertemplate settings: true.executable=cat # hg update -C 1 $ cat < printargs_merge_tool - > while test \$# -gt 0; do echo arg: \"\$1\"; shift; done + > while test \$# -gt 0; do echo arg: \""\$1"\"; shift; done > EOF $ hg --config merge-tools.true.executable='sh' \ > --config merge-tools.true.args='./printargs_merge_tool ll:$labellocal lo: $labelother lb:$labelbase": "$base' \ @@ -1594,34 +1592,7 @@ mergemarkertemplate settings: arg: "ll:working copy" arg: "lo:" arg: "merge rev" - arg: "lb:base: */f~base.*" (glob) - 0 files updated, 1 files merged, 0 files removed, 0 files unresolved - (branch merge, don't forget to commit) - $ rm -f 'printargs_merge_tool' - -Same test with experimental.mergetempdirprefix set: - - $ beforemerge - [merge-tools] - false.whatever= - true.priority=1 - true.executable=cat - # hg update -C 1 - $ cat < printargs_merge_tool - > while test \$# -gt 0; do echo arg: \"\$1\"; shift; done - > EOF - $ hg --config experimental.mergetempdirprefix=$TESTTMP/hgmerge. \ - > --config merge-tools.true.executable='sh' \ - > --config merge-tools.true.args='./printargs_merge_tool ll:$labellocal lo: $labelother lb:$labelbase": "$base' \ - > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \ - > --config ui.mergemarkertemplate='uitmpl {rev}' \ - > --config ui.mergemarkers=detailed \ - > merge -r 2 - merging f - arg: "ll:working copy" - arg: "lo:" - arg: "merge rev" - arg: "lb:base: */hgmerge.*/f~base" (glob) + arg: "lb:common ancestor: */f~base" (glob) 0 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) $ rm -f 'printargs_merge_tool' @@ -1638,7 +1609,7 @@ mergemarkertemplate: true.executable=cat # hg update -C 1 $ cat < printargs_merge_tool - > while test \$# -gt 0; do echo arg: \"\$1\"; shift; done + > while test \$# -gt 0; do echo arg: \""\$1"\"; shift; done > EOF $ hg --config merge-tools.true.executable='sh' \ > --config merge-tools.true.args='./printargs_merge_tool ll:$labellocal lo: $labelother lb:$labelbase": "$base' \ @@ -1651,7 +1622,7 @@ mergemarkertemplate: arg: "ll:working copy: tooltmpl ef83787e2614" arg: "lo:" arg: "merge rev: tooltmpl 0185f4e0cf02" - arg: "lb:base: */f~base.*" (glob) + arg: "lb:common ancestor: */f~base" (glob) 0 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) $ rm -f 'printargs_merge_tool' @@ -1666,9 +1637,9 @@ premerge=keep is used and has 'detailed' true.executable=cat # hg update -C 1 $ cat < mytool - > echo labellocal: \"\$1\" - > echo labelother: \"\$2\" - > echo "output (arg)": \"\$3\" + > echo labellocal: \""\$1"\" + > echo labelother: \""\$2"\" + > echo "output (arg)": \""\$3"\" > echo "output (contents)": > cat "\$3" > EOF @@ -1704,9 +1675,9 @@ mergemarkers=detailed; labellocal and la true.executable=cat # hg update -C 1 $ cat < mytool - > echo labellocal: \"\$1\" - > echo labelother: \"\$2\" - > echo "output (arg)": \"\$3\" + > echo labellocal: \""\$1"\" + > echo labelother: \""\$2"\" + > echo "output (arg)": \""\$3"\" > echo "output (contents)": > cat "\$3" > EOF @@ -1837,7 +1808,6 @@ missingbinary is a merge-tool that doesn $ hg merge -y -r 2 --config ui.merge=missingbinary couldn't find merge tool missingbinary (for pattern f) merging f - couldn't find merge tool missingbinary (for pattern f) revision 1 space revision 0 @@ -1898,23 +1868,7 @@ Verify naming of temporary files and tha $ hg update -q -C 2 $ hg merge -y -r tip --tool echo --config merge-tools.echo.args='$base $local $other $output' merging f and f.txt to f.txt - */f~base.* */f~local.*.txt */f~other.*.txt $TESTTMP/repo/f.txt (glob) - 0 files updated, 1 files merged, 0 files removed, 0 files unresolved - (branch merge, don't forget to commit) - -Verify naming of temporary files and that extension is preserved -(experimental.mergetempdirprefix version): - - $ hg update -q -C 1 - $ hg mv f f.txt - $ hg ci -qm "f.txt" - warning: commit already existed in the repository! - $ hg update -q -C 2 - $ hg merge -y -r tip --tool echo \ - > --config merge-tools.echo.args='$base $local $other $output' \ - > --config experimental.mergetempdirprefix=$TESTTMP/hgmerge. - merging f and f.txt to f.txt - $TESTTMP/hgmerge.*/f~base $TESTTMP/hgmerge.*/f~local.txt $TESTTMP/hgmerge.*/f~other.txt $TESTTMP/repo/f.txt (glob) + */hgmerge-*/f~base */hgmerge-*/f~local.txt */hgmerge-*/f~other.txt */repo/f.txt (glob) 0 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) @@ -2018,7 +1972,7 @@ Check that the extra information is prin Running merge tool for b ("*/bin/echo.exe"): (glob) (windows !) Running merge tool for b (*/bin/echo): (glob) (no-windows !) - local (working copy): 10:2d1f533d add binary file (#2) tip default - - base (base): -1:00000000 default + - base (common ancestor): -1:00000000 default - other (merge rev): 9:1e7ad7d7 add binary file (#1) default merge runs here ... 0 files updated, 1 files merged, 0 files removed, 0 files unresolved diff --git a/tests/test-merge-types.t b/tests/test-merge-types.t --- a/tests/test-merge-types.t +++ b/tests/test-merge-types.t @@ -34,7 +34,7 @@ Symlink is local parent, executable is o branchmerge: True, force: False, partial: False ancestor: c334dc3be0da, local: 521a1e40188f+, remote: 3574f3e69b1c preserving a for resolve of a - a: versions differ -> m (premerge) + a: versions differ -> m tool internal:merge (for pattern a) can't handle symlinks couldn't find merge tool hgmerge no tool found to merge a @@ -68,7 +68,7 @@ Symlink is other parent, executable is l branchmerge: True, force: False, partial: False ancestor: c334dc3be0da, local: 3574f3e69b1c+, remote: 521a1e40188f preserving a for resolve of a - a: versions differ -> m (premerge) + a: versions differ -> m picked tool ':union' for a (binary False symlink True changedelete False) merging a my a@3574f3e69b1c+ other a@521a1e40188f ancestor a@c334dc3be0da @@ -90,7 +90,7 @@ Symlink is other parent, executable is l branchmerge: True, force: False, partial: False ancestor: c334dc3be0da, local: 3574f3e69b1c+, remote: 521a1e40188f preserving a for resolve of a - a: versions differ -> m (premerge) + a: versions differ -> m picked tool ':merge3' for a (binary False symlink True changedelete False) merging a my a@3574f3e69b1c+ other a@521a1e40188f ancestor a@c334dc3be0da @@ -112,7 +112,7 @@ Symlink is other parent, executable is l branchmerge: True, force: False, partial: False ancestor: c334dc3be0da, local: 3574f3e69b1c+, remote: 521a1e40188f preserving a for resolve of a - a: versions differ -> m (premerge) + a: versions differ -> m picked tool ':merge-local' for a (binary False symlink True changedelete False) merging a my a@3574f3e69b1c+ other a@521a1e40188f ancestor a@c334dc3be0da @@ -133,7 +133,7 @@ Symlink is other parent, executable is l branchmerge: True, force: False, partial: False ancestor: c334dc3be0da, local: 3574f3e69b1c+, remote: 521a1e40188f preserving a for resolve of a - a: versions differ -> m (premerge) + a: versions differ -> m picked tool ':merge-other' for a (binary False symlink True changedelete False) merging a my a@3574f3e69b1c+ other a@521a1e40188f ancestor a@c334dc3be0da @@ -166,7 +166,7 @@ Update to link with local change should branchmerge: False, force: False, partial: False ancestor: c334dc3be0da, local: c334dc3be0da+, remote: 521a1e40188f preserving a for resolve of a - a: versions differ -> m (premerge) + a: versions differ -> m (couldn't find merge tool hgmerge|tool hgmerge can't handle symlinks) (re) no tool found to merge a picked tool ':prompt' for a (binary False symlink True changedelete False) @@ -343,9 +343,12 @@ h: l vs l, different $ hg merge merging a + warning: conflicts while merging a! (edit, then use 'hg resolve --mark') warning: cannot merge flags for b without common ancestor - keeping local flags merging b + warning: conflicts while merging b! (edit, then use 'hg resolve --mark') merging bx + warning: conflicts while merging bx! (edit, then use 'hg resolve --mark') warning: cannot merge flags for c without common ancestor - keeping local flags tool internal:merge (for pattern d) can't handle symlinks no tool found to merge d @@ -362,9 +365,6 @@ h: l vs l, different file 'h' needs to be resolved. You can keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved. What do you want to do? u - warning: conflicts while merging a! (edit, then use 'hg resolve --mark') - warning: conflicts while merging b! (edit, then use 'hg resolve --mark') - warning: conflicts while merging bx! (edit, then use 'hg resolve --mark') 3 files updated, 0 files merged, 0 files removed, 6 files unresolved use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon [1] @@ -411,9 +411,12 @@ h: l vs l, different $ hg up -Cqr1 $ hg merge merging a + warning: conflicts while merging a! (edit, then use 'hg resolve --mark') warning: cannot merge flags for b without common ancestor - keeping local flags merging b + warning: conflicts while merging b! (edit, then use 'hg resolve --mark') merging bx + warning: conflicts while merging bx! (edit, then use 'hg resolve --mark') warning: cannot merge flags for c without common ancestor - keeping local flags tool internal:merge (for pattern d) can't handle symlinks no tool found to merge d @@ -430,9 +433,6 @@ h: l vs l, different file 'h' needs to be resolved. You can keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved. What do you want to do? u - warning: conflicts while merging a! (edit, then use 'hg resolve --mark') - warning: conflicts while merging b! (edit, then use 'hg resolve --mark') - warning: conflicts while merging bx! (edit, then use 'hg resolve --mark') 3 files updated, 0 files merged, 0 files removed, 6 files unresolved use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon [1] diff --git a/tests/test-merge1.t b/tests/test-merge1.t --- a/tests/test-merge1.t +++ b/tests/test-merge1.t @@ -349,6 +349,10 @@ Test that updated files are treated as " aren't changed), even if none of mode, size and timestamp of them isn't changed on the filesystem (see also issue4583). +This test is now "best effort" as the mechanism to prevent such race are +getting better, it get more complicated to test a specific scenario that would +trigger it. If you see flakyness here, there is a race. + $ cat > $TESTTMP/abort.py < from __future__ import absolute_import > # emulate aborting before "recordupdates()". in this case, files @@ -365,13 +369,6 @@ isn't changed on the filesystem (see als > extensions.wrapfunction(merge, "applyupdates", applyupdates) > EOF - $ cat >> .hg/hgrc < [fakedirstatewritetime] - > # emulate invoking dirstate.write() via repo.status() - > # at 2000-01-01 00:00 - > fakenow = 200001010000 - > EOF - (file gotten from other revision) $ hg update -q -C 2 @@ -381,12 +378,8 @@ isn't changed on the filesystem (see als $ hg update -q -C 3 $ cat b This is file b1 - $ touch -t 200001010000 b - $ hg debugrebuildstate - $ cat >> .hg/hgrc < [extensions] - > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py > abort = $TESTTMP/abort.py > EOF $ hg merge 5 @@ -394,13 +387,11 @@ isn't changed on the filesystem (see als [255] $ cat >> .hg/hgrc < [extensions] - > fakedirstatewritetime = ! > abort = ! > EOF $ cat b THIS IS FILE B5 - $ touch -t 200001010000 b $ hg status -A b M b @@ -413,12 +404,10 @@ isn't changed on the filesystem (see als $ cat b this is file b6 - $ touch -t 200001010000 b - $ hg debugrebuildstate + $ hg status $ cat >> .hg/hgrc < [extensions] - > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py > abort = $TESTTMP/abort.py > EOF $ hg merge --tool internal:other 5 @@ -426,13 +415,11 @@ isn't changed on the filesystem (see als [255] $ cat >> .hg/hgrc < [extensions] - > fakedirstatewritetime = ! > abort = ! > EOF $ cat b THIS IS FILE B5 - $ touch -t 200001010000 b $ hg status -A b M b diff --git a/tests/test-merge7.t b/tests/test-merge7.t --- a/tests/test-merge7.t +++ b/tests/test-merge7.t @@ -86,13 +86,10 @@ pull and merge from test-a again ancestor: 96b70246a118, local: 50c3a7e29886+, remote: 40d11a4173a8 starting 4 threads for background file closing (?) preserving test.txt for resolve of test.txt - test.txt: versions differ -> m (premerge) + test.txt: versions differ -> m picked tool ':merge' for test.txt (binary False symlink False changedelete False) merging test.txt my test.txt@50c3a7e29886+ other test.txt@40d11a4173a8 ancestor test.txt@96b70246a118 - test.txt: versions differ -> m (merge) - picked tool ':merge' for test.txt (binary False symlink False changedelete False) - my test.txt@50c3a7e29886+ other test.txt@40d11a4173a8 ancestor test.txt@96b70246a118 warning: conflicts while merging test.txt! (edit, then use 'hg resolve --mark') 0 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 diff --git a/tests/test-merge9.t b/tests/test-merge9.t --- a/tests/test-merge9.t +++ b/tests/test-merge9.t @@ -27,8 +27,8 @@ a file-level merge failed test with the rename on the remote side $ HGMERGE=false hg merge merging bar + merging bar failed! merging foo and baz to baz - merging bar failed! 1 files updated, 1 files merged, 0 files removed, 1 files unresolved use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon [1] @@ -41,8 +41,8 @@ test with the rename on the local side 3 files updated, 0 files merged, 1 files removed, 0 files unresolved $ HGMERGE=false hg merge merging bar + merging bar failed! merging baz and foo to baz - merging bar failed! 1 files updated, 1 files merged, 0 files removed, 1 files unresolved use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon [1] diff --git a/tests/test-narrow-acl.t b/tests/test-narrow-acl.t --- a/tests/test-narrow-acl.t +++ b/tests/test-narrow-acl.t @@ -34,7 +34,7 @@ The clone directory should only contain f2 Requirements should contain narrowhg - $ cat narrowclone1/.hg/requires | grep narrowhg + $ hg debugrequires -R narrowclone1 | grep narrowhg narrowhg-experimental NarrowHG should track f1 and f2 diff --git a/tests/test-narrow-clone-no-ellipsis.t b/tests/test-narrow-clone-no-ellipsis.t --- a/tests/test-narrow-clone-no-ellipsis.t +++ b/tests/test-narrow-clone-no-ellipsis.t @@ -22,7 +22,7 @@ narrow clone a file, f10 added 40 changesets with 1 changes to 1 files new changesets *:* (glob) $ cd narrow - $ cat .hg/requires | grep -v generaldelta + $ hg debugrequires | grep -v generaldelta dotencode dirstate-v2 (dirstate-v2 !) fncache @@ -30,6 +30,7 @@ narrow clone a file, f10 persistent-nodemap (rust !) revlog-compression-zstd (zstd !) revlogv1 + share-safe sparserevlog store testonly-simplestore (reposimplestore !) diff --git a/tests/test-narrow-clone-stream.t b/tests/test-narrow-clone-stream.t --- a/tests/test-narrow-clone-stream.t +++ b/tests/test-narrow-clone-stream.t @@ -61,7 +61,7 @@ Cloning a specific file when stream clon Making sure we have the correct set of requirements - $ cat .hg/requires + $ hg debugrequires dotencode (tree !) dotencode (flat-fncache !) dirstate-v2 (dirstate-v2 !) @@ -72,6 +72,7 @@ Making sure we have the correct set of r persistent-nodemap (rust !) revlog-compression-zstd (zstd !) revlogv1 + share-safe sparserevlog store treemanifest (tree !) @@ -86,6 +87,7 @@ Making sure store has the required files fncache (flat-fncache !) meta (tree !) narrowspec + requires undo undo.backupfiles undo.narrowspec diff --git a/tests/test-narrow-clone.t b/tests/test-narrow-clone.t --- a/tests/test-narrow-clone.t +++ b/tests/test-narrow-clone.t @@ -38,7 +38,7 @@ narrow clone a file, f10 added 3 changesets with 1 changes to 1 files new changesets *:* (glob) $ cd narrow - $ cat .hg/requires | grep -v generaldelta + $ hg debugrequires | grep -v generaldelta dotencode dirstate-v2 (dirstate-v2 !) fncache @@ -46,6 +46,7 @@ narrow clone a file, f10 persistent-nodemap (rust !) revlog-compression-zstd (zstd !) revlogv1 + share-safe sparserevlog store testonly-simplestore (reposimplestore !) diff --git a/tests/test-narrow-expanddirstate.t b/tests/test-narrow-expanddirstate.t --- a/tests/test-narrow-expanddirstate.t +++ b/tests/test-narrow-expanddirstate.t @@ -142,7 +142,7 @@ This patch will not apply cleanly at the Hunk #1 FAILED at 0 1 out of 1 hunks FAILED -- saving rejects to file patchdir/f3.rej abort: patch failed to apply - [255] + [20] $ hg tracked | grep patchdir [1] $ hg files | grep patchdir > /dev/null diff --git a/tests/test-narrow-merge-outside.t b/tests/test-narrow-merge-outside.t new file mode 100644 --- /dev/null +++ b/tests/test-narrow-merge-outside.t @@ -0,0 +1,111 @@ +=================================================================== +Test merge behavior with narrow for item outside of the narrow spec +=================================================================== + +This test currently check for simple "outside of narrow" merge case. I suspect +there might be more corner case that need testing, so extending this tests, or +replacing it by a more "generative" version, comparing behavior with and without narow. + +This the feature is currently working with flat manifest only. This is the only +case tested. Consider using test-case if tree start supporting this case of +merge. + +Create some initial setup + + $ . "$TESTDIR/narrow-library.sh" + + $ hg init server + $ echo root > server/root + $ mkdir server/inside + $ mkdir server/outside + $ echo babar > server/inside/inside-change + $ echo pom > server/outside/outside-changing + $ echo arthur > server/outside/outside-removed + $ hg -R server add server/ + adding server/inside/inside-change + adding server/outside/outside-changing + adding server/outside/outside-removed + adding server/root + $ hg -R server commit -m root + + + + $ hg clone ssh://user@dummy/server client --narrow --include inside + requesting all changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + new changesets a0c415d360e5 + updating to branch default + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + +"trivial" change outside of narrow spec on the server + + $ echo zephir > server/outside/outside-added + $ hg -R server add server/outside/outside-added + $ echo flore > server/outside/outside-changing + $ hg -R server remove server/outside/outside-removed + $ hg -R server commit -m "outside change" + +Merge them with some unrelated local change + + $ echo celeste > client/inside/inside-change + $ hg -R client commit -m "inside change" + $ hg -R client pull + pulling from ssh://user@dummy/server + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 0 changes to 0 files (+1 heads) + new changesets f9ec5453023e + (run 'hg heads' to see heads, 'hg merge' to merge) + $ hg -R client merge + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg -R client ci -m 'merge changes' + $ hg -R client push -r . + pushing to ssh://user@dummy/server + searching for changes + remote: adding changesets + remote: adding manifests + remote: adding file changes + remote: added 2 changesets with 1 changes to 1 files + +Checking result +--------------- + +general sentry of all output + + $ hg --repository server manifest --debug --rev 0 + 360afd990eeff79e4a7f9f3ded5ecd7bc2fd3b59 644 inside/inside-change + 7db95ce5cd8e734ad12e3f5f37779a08070a1399 644 outside/outside-changing + 1591f6db41a30b68bd94ddccf4a4ce4f4fbe2a44 644 outside/outside-removed + 50ecbc31c0e82dd60c2747c434d1f11b85c0e178 644 root + $ hg --repository server manifest --debug --rev 1 + 360afd990eeff79e4a7f9f3ded5ecd7bc2fd3b59 644 inside/inside-change + 486c008d6dddcaeb5e5f99556a121800cdcfb149 644 outside/outside-added + 153d7af5e4f53f44475bc0ff2b806c86f019eda4 644 outside/outside-changing + 50ecbc31c0e82dd60c2747c434d1f11b85c0e178 644 root + + $ hg --repository server manifest --debug --rev 2 + 1b3ab69c6c847abc8fd25537241fedcd4d188668 644 inside/inside-change + 7db95ce5cd8e734ad12e3f5f37779a08070a1399 644 outside/outside-changing + 1591f6db41a30b68bd94ddccf4a4ce4f4fbe2a44 644 outside/outside-removed + 50ecbc31c0e82dd60c2747c434d1f11b85c0e178 644 root + $ hg --repository server manifest --debug --rev 3 + 1b3ab69c6c847abc8fd25537241fedcd4d188668 644 inside/inside-change + 486c008d6dddcaeb5e5f99556a121800cdcfb149 644 outside/outside-added + 153d7af5e4f53f44475bc0ff2b806c86f019eda4 644 outside/outside-changing + 50ecbc31c0e82dd60c2747c434d1f11b85c0e178 644 root + +The file changed outside should be changed by the merge + + $ hg --repository server manifest --debug --rev 'desc("inside change")' | grep outside-changing + 7db95ce5cd8e734ad12e3f5f37779a08070a1399 644 outside/outside-changing + + $ hg --repository server manifest --debug --rev 'desc("outside change")' | grep outside-changing + 153d7af5e4f53f44475bc0ff2b806c86f019eda4 644 outside/outside-changing + $ hg --repository server manifest --debug --rev 'desc("merge")' | grep outside-changing + 153d7af5e4f53f44475bc0ff2b806c86f019eda4 644 outside/outside-changing diff --git a/tests/test-narrow-merge.t b/tests/test-narrow-merge.t --- a/tests/test-narrow-merge.t +++ b/tests/test-narrow-merge.t @@ -83,12 +83,67 @@ Can merge conflicting changes inside nar TODO: Can merge non-conflicting changes outside narrow spec $ hg update -q 'desc("modify inside/f1")' + +#if flat + $ hg merge 'desc("modify outside/f1")' - abort: merge affects file 'outside/f1' outside narrow, which is not yet supported (flat !) + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + +status should be clean + + $ hg status + ? inside/f1.orig + +file out of the spec should still not be in the dirstate at all + + $ hg debugdirstate | grep outside/f1 + [1] + +Commit that merge + + $ hg ci -m 'merge from outside to inside' + +status should be clean + + $ hg status + ? inside/f1.orig + +file out of the spec should not be in the mergestate anymore + + $ hg debugmergestate | grep outside/f1 + [1] + +file out of the spec should still not be in the dirstate at all + + $ hg debugdirstate | grep outside/f1 + [1] + +The filenode used should come from p2 + + $ hg manifest --debug --rev . | grep outside/f1 + 83cd11431a3b2aff8a3995e5f27bcf33cdb5be98 644 outside/f1 + $ hg manifest --debug --rev 'p1(.)' | grep outside/f1 + c6b956c48be2cd4fa94be16002aba311143806fa 644 outside/f1 + $ hg manifest --debug --rev 'p2(.)' | grep outside/f1 + 83cd11431a3b2aff8a3995e5f27bcf33cdb5be98 644 outside/f1 + + +remove the commit to get in the previous situation again + + $ hg debugstrip -r . + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/48eb25338b19-a1bb8350-backup.hg + +#else + + $ hg merge 'desc("modify outside/f1")' abort: merge affects file 'outside/' outside narrow, which is not yet supported (tree !) (merging in the other direction may work) [255] +#endif + $ hg update -q 'desc("modify outside/f1")' $ hg merge 'desc("modify inside/f1")' 1 files updated, 0 files merged, 0 files removed, 0 files unresolved @@ -101,4 +156,4 @@ Refuses merge of conflicting outside cha $ hg merge 'desc("conflicting outside/f1")' abort: conflict in file 'outside/f1' is outside narrow clone (flat !) abort: conflict in file 'outside/' is outside narrow clone (tree !) - [255] + [20] diff --git a/tests/test-narrow-rebase.t b/tests/test-narrow-rebase.t --- a/tests/test-narrow-rebase.t +++ b/tests/test-narrow-rebase.t @@ -96,4 +96,4 @@ Rebase interrupts on conflicting changes $ hg rebase -d 'desc("modify outside/f1")' rebasing 4:707c035aadb6 "conflicting outside/f1" abort: conflict in file 'outside/f1' is outside narrow clone - [255] + [20] diff --git a/tests/test-narrow-sparse.t b/tests/test-narrow-sparse.t --- a/tests/test-narrow-sparse.t +++ b/tests/test-narrow-sparse.t @@ -56,7 +56,7 @@ XXX: we should have a flag in `hg debugs $ test -f .hg/sparse [1] - $ cat .hg/requires + $ hg debugrequires dotencode dirstate-v2 (dirstate-v2 !) fncache @@ -65,6 +65,7 @@ XXX: we should have a flag in `hg debugs persistent-nodemap (rust !) revlog-compression-zstd (zstd !) revlogv1 + share-safe sparserevlog store treemanifest (tree !) diff --git a/tests/test-obsmarker-template.t b/tests/test-obsmarker-template.t --- a/tests/test-obsmarker-template.t +++ b/tests/test-obsmarker-template.t @@ -1762,7 +1762,7 @@ Test setup 2 new obsolescence markers obsoleted 1 changesets new changesets 7a230b46bf61 (1 drafts) - (run 'hg heads' to see heads, 'hg merge' to merge) + (run 'hg heads' to see heads) $ hg log --hidden -G o changeset: 2:7a230b46bf61 | tag: tip diff --git a/tests/test-obsolete-distributed.t b/tests/test-obsolete-distributed.t --- a/tests/test-obsolete-distributed.t +++ b/tests/test-obsolete-distributed.t @@ -570,7 +570,7 @@ With --update added 2 changesets with 0 changes to 2 files (+1 heads) (2 other changesets obsolete on arrival) abort: cannot update to target: filtered revision '6' - [255] + [10] $ cd .. diff --git a/tests/test-obsolete-tag-cache.t b/tests/test-obsolete-tag-cache.t --- a/tests/test-obsolete-tag-cache.t +++ b/tests/test-obsolete-tag-cache.t @@ -73,11 +73,11 @@ Hiding a non-tip changeset should change 55482a6fb4b1881fa8f746fd52cf6f096bb21c89 test1 $ hg blackbox -l 5 - 1970/01/01 00:00:00 bob @2942a772f72a444bef4bef13874d515f50fa27b6 (5000)> tags - 1970/01/01 00:00:00 bob @2942a772f72a444bef4bef13874d515f50fa27b6 (5000)> 2/2 cache hits/lookups in * seconds (glob) - 1970/01/01 00:00:00 bob @2942a772f72a444bef4bef13874d515f50fa27b6 (5000)> writing .hg/cache/tags2-visible with 2 tags - 1970/01/01 00:00:00 bob @2942a772f72a444bef4bef13874d515f50fa27b6 (5000)> tags exited 0 after * seconds (glob) - 1970/01/01 00:00:00 bob @2942a772f72a444bef4bef13874d515f50fa27b6 (5000)> blackbox -l 5 + 1970-01-01 00:00:00.000 bob @2942a772f72a444bef4bef13874d515f50fa27b6 (5000)> tags + 1970-01-01 00:00:00.000 bob @2942a772f72a444bef4bef13874d515f50fa27b6 (5000)> 2/2 cache hits/lookups in * seconds (glob) + 1970-01-01 00:00:00.000 bob @2942a772f72a444bef4bef13874d515f50fa27b6 (5000)> writing .hg/cache/tags2-visible with 2 tags + 1970-01-01 00:00:00.000 bob @2942a772f72a444bef4bef13874d515f50fa27b6 (5000)> tags exited 0 after * seconds (glob) + 1970-01-01 00:00:00.000 bob @2942a772f72a444bef4bef13874d515f50fa27b6 (5000)> blackbox -l 5 Hiding another changeset should cause the filtered hash to change @@ -97,11 +97,11 @@ Hiding another changeset should cause th 042eb6bfcc4909bad84a1cbf6eb1ddf0ab587d41 head2 $ hg blackbox -l 5 - 1970/01/01 00:00:00 bob @2942a772f72a444bef4bef13874d515f50fa27b6 (5000)> tags - 1970/01/01 00:00:00 bob @2942a772f72a444bef4bef13874d515f50fa27b6 (5000)> 1/1 cache hits/lookups in * seconds (glob) - 1970/01/01 00:00:00 bob @2942a772f72a444bef4bef13874d515f50fa27b6 (5000)> writing .hg/cache/tags2-visible with 1 tags - 1970/01/01 00:00:00 bob @2942a772f72a444bef4bef13874d515f50fa27b6 (5000)> tags exited 0 after * seconds (glob) - 1970/01/01 00:00:00 bob @2942a772f72a444bef4bef13874d515f50fa27b6 (5000)> blackbox -l 5 + 1970-01-01 00:00:00.000 bob @2942a772f72a444bef4bef13874d515f50fa27b6 (5000)> tags + 1970-01-01 00:00:00.000 bob @2942a772f72a444bef4bef13874d515f50fa27b6 (5000)> 1/1 cache hits/lookups in * seconds (glob) + 1970-01-01 00:00:00.000 bob @2942a772f72a444bef4bef13874d515f50fa27b6 (5000)> writing .hg/cache/tags2-visible with 1 tags + 1970-01-01 00:00:00.000 bob @2942a772f72a444bef4bef13874d515f50fa27b6 (5000)> tags exited 0 after * seconds (glob) + 1970-01-01 00:00:00.000 bob @2942a772f72a444bef4bef13874d515f50fa27b6 (5000)> blackbox -l 5 Resolving tags on an unfiltered repo writes a separate tags cache @@ -118,8 +118,8 @@ Resolving tags on an unfiltered repo wri d75775ffbc6bca1794d300f5571272879bd280da test2 $ hg blackbox -l 5 - 1970/01/01 00:00:00 bob @2942a772f72a444bef4bef13874d515f50fa27b6 (5000)> --hidden tags - 1970/01/01 00:00:00 bob @2942a772f72a444bef4bef13874d515f50fa27b6 (5000)> 2/2 cache hits/lookups in * seconds (glob) - 1970/01/01 00:00:00 bob @2942a772f72a444bef4bef13874d515f50fa27b6 (5000)> writing .hg/cache/tags2 with 3 tags - 1970/01/01 00:00:00 bob @2942a772f72a444bef4bef13874d515f50fa27b6 (5000)> --hidden tags exited 0 after * seconds (glob) - 1970/01/01 00:00:00 bob @2942a772f72a444bef4bef13874d515f50fa27b6 (5000)> blackbox -l 5 + 1970-01-01 00:00:00.000 bob @2942a772f72a444bef4bef13874d515f50fa27b6 (5000)> --hidden tags + 1970-01-01 00:00:00.000 bob @2942a772f72a444bef4bef13874d515f50fa27b6 (5000)> 2/2 cache hits/lookups in * seconds (glob) + 1970-01-01 00:00:00.000 bob @2942a772f72a444bef4bef13874d515f50fa27b6 (5000)> writing .hg/cache/tags2 with 3 tags + 1970-01-01 00:00:00.000 bob @2942a772f72a444bef4bef13874d515f50fa27b6 (5000)> --hidden tags exited 0 after * seconds (glob) + 1970-01-01 00:00:00.000 bob @2942a772f72a444bef4bef13874d515f50fa27b6 (5000)> blackbox -l 5 diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t --- a/tests/test-obsolete.t +++ b/tests/test-obsolete.t @@ -169,10 +169,6 @@ check that heads does not report them 5:5601fb93a350 (draft) [tip ] add new_3_c $ hg heads --hidden 5:5601fb93a350 (draft) [tip ] add new_3_c - 4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c [rewritten as 5:5601fb93a350] - 3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c [rewritten as 4:ca819180edb9] - 2:245bde4270cd (draft *obsolete*) [ ] add original_c [rewritten as 3:cdbce2fbb163] - check that summary does not report them @@ -193,7 +189,7 @@ check that summary does not report them add new_3_c branch: default commit: (clean) - update: 3 new changesets, 4 branch heads (merge) + update: (current) phases: 6 draft remote: 3 outgoing diff --git a/tests/test-parseindex2.py b/tests/test-parseindex2.py --- a/tests/test-parseindex2.py +++ b/tests/test-parseindex2.py @@ -57,6 +57,7 @@ def py_parseindex(data, inline): 0, constants.COMP_MODE_INLINE, constants.COMP_MODE_INLINE, + constants.RANK_UNKNOWN, ) nodemap[e[7]] = n append(e) @@ -72,6 +73,7 @@ def py_parseindex(data, inline): 0, constants.COMP_MODE_INLINE, constants.COMP_MODE_INLINE, + constants.RANK_UNKNOWN, ) nodemap[e[7]] = n append(e) @@ -132,8 +134,8 @@ data_non_inlined = ( ) -def parse_index2(data, inline, revlogv2=False): - index, chunkcache = parsers.parse_index2(data, inline, revlogv2=revlogv2) +def parse_index2(data, inline, format=constants.REVLOGV1): + index, chunkcache = parsers.parse_index2(data, inline, format=format) return list(index), chunkcache @@ -268,6 +270,7 @@ class parseindex2tests(unittest.TestCase 0, constants.COMP_MODE_INLINE, constants.COMP_MODE_INLINE, + constants.RANK_UNKNOWN, ) index, junk = parsers.parse_index2(data_inlined, True) got = index[-1] @@ -303,6 +306,7 @@ class parseindex2tests(unittest.TestCase 0, constants.COMP_MODE_INLINE, constants.COMP_MODE_INLINE, + constants.RANK_UNKNOWN, ) index.append(e) diff --git a/tests/test-permissions.t b/tests/test-permissions.t --- a/tests/test-permissions.t +++ b/tests/test-permissions.t @@ -78,7 +78,7 @@ (fsmonitor makes "hg status" avoid accessing to "dir") $ hg status - dir: Permission denied + dir: Permission denied* (glob) M a #endif diff --git a/tests/test-persistent-nodemap.t b/tests/test-persistent-nodemap.t --- a/tests/test-persistent-nodemap.t +++ b/tests/test-persistent-nodemap.t @@ -65,6 +65,7 @@ As a result, -1 passed from Rust for the format-variant repo fncache: yes dirstate-v2: no + tracked-hint: no dotencode: yes generaldelta: yes share-safe: yes @@ -782,9 +783,10 @@ downgrading format-variant repo config default fncache: yes yes yes dirstate-v2: no no no + tracked-hint: no no no dotencode: yes yes yes generaldelta: yes yes yes - share-safe: yes yes no + share-safe: yes yes yes sparserevlog: yes yes yes persistent-nodemap: yes no no copies-sdc: no no no @@ -809,8 +811,6 @@ downgrading - manifest $ ls -1 .hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)' - 00changelog-*.nd (glob) - 00manifest-*.nd (glob) undo.backup.00changelog.n undo.backup.00manifest.n $ hg debugnodemap --metadata @@ -826,9 +826,10 @@ upgrading format-variant repo config default fncache: yes yes yes dirstate-v2: no no no + tracked-hint: no no no dotencode: yes yes yes generaldelta: yes yes yes - share-safe: yes yes no + share-safe: yes yes yes sparserevlog: yes yes yes persistent-nodemap: no yes no copies-sdc: no no no diff --git a/tests/test-phases.t b/tests/test-phases.t --- a/tests/test-phases.t +++ b/tests/test-phases.t @@ -882,16 +882,8 @@ Check we deny its usage on older reposit $ hg init no-internal-phase --config format.internal-phase=no $ cd no-internal-phase - $ cat .hg/requires - dotencode - dirstate-v2 (dirstate-v2 !) - fncache - generaldelta - persistent-nodemap (rust !) - revlog-compression-zstd (zstd !) - revlogv1 - sparserevlog - store + $ hg debugrequires | grep internal-phase + [1] $ echo X > X $ hg add X $ hg status @@ -911,17 +903,8 @@ Check it works fine with repository that $ hg init internal-phase --config format.internal-phase=yes $ cd internal-phase - $ cat .hg/requires - dotencode - dirstate-v2 (dirstate-v2 !) - fncache - generaldelta + $ hg debugrequires | grep internal-phase internal-phase - persistent-nodemap (rust !) - revlog-compression-zstd (zstd !) - revlogv1 - sparserevlog - store $ mkcommit A test-debug-phase: new rev 0: x -> 1 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> draft diff --git a/tests/test-pull-network.t b/tests/test-pull-network.t --- a/tests/test-pull-network.t +++ b/tests/test-pull-network.t @@ -1,15 +1,5 @@ #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 diff --git a/tests/test-pull-r.t b/tests/test-pull-r.t --- a/tests/test-pull-r.t +++ b/tests/test-pull-r.t @@ -112,7 +112,7 @@ Pull a missing revision: $ hg pull -qr missing ../repo abort: unknown revision 'missing' - [255] + [10] Pull multiple revisions with update: diff --git a/tests/test-purge.t b/tests/test-purge.t --- a/tests/test-purge.t +++ b/tests/test-purge.t @@ -29,7 +29,7 @@ purge without the extension $ hg st $ touch foo $ hg purge - permanently delete 1 unkown files? (yN) n + permanently delete 1 unknown files? (yN) n abort: removal cancelled [250] $ hg st @@ -93,7 +93,7 @@ delete an untracked file untracked_file untracked_file_readonly $ hg purge --confirm - permanently delete 2 unkown files? (yN) n + permanently delete 2 unknown files? (yN) n abort: removal cancelled [250] $ hg purge -v @@ -156,7 +156,7 @@ delete only part of the tree $ hg purge -p ../untracked_directory untracked_directory/nested_directory $ hg purge --confirm - permanently delete 1 unkown files? (yN) n + permanently delete 1 unknown files? (yN) n abort: removal cancelled [250] $ hg purge -v ../untracked_directory @@ -203,7 +203,7 @@ skip ignored files if -i or --all not sp ignored untracked_file $ hg purge --confirm --all - permanently delete 1 unkown and 1 ignored files? (yN) n + permanently delete 1 unknown and 1 ignored files? (yN) n abort: removal cancelled [250] $ hg purge -v --all diff --git a/tests/test-qrecord.t b/tests/test-qrecord.t --- a/tests/test-qrecord.t +++ b/tests/test-qrecord.t @@ -117,7 +117,7 @@ help (bad mq) $ echo "mq=nonexistent" >> $HGRCPATH $ hg help qrecord - *** failed to import extension mq from nonexistent: [Errno *] * (glob) + *** failed to import extension "mq" from nonexistent: [Errno *] * (glob) hg qrecord [OPTION]... PATCH [FILE]... interactively record a new patch diff --git a/tests/test-rank.t b/tests/test-rank.t new file mode 100644 --- /dev/null +++ b/tests/test-rank.t @@ -0,0 +1,223 @@ +============================================================= +Check that we can compute and exchange revision rank properly +============================================================= + + $ cat << EOF >> $HGRCPATH + > [format] + > exp-use-changelog-v2=enable-unstable-format-and-corrupt-my-data + > EOF + + +Test minimal rank computation with merge + + $ hg init rank-repo-minimal + $ cd rank-repo-minimal + $ touch 0 + $ hg commit -Aqm 0 + $ touch 1 + $ hg commit -Aqm 1 + $ hg update -qr 0 + $ touch 2 + $ hg commit -Aqm 2 + $ hg merge -qr 1 + $ hg commit -m 3 + $ touch 4 + $ hg commit -Aqm 4 + $ hg log --graph --template '{rev} {_fast_rank}\n' + @ 4 5 + | + o 3 4 + |\ + | o 2 2 + | | + o | 1 2 + |/ + o 0 1 + + $ cd .. + + +Build a bigger example repo + + $ hg init rank-repo-generated + $ cd rank-repo-generated + $ hg debugbuilddag '.:root1+5:mp1>>>>>> source: f899f3910ce7 B - test: B + >>>>>>> source: f899f3910ce7 B - test: B $ echo BC > A $ hg resolve -m (no more unresolved files) @@ -745,13 +745,13 @@ Test aborted editor on final message o 0: 4a2df7238c3b 'A' $ cat A - <<<<<<< dest: 82b8abf9c185 D - test: D + <<<<<<< dest: 82b8abf9c185 D - test: D BC - ||||||| base + ||||||| parent of source: f899f3910ce7 B - test: B B ======= C - >>>>>>> source: 63668d570d21 C tip - test: C + >>>>>>> source: 63668d570d21 C tip - test: C $ echo BD > A $ hg resolve -m (no more unresolved files) diff --git a/tests/test-rebase-conflicts.t b/tests/test-rebase-conflicts.t --- a/tests/test-rebase-conflicts.t +++ b/tests/test-rebase-conflicts.t @@ -372,13 +372,13 @@ Test minimization of merge conflicts +++ b/a * (glob) @@ -1,2 +1,8 @@ a - +<<<<<<< dest: 328e4ab1f7cc ab - test: ab + +<<<<<<< dest: 328e4ab1f7cc ab - test: ab b - +||||||| base + +||||||| parent of source: cb9a9f314b8b - test: a +======= +b +c - +>>>>>>> source: 7bc217434fc1 - test: abc + +>>>>>>> source: 7bc217434fc1 - test: abc Test rebase with obsstore turned on and off (issue5606) diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t --- a/tests/test-rebase-obsolete.t +++ b/tests/test-rebase-obsolete.t @@ -650,6 +650,7 @@ Test hidden changesets in the rebase set $ hg add J $ hg commit -m J 1 new orphan changesets + created new head $ hg debugobsolete `hg log --rev . -T '{node}'` 1 new obsolescence markers obsoleted 1 changesets diff --git a/tests/test-rebase-obsolete2.t b/tests/test-rebase-obsolete2.t --- a/tests/test-rebase-obsolete2.t +++ b/tests/test-rebase-obsolete2.t @@ -47,6 +47,7 @@ setup $ hg add C $ hg commit -m C 1 new orphan changesets + created new head $ hg log -G @ 4:212cb178bcbb C | @@ -73,6 +74,7 @@ Even when the chain include missing node $ hg add D $ hg commit -m D 1 new orphan changesets + created new head $ hg --hidden strip -r 'desc(B1)' saved backup bundle to $TESTTMP/obsskip/.hg/strip-backup/86f6414ccda7-b1c452ee-backup.hg 1 new orphan changesets @@ -194,6 +196,7 @@ If a rebase is going to create divergenc $ hg add foo $ hg commit -m "bar foo" 1 new orphan changesets + created new head $ hg log -G @ 14:73568ab6879d bar foo | diff --git a/tests/test-rebase-obsolete4.t b/tests/test-rebase-obsolete4.t --- a/tests/test-rebase-obsolete4.t +++ b/tests/test-rebase-obsolete4.t @@ -169,7 +169,7 @@ rebasestate may contain hidden hashes. " D branch: default commit: 1 modified, 1 added, 1 unknown, 1 unresolved - update: 1 new changesets, 2 branch heads (merge) + update: (current) phases: 3 draft rebase: 0 rebased, 2 remaining (rebase --continue) diff --git a/tests/test-rebase-scenario-global.t b/tests/test-rebase-scenario-global.t --- a/tests/test-rebase-scenario-global.t +++ b/tests/test-rebase-scenario-global.t @@ -380,7 +380,7 @@ Check that temporary bundle doesn't lose $ hg --config format.usegeneraldelta=no init issue5678 $ cd issue5678 - $ grep generaldelta .hg/requires + $ hg debugrequires | grep generaldelta [1] $ echo a > a $ hg ci -Aqm a diff --git a/tests/test-rebuildstate.t b/tests/test-rebuildstate.t --- a/tests/test-rebuildstate.t +++ b/tests/test-rebuildstate.t @@ -79,6 +79,7 @@ but in the dirstate $ touch foo bar qux $ hg add qux $ hg remove bar + $ sleep 1 # remove potential ambiguity in mtime $ hg status -A A qux R bar @@ -106,6 +107,7 @@ dirstate $ hg manifest bar foo + $ sleep 1 # remove potential ambiguity in mtime $ hg status -A A qux R bar diff --git a/tests/test-remotefilelog-bundle2-legacy.t b/tests/test-remotefilelog-bundle2-legacy.t --- a/tests/test-remotefilelog-bundle2-legacy.t +++ b/tests/test-remotefilelog-bundle2-legacy.t @@ -40,7 +40,7 @@ without changegroup2 support > EOF $ hg init master - $ grep generaldelta master/.hg/requires + $ hg debugrequires -R master | grep generaldelta generaldelta $ cd master preferuncompressed = False so that we can make both generaldelta and non-generaldelta clones diff --git a/tests/test-remotefilelog-bundle2.t b/tests/test-remotefilelog-bundle2.t --- a/tests/test-remotefilelog-bundle2.t +++ b/tests/test-remotefilelog-bundle2.t @@ -3,7 +3,7 @@ $ . "$TESTDIR/remotefilelog-library.sh" $ hg init master - $ grep generaldelta master/.hg/requires + $ hg debugrequires -R master | grep generaldelta generaldelta $ cd master preferuncompressed = False so that we can make both generaldelta and non-generaldelta clones @@ -22,10 +22,10 @@ preferuncompressed = False so that we ca $ hgcloneshallow ssh://user@dummy/master shallow-generaldelta -q --pull --config experimental.bundle2-exp=True 1 files fetched over 1 fetches - (1 misses, 0.00% hit ratio) over *s (glob) - $ grep generaldelta shallow-generaldelta/.hg/requires + $ hg debugrequires -R shallow-generaldelta/ | grep generaldelta generaldelta $ hgcloneshallow ssh://user@dummy/master shallow-plain -q --pull --config format.usegeneraldelta=False --config format.generaldelta=False --config experimental.bundle2-exp=True - $ grep generaldelta shallow-plain/.hg/requires + $ hg debugrequires -R shallow-plain/ | grep generaldelta [1] $ cd master diff --git a/tests/test-remotefilelog-clone-tree.t b/tests/test-remotefilelog-clone-tree.t --- a/tests/test-remotefilelog-clone-tree.t +++ b/tests/test-remotefilelog-clone-tree.t @@ -25,7 +25,7 @@ searching for changes no changes found $ cd shallow - $ cat .hg/requires + $ hg debugrequires dotencode dirstate-v2 (dirstate-v2 !) exp-remotefilelog-repo-req-1 @@ -34,6 +34,7 @@ persistent-nodemap (rust !) revlog-compression-zstd (zstd !) revlogv1 + share-safe sparserevlog store treemanifest @@ -69,7 +70,7 @@ searching for changes no changes found $ cd shallow2 - $ cat .hg/requires + $ hg debugrequires dotencode dirstate-v2 (dirstate-v2 !) exp-remotefilelog-repo-req-1 @@ -78,6 +79,7 @@ persistent-nodemap (rust !) revlog-compression-zstd (zstd !) revlogv1 + share-safe sparserevlog store treemanifest @@ -113,7 +115,7 @@ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ ls shallow3/.hg/store/data - $ cat shallow3/.hg/requires + $ hg debugrequires -R shallow3/ dotencode dirstate-v2 (dirstate-v2 !) exp-remotefilelog-repo-req-1 @@ -122,6 +124,7 @@ persistent-nodemap (rust !) revlog-compression-zstd (zstd !) revlogv1 + share-safe sparserevlog store treemanifest diff --git a/tests/test-remotefilelog-clone.t b/tests/test-remotefilelog-clone.t --- a/tests/test-remotefilelog-clone.t +++ b/tests/test-remotefilelog-clone.t @@ -22,7 +22,7 @@ searching for changes no changes found $ cd shallow - $ cat .hg/requires + $ hg debugrequires dotencode dirstate-v2 (dirstate-v2 !) exp-remotefilelog-repo-req-1 @@ -31,6 +31,7 @@ persistent-nodemap (rust !) revlog-compression-zstd (zstd !) revlogv1 + share-safe sparserevlog store @@ -59,7 +60,7 @@ searching for changes no changes found $ cd shallow2 - $ cat .hg/requires + $ hg debugrequires dotencode dirstate-v2 (dirstate-v2 !) exp-remotefilelog-repo-req-1 @@ -68,6 +69,7 @@ persistent-nodemap (rust !) revlog-compression-zstd (zstd !) revlogv1 + share-safe sparserevlog store $ ls .hg/store/data @@ -111,7 +113,7 @@ check its contents separately. 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ ls shallow3/.hg/store/data - $ cat shallow3/.hg/requires + $ hg debugrequires -R shallow3/ dotencode dirstate-v2 (dirstate-v2 !) exp-remotefilelog-repo-req-1 @@ -120,5 +122,6 @@ check its contents separately. persistent-nodemap (rust !) revlog-compression-zstd (zstd !) revlogv1 + share-safe sparserevlog store diff --git a/tests/test-remotefilelog-log.t b/tests/test-remotefilelog-log.t --- a/tests/test-remotefilelog-log.t +++ b/tests/test-remotefilelog-log.t @@ -25,7 +25,7 @@ Shallow clone from full searching for changes no changes found $ cd shallow - $ cat .hg/requires + $ hg debugrequires dotencode dirstate-v2 (dirstate-v2 !) exp-remotefilelog-repo-req-1 @@ -34,6 +34,7 @@ Shallow clone from full persistent-nodemap (rust !) revlog-compression-zstd (zstd !) revlogv1 + share-safe sparserevlog store diff --git a/tests/test-remotefilelog-repack.t b/tests/test-remotefilelog-repack.t --- a/tests/test-remotefilelog-repack.t +++ b/tests/test-remotefilelog-repack.t @@ -307,7 +307,7 @@ 3 gen1 packs, 1 gen0 pack - packs 3 gen1 1 files fetched over 1 fetches - (1 misses, 0.00% hit ratio) over * (glob) $ hg prefetch -r 38 abort: unknown revision '38' - [255] + [10] $ ls_l $TESTTMP/hgcache/master/packs/ | grep datapack -r--r--r-- 70 052643fdcdebbd42d7c180a651a30d46098e6fe1.datapack $ ls_l $TESTTMP/hgcache/master/packs/ | grep histpack diff --git a/tests/test-rename-merge1.t b/tests/test-rename-merge1.t --- a/tests/test-rename-merge1.t +++ b/tests/test-rename-merge1.t @@ -44,7 +44,7 @@ getting b2 preserving a for resolve of b removing a - b: remote moved from a -> m (premerge) + b: remote moved from a -> m picked tool ':merge' for b (binary False symlink False changedelete False) merging a and b to b my b@044f8520aeeb+ other b@85c198ef2f6c ancestor a@af1939970a1c @@ -218,7 +218,7 @@ modify x and rename y to z on the other ancestor: 5151c134577e, local: 07fcbc9a74ed+, remote: f21419739508 starting 4 threads for background file closing (?) preserving z for resolve of z - z: both renamed from y -> m (premerge) + z: both renamed from y -> m picked tool ':merge3' for z (binary False symlink False changedelete False) merging z my z@07fcbc9a74ed+ other z@f21419739508 ancestor y@5151c134577e diff --git a/tests/test-rename-merge2.t b/tests/test-rename-merge2.t --- a/tests/test-rename-merge2.t +++ b/tests/test-rename-merge2.t @@ -88,18 +88,15 @@ args: starting 4 threads for background file closing (?) preserving a for resolve of b preserving rev for resolve of rev - b: remote copied from a -> m (premerge) + b: remote copied from a -> m picked tool '* ../merge' for b (binary False symlink False changedelete False) (glob) merging a and b to b my b@e300d1c794ec+ other b@4ce40f5aca24 ancestor a@924404dff337 premerge successful - rev: versions differ -> m (premerge) + rev: versions differ -> m picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) merging rev my rev@e300d1c794ec+ other rev@4ce40f5aca24 ancestor rev@924404dff337 - rev: versions differ -> m (merge) - picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) - my rev@e300d1c794ec+ other rev@4ce40f5aca24 ancestor rev@924404dff337 launching merge tool: * ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 0 files updated, 2 files merged, 0 files removed, 0 files unresolved @@ -128,18 +125,15 @@ args: getting a preserving b for resolve of b preserving rev for resolve of rev - b: local copied/moved from a -> m (premerge) + b: local copied/moved from a -> m picked tool '* ../merge' for b (binary False symlink False changedelete False) (glob) merging b and a to b my b@86a2aa42fc76+ other a@f4db7e329e71 ancestor a@924404dff337 premerge successful - rev: versions differ -> m (premerge) + rev: versions differ -> m picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) merging rev my rev@86a2aa42fc76+ other rev@f4db7e329e71 ancestor rev@924404dff337 - rev: versions differ -> m (merge) - picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) - my rev@86a2aa42fc76+ other rev@f4db7e329e71 ancestor rev@924404dff337 launching merge tool: * ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 1 files updated, 2 files merged, 0 files removed, 0 files unresolved @@ -168,18 +162,15 @@ args: preserving a for resolve of b preserving rev for resolve of rev removing a - b: remote moved from a -> m (premerge) + b: remote moved from a -> m picked tool '* ../merge' for b (binary False symlink False changedelete False) (glob) merging a and b to b my b@e300d1c794ec+ other b@bdb19105162a ancestor a@924404dff337 premerge successful - rev: versions differ -> m (premerge) + rev: versions differ -> m picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) merging rev my rev@e300d1c794ec+ other rev@bdb19105162a ancestor rev@924404dff337 - rev: versions differ -> m (merge) - picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) - my rev@e300d1c794ec+ other rev@bdb19105162a ancestor rev@924404dff337 launching merge tool: * ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 0 files updated, 2 files merged, 0 files removed, 0 files unresolved @@ -206,18 +197,15 @@ args: starting 4 threads for background file closing (?) preserving b for resolve of b preserving rev for resolve of rev - b: local copied/moved from a -> m (premerge) + b: local copied/moved from a -> m picked tool '* ../merge' for b (binary False symlink False changedelete False) (glob) merging b and a to b my b@02963e448370+ other a@f4db7e329e71 ancestor a@924404dff337 premerge successful - rev: versions differ -> m (premerge) + rev: versions differ -> m picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) merging rev my rev@02963e448370+ other rev@f4db7e329e71 ancestor rev@924404dff337 - rev: versions differ -> m (merge) - picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) - my rev@02963e448370+ other rev@f4db7e329e71 ancestor rev@924404dff337 launching merge tool: * ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 0 files updated, 2 files merged, 0 files removed, 0 files unresolved @@ -244,13 +232,10 @@ args: b: remote created -> g getting b preserving rev for resolve of rev - rev: versions differ -> m (premerge) + rev: versions differ -> m picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) merging rev my rev@94b33a1b7f2d+ other rev@4ce40f5aca24 ancestor rev@924404dff337 - rev: versions differ -> m (merge) - picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) - my rev@94b33a1b7f2d+ other rev@4ce40f5aca24 ancestor rev@924404dff337 launching merge tool: * ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 1 files updated, 1 files merged, 0 files removed, 0 files unresolved @@ -276,13 +261,10 @@ args: ancestor: 924404dff337, local: 86a2aa42fc76+, remote: 97c705ade336 starting 4 threads for background file closing (?) preserving rev for resolve of rev - rev: versions differ -> m (premerge) + rev: versions differ -> m picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) merging rev my rev@86a2aa42fc76+ other rev@97c705ade336 ancestor rev@924404dff337 - rev: versions differ -> m (merge) - picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) - my rev@86a2aa42fc76+ other rev@97c705ade336 ancestor rev@924404dff337 launching merge tool: * ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 0 files updated, 1 files merged, 0 files removed, 0 files unresolved @@ -311,13 +293,10 @@ args: b: remote created -> g getting b preserving rev for resolve of rev - rev: versions differ -> m (premerge) + rev: versions differ -> m picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) merging rev my rev@94b33a1b7f2d+ other rev@bdb19105162a ancestor rev@924404dff337 - rev: versions differ -> m (merge) - picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) - my rev@94b33a1b7f2d+ other rev@bdb19105162a ancestor rev@924404dff337 launching merge tool: * ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 1 files updated, 1 files merged, 1 files removed, 0 files unresolved @@ -342,13 +321,10 @@ args: ancestor: 924404dff337, local: 02963e448370+, remote: 97c705ade336 starting 4 threads for background file closing (?) preserving rev for resolve of rev - rev: versions differ -> m (premerge) + rev: versions differ -> m picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) merging rev my rev@02963e448370+ other rev@97c705ade336 ancestor rev@924404dff337 - rev: versions differ -> m (merge) - picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) - my rev@02963e448370+ other rev@97c705ade336 ancestor rev@924404dff337 launching merge tool: * ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 0 files updated, 1 files merged, 0 files removed, 0 files unresolved @@ -374,22 +350,16 @@ args: starting 4 threads for background file closing (?) preserving b for resolve of b preserving rev for resolve of rev - b: both renamed from a -> m (premerge) + b: both renamed from a -> m picked tool '* ../merge' for b (binary False symlink False changedelete False) (glob) merging b my b@62e7bf090eba+ other b@49b6d8032493 ancestor a@924404dff337 - rev: versions differ -> m (premerge) + launching merge tool: * ../merge *$TESTTMP/t/t/b* * * (glob) + merge tool returned: 0 + rev: versions differ -> m picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) merging rev my rev@62e7bf090eba+ other rev@49b6d8032493 ancestor rev@924404dff337 - b: both renamed from a -> m (merge) - picked tool '* ../merge' for b (binary False symlink False changedelete False) (glob) - my b@62e7bf090eba+ other b@49b6d8032493 ancestor a@924404dff337 - launching merge tool: * ../merge *$TESTTMP/t/t/b* * * (glob) - merge tool returned: 0 - rev: versions differ -> m (merge) - picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) - my rev@62e7bf090eba+ other rev@49b6d8032493 ancestor rev@924404dff337 launching merge tool: * ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 0 files updated, 2 files merged, 0 files removed, 0 files unresolved @@ -425,13 +395,10 @@ m "um a c" "um x c" " " "10 do merg c: remote created -> g getting c preserving rev for resolve of rev - rev: versions differ -> m (premerge) + rev: versions differ -> m picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) merging rev my rev@02963e448370+ other rev@fe905ef2c33e ancestor rev@924404dff337 - rev: versions differ -> m (merge) - picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) - my rev@02963e448370+ other rev@fe905ef2c33e ancestor rev@924404dff337 launching merge tool: * ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 1 files updated, 1 files merged, 0 files removed, 0 files unresolved @@ -456,22 +423,16 @@ m "um a c" "um x c" " " "10 do merg starting 4 threads for background file closing (?) preserving b for resolve of b preserving rev for resolve of rev - b: both created -> m (premerge) + b: both created -> m picked tool '* ../merge' for b (binary False symlink False changedelete False) (glob) merging b my b@86a2aa42fc76+ other b@af30c7647fc7 ancestor b@000000000000 - rev: versions differ -> m (premerge) + launching merge tool: * ../merge *$TESTTMP/t/t/b* * * (glob) + merge tool returned: 0 + rev: versions differ -> m picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) merging rev my rev@86a2aa42fc76+ other rev@af30c7647fc7 ancestor rev@924404dff337 - b: both created -> m (merge) - picked tool '* ../merge' for b (binary False symlink False changedelete False) (glob) - my b@86a2aa42fc76+ other b@af30c7647fc7 ancestor b@000000000000 - launching merge tool: * ../merge *$TESTTMP/t/t/b* * * (glob) - merge tool returned: 0 - rev: versions differ -> m (merge) - picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) - my rev@86a2aa42fc76+ other rev@af30c7647fc7 ancestor rev@924404dff337 launching merge tool: * ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 0 files updated, 2 files merged, 0 files removed, 0 files unresolved @@ -498,22 +459,16 @@ m "um a c" "um x c" " " "10 do merg starting 4 threads for background file closing (?) preserving b for resolve of b preserving rev for resolve of rev - b: both created -> m (premerge) + b: both created -> m picked tool '* ../merge' for b (binary False symlink False changedelete False) (glob) merging b my b@59318016310c+ other b@bdb19105162a ancestor b@000000000000 - rev: versions differ -> m (premerge) + launching merge tool: * ../merge *$TESTTMP/t/t/b* * * (glob) + merge tool returned: 0 + rev: versions differ -> m picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) merging rev my rev@59318016310c+ other rev@bdb19105162a ancestor rev@924404dff337 - b: both created -> m (merge) - picked tool '* ../merge' for b (binary False symlink False changedelete False) (glob) - my b@59318016310c+ other b@bdb19105162a ancestor b@000000000000 - launching merge tool: * ../merge *$TESTTMP/t/t/b* * * (glob) - merge tool returned: 0 - rev: versions differ -> m (merge) - picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) - my rev@59318016310c+ other rev@bdb19105162a ancestor rev@924404dff337 launching merge tool: * ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 0 files updated, 2 files merged, 1 files removed, 0 files unresolved @@ -538,18 +493,15 @@ m "um a c" "um x c" " " "10 do merg getting a preserving b for resolve of b preserving rev for resolve of rev - b: both renamed from a -> m (premerge) + b: both renamed from a -> m picked tool '* ../merge' for b (binary False symlink False changedelete False) (glob) merging b my b@86a2aa42fc76+ other b@8dbce441892a ancestor a@924404dff337 premerge successful - rev: versions differ -> m (premerge) + rev: versions differ -> m picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) merging rev my rev@86a2aa42fc76+ other rev@8dbce441892a ancestor rev@924404dff337 - rev: versions differ -> m (merge) - picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) - my rev@86a2aa42fc76+ other rev@8dbce441892a ancestor rev@924404dff337 launching merge tool: * ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 1 files updated, 2 files merged, 0 files removed, 0 files unresolved @@ -576,22 +528,16 @@ m "um a c" "um x c" " " "10 do merg starting 4 threads for background file closing (?) preserving b for resolve of b preserving rev for resolve of rev - b: both created -> m (premerge) + b: both created -> m picked tool '* ../merge' for b (binary False symlink False changedelete False) (glob) merging b my b@59318016310c+ other b@bdb19105162a ancestor b@000000000000 - rev: versions differ -> m (premerge) + launching merge tool: * ../merge *$TESTTMP/t/t/b* * * (glob) + merge tool returned: 0 + rev: versions differ -> m picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) merging rev my rev@59318016310c+ other rev@bdb19105162a ancestor rev@924404dff337 - b: both created -> m (merge) - picked tool '* ../merge' for b (binary False symlink False changedelete False) (glob) - my b@59318016310c+ other b@bdb19105162a ancestor b@000000000000 - launching merge tool: * ../merge *$TESTTMP/t/t/b* * * (glob) - merge tool returned: 0 - rev: versions differ -> m (merge) - picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) - my rev@59318016310c+ other rev@bdb19105162a ancestor rev@924404dff337 launching merge tool: * ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 0 files updated, 2 files merged, 1 files removed, 0 files unresolved @@ -616,18 +562,15 @@ m "um a c" "um x c" " " "10 do merg getting a preserving b for resolve of b preserving rev for resolve of rev - b: both renamed from a -> m (premerge) + b: both renamed from a -> m picked tool '* ../merge' for b (binary False symlink False changedelete False) (glob) merging b my b@86a2aa42fc76+ other b@8dbce441892a ancestor a@924404dff337 premerge successful - rev: versions differ -> m (premerge) + rev: versions differ -> m picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) merging rev my rev@86a2aa42fc76+ other rev@8dbce441892a ancestor rev@924404dff337 - rev: versions differ -> m (merge) - picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) - my rev@86a2aa42fc76+ other rev@8dbce441892a ancestor rev@924404dff337 launching merge tool: * ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 1 files updated, 2 files merged, 0 files removed, 0 files unresolved @@ -652,18 +595,15 @@ m "um a c" "um x c" " " "10 do merg starting 4 threads for background file closing (?) preserving b for resolve of b preserving rev for resolve of rev - b: both renamed from a -> m (premerge) + b: both renamed from a -> m picked tool '* ../merge' for b (binary False symlink False changedelete False) (glob) merging b my b@0b76e65c8289+ other b@4ce40f5aca24 ancestor a@924404dff337 premerge successful - rev: versions differ -> m (premerge) + rev: versions differ -> m picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) merging rev my rev@0b76e65c8289+ other rev@4ce40f5aca24 ancestor rev@924404dff337 - rev: versions differ -> m (merge) - picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) - my rev@0b76e65c8289+ other rev@4ce40f5aca24 ancestor rev@924404dff337 launching merge tool: * ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 0 files updated, 2 files merged, 0 files removed, 0 files unresolved @@ -688,18 +628,15 @@ m "um a c" "um x c" " " "10 do merg starting 4 threads for background file closing (?) preserving b for resolve of b preserving rev for resolve of rev - b: both renamed from a -> m (premerge) + b: both renamed from a -> m picked tool '* ../merge' for b (binary False symlink False changedelete False) (glob) merging b my b@02963e448370+ other b@8dbce441892a ancestor a@924404dff337 premerge successful - rev: versions differ -> m (premerge) + rev: versions differ -> m picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) merging rev my rev@02963e448370+ other rev@8dbce441892a ancestor rev@924404dff337 - rev: versions differ -> m (merge) - picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) - my rev@02963e448370+ other rev@8dbce441892a ancestor rev@924404dff337 launching merge tool: * ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 0 files updated, 2 files merged, 0 files removed, 0 files unresolved @@ -723,18 +660,15 @@ m "um a c" "um x c" " " "10 do merg starting 4 threads for background file closing (?) preserving b for resolve of b preserving rev for resolve of rev - b: both renamed from a -> m (premerge) + b: both renamed from a -> m picked tool '* ../merge' for b (binary False symlink False changedelete False) (glob) merging b my b@0b76e65c8289+ other b@bdb19105162a ancestor a@924404dff337 premerge successful - rev: versions differ -> m (premerge) + rev: versions differ -> m picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) merging rev my rev@0b76e65c8289+ other rev@bdb19105162a ancestor rev@924404dff337 - rev: versions differ -> m (merge) - picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) - my rev@0b76e65c8289+ other rev@bdb19105162a ancestor rev@924404dff337 launching merge tool: * ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 0 files updated, 2 files merged, 0 files removed, 0 files unresolved @@ -762,22 +696,16 @@ m "um a c" "um x c" " " "10 do merg preserving a for resolve of b preserving rev for resolve of rev removing a - b: remote moved from a -> m (premerge) + b: remote moved from a -> m picked tool '* ../merge' for b (binary False symlink False changedelete False) (glob) merging a and b to b my b@e300d1c794ec+ other b@49b6d8032493 ancestor a@924404dff337 - rev: versions differ -> m (premerge) + launching merge tool: * ../merge *$TESTTMP/t/t/b* * * (glob) + merge tool returned: 0 + rev: versions differ -> m picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) merging rev my rev@e300d1c794ec+ other rev@49b6d8032493 ancestor rev@924404dff337 - b: remote moved from a -> m (merge) - picked tool '* ../merge' for b (binary False symlink False changedelete False) (glob) - my b@e300d1c794ec+ other b@49b6d8032493 ancestor a@924404dff337 - launching merge tool: * ../merge *$TESTTMP/t/t/b* * * (glob) - merge tool returned: 0 - rev: versions differ -> m (merge) - picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) - my rev@e300d1c794ec+ other rev@49b6d8032493 ancestor rev@924404dff337 launching merge tool: * ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 0 files updated, 2 files merged, 0 files removed, 0 files unresolved @@ -804,22 +732,16 @@ m "um a c" "um x c" " " "10 do merg starting 4 threads for background file closing (?) preserving b for resolve of b preserving rev for resolve of rev - b: local copied/moved from a -> m (premerge) + b: local copied/moved from a -> m picked tool '* ../merge' for b (binary False symlink False changedelete False) (glob) merging b and a to b my b@62e7bf090eba+ other a@f4db7e329e71 ancestor a@924404dff337 - rev: versions differ -> m (premerge) + launching merge tool: * ../merge *$TESTTMP/t/t/b* * * (glob) + merge tool returned: 0 + rev: versions differ -> m picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) merging rev my rev@62e7bf090eba+ other rev@f4db7e329e71 ancestor rev@924404dff337 - b: local copied/moved from a -> m (merge) - picked tool '* ../merge' for b (binary False symlink False changedelete False) (glob) - my b@62e7bf090eba+ other a@f4db7e329e71 ancestor a@924404dff337 - launching merge tool: * ../merge *$TESTTMP/t/t/b* * * (glob) - merge tool returned: 0 - rev: versions differ -> m (merge) - picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) - my rev@62e7bf090eba+ other rev@f4db7e329e71 ancestor rev@924404dff337 launching merge tool: * ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 0 files updated, 2 files merged, 0 files removed, 0 files unresolved @@ -852,18 +774,15 @@ m "nm a b" "um x a" " " "22 get a, getting c preserving b for resolve of b preserving rev for resolve of rev - b: local copied/moved from a -> m (premerge) + b: local copied/moved from a -> m picked tool '* ../merge' for b (binary False symlink False changedelete False) (glob) merging b and a to b my b@02963e448370+ other a@2b958612230f ancestor a@924404dff337 premerge successful - rev: versions differ -> m (premerge) + rev: versions differ -> m picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) merging rev my rev@02963e448370+ other rev@2b958612230f ancestor rev@924404dff337 - rev: versions differ -> m (merge) - picked tool '* ../merge' for rev (binary False symlink False changedelete False) (glob) - my rev@02963e448370+ other rev@2b958612230f ancestor rev@924404dff337 launching merge tool: * ../merge *$TESTTMP/t/t/rev* * * (glob) merge tool returned: 0 1 files updated, 2 files merged, 0 files removed, 0 files unresolved diff --git a/tests/test-rename.t b/tests/test-rename.t --- a/tests/test-rename.t +++ b/tests/test-rename.t @@ -610,7 +610,7 @@ check illegal path components $ hg rename d1/d11/a1 .hg/foo abort: path contains illegal component: .hg/foo - [255] + [10] $ hg status -C $ hg rename d1/d11/a1 ../foo abort: ../foo not under root '$TESTTMP' @@ -620,7 +620,7 @@ check illegal path components $ mv d1/d11/a1 .hg/foo $ hg rename --after d1/d11/a1 .hg/foo abort: path contains illegal component: .hg/foo - [255] + [10] $ hg status -C ! d1/d11/a1 $ hg update -C @@ -629,11 +629,11 @@ check illegal path components $ hg rename d1/d11/a1 .hg abort: path contains illegal component: .hg/a1 - [255] + [10] $ hg --config extensions.largefiles= rename d1/d11/a1 .hg The fsmonitor extension is incompatible with the largefiles extension and has been disabled. (fsmonitor !) abort: path contains illegal component: .hg/a1 - [255] + [10] $ hg status -C $ hg rename d1/d11/a1 .. abort: ../a1 not under root '$TESTTMP' @@ -647,7 +647,7 @@ check illegal path components $ mv d1/d11/a1 .hg $ hg rename --after d1/d11/a1 .hg abort: path contains illegal component: .hg/a1 - [255] + [10] $ hg status -C ! d1/d11/a1 $ hg update -C @@ -656,7 +656,7 @@ check illegal path components $ (cd d1/d11; hg rename ../../d2/b ../../.hg/foo) abort: path contains illegal component: .hg/foo - [255] + [10] $ hg status -C $ (cd d1/d11; hg rename ../../d2/b ../../../foo) abort: ../../../foo not under root '$TESTTMP' diff --git a/tests/test-repo-compengines.t b/tests/test-repo-compengines.t --- a/tests/test-repo-compengines.t +++ b/tests/test-repo-compengines.t @@ -9,16 +9,8 @@ A new repository uses zlib storage, whic $ hg init default $ cd default - $ cat .hg/requires - dotencode - dirstate-v2 (dirstate-v2 !) - fncache - generaldelta - persistent-nodemap (rust !) - revlogv1 - sparserevlog - store - testonly-simplestore (reposimplestore !) + $ hg debugrequires | grep compression + [1] $ touch foo $ hg -q commit -A -m 'initial commit with a lot of repeated repeated repeated text to trigger compression' @@ -59,16 +51,8 @@ with that engine or a requirement $ touch bar $ hg --config format.revlog-compression=none -q commit -A -m 'add bar with a lot of repeated repeated repeated text' - $ cat .hg/requires - dotencode - dirstate-v2 (dirstate-v2 !) - fncache - generaldelta - persistent-nodemap (rust !) - revlogv1 - sparserevlog - store - testonly-simplestore (reposimplestore !) + $ hg debugrequires | grep compression + [1] $ hg debugrevlog -c | grep 0x78 0x78 (x) : 2 (100.00%) @@ -79,17 +63,8 @@ with that engine or a requirement $ hg --config format.revlog-compression=zstd init zstd $ cd zstd - $ cat .hg/requires - dotencode - dirstate-v2 (dirstate-v2 !) - fncache - generaldelta - persistent-nodemap (rust !) + $ hg debugrequires | grep compression revlog-compression-zstd - revlogv1 - sparserevlog - store - testonly-simplestore (reposimplestore !) $ touch foo $ hg -q commit -A -m 'initial commit with a lot of repeated repeated repeated text' @@ -183,17 +158,8 @@ checking details of none compression summary: some-commit - $ cat none-compression/.hg/requires - dotencode + $ hg debugrequires -R none-compression/ | grep compression exp-compression-none - dirstate-v2 (dirstate-v2 !) - fncache - generaldelta - persistent-nodemap (rust !) - revlogv1 - sparserevlog - store - testonly-simplestore (reposimplestore !) $ $RUNTESTDIR/f -s none-compression/.hg/store/data/* none-compression/.hg/store/data/a.i: size=4216 diff --git a/tests/test-requires.t b/tests/test-requires.t --- a/tests/test-requires.t +++ b/tests/test-requires.t @@ -57,6 +57,7 @@ another repository of push/pull/clone on persistent-nodemap (rust !) revlog-compression-zstd (zstd !) revlogv1 + share-safe sparserevlog store $ hg -R supported status diff --git a/tests/test-resolve.t b/tests/test-resolve.t --- a/tests/test-resolve.t +++ b/tests/test-resolve.t @@ -196,8 +196,8 @@ resolve without arguments should suggest resolve --all should re-merge all unresolved files $ hg resolve --all merging file1 + warning: conflicts while merging file1! (edit, then use 'hg resolve --mark') merging file2 - warning: conflicts while merging file1! (edit, then use 'hg resolve --mark') warning: conflicts while merging file2! (edit, then use 'hg resolve --mark') [1] $ cat file1.orig @@ -211,8 +211,8 @@ resolve --all should re-merge all unreso $ hg resolve --all --verbose --config 'ui.origbackuppath=.hg/origbackups' merging file1 creating directory: $TESTTMP/repo/.hg/origbackups + warning: conflicts while merging file1! (edit, then use 'hg resolve --mark') merging file2 - warning: conflicts while merging file1! (edit, then use 'hg resolve --mark') warning: conflicts while merging file2! (edit, then use 'hg resolve --mark') [1] $ ls .hg/origbackups @@ -478,10 +478,10 @@ Test 'hg resolve' confirm config option $ hg rebase -s 1 -d 2 rebasing 1:f30f98a8181f "added emp1 emp2 emp3" merging emp1 + warning: conflicts while merging emp1! (edit, then use 'hg resolve --mark') merging emp2 + warning: conflicts while merging emp2! (edit, then use 'hg resolve --mark') merging emp3 - warning: conflicts while merging emp1! (edit, then use 'hg resolve --mark') - warning: conflicts while merging emp2! (edit, then use 'hg resolve --mark') warning: conflicts while merging emp3! (edit, then use 'hg resolve --mark') unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') [240] @@ -490,10 +490,10 @@ Test when commands.resolve.confirm confi =========================================================== $ hg resolve --all merging emp1 + warning: conflicts while merging emp1! (edit, then use 'hg resolve --mark') merging emp2 + warning: conflicts while merging emp2! (edit, then use 'hg resolve --mark') merging emp3 - warning: conflicts while merging emp1! (edit, then use 'hg resolve --mark') - warning: conflicts while merging emp2! (edit, then use 'hg resolve --mark') warning: conflicts while merging emp3! (edit, then use 'hg resolve --mark') [1] @@ -522,10 +522,10 @@ Test when config option is set: > EOF re-merge all unresolved files (yn)? y merging emp1 + warning: conflicts while merging emp1! (edit, then use 'hg resolve --mark') merging emp2 + warning: conflicts while merging emp2! (edit, then use 'hg resolve --mark') merging emp3 - warning: conflicts while merging emp1! (edit, then use 'hg resolve --mark') - warning: conflicts while merging emp2! (edit, then use 'hg resolve --mark') warning: conflicts while merging emp3! (edit, then use 'hg resolve --mark') [1] diff --git a/tests/test-revert.t b/tests/test-revert.t --- a/tests/test-revert.t +++ b/tests/test-revert.t @@ -320,7 +320,7 @@ Also true for move overwriting an existi $ hg mv --force a b/b $ hg revert b/b - $ hg status a b/b + $ hg status a b/b --copies $ cd .. diff --git a/tests/test-revlog-raw.py b/tests/test-revlog-raw.py --- a/tests/test-revlog-raw.py +++ b/tests/test-revlog-raw.py @@ -325,7 +325,7 @@ def writecases(rlog, tr): rawtext = text if rlog.rawsize(rev) != len(rawtext): abort('rev %d: wrong rawsize' % rev) - if rlog.revision(rev, raw=False) != text: + if rlog.revision(rev) != text: abort('rev %d: wrong text' % rev) if rlog.rawdata(rev) != rawtext: abort('rev %d: wrong rawtext' % rev) diff --git a/tests/test-revlog-v2.t b/tests/test-revlog-v2.t --- a/tests/test-revlog-v2.t +++ b/tests/test-revlog-v2.t @@ -20,16 +20,9 @@ Can create and open repo with revlog v2 $ hg init new-repo $ cd new-repo - $ cat .hg/requires - dotencode - dirstate-v2 (dirstate-v2 !) + $ hg debugrequires | grep revlogv2 exp-revlogv2.2 - fncache - generaldelta - persistent-nodemap (rust !) - revlog-compression-zstd (zstd !) - sparserevlog - store + dirstate-v2 (dirstate-v2 !) $ hg log diff --git a/tests/test-revset.t b/tests/test-revset.t --- a/tests/test-revset.t +++ b/tests/test-revset.t @@ -306,7 +306,7 @@ names that should be caught by fallback (negate (symbol 'a'))) abort: unknown revision '-a' - [255] + [10] $ try é (symbol '\xc3\xa9') * set: diff --git a/tests/test-revset2.t b/tests/test-revset2.t --- a/tests/test-revset2.t +++ b/tests/test-revset2.t @@ -870,7 +870,7 @@ aliases: $ try m (symbol 'm') abort: unknown revision 'm' - [255] + [10] $ HGPLAINEXCEPT=revsetalias $ export HGPLAINEXCEPT @@ -1061,7 +1061,7 @@ far away. (symbol 'max') (string '$1'))) abort: unknown revision '$1' - [255] + [10] test scope of alias expansion: 'universe' is expanded prior to 'shadowall(0)', but 'all()' should never be substituted to '0()'. @@ -1601,7 +1601,7 @@ loading it > EOF $ hg debugrevspec "custom1()" - *** failed to import extension custompredicate from $TESTTMP/custompredicate.py: intentional failure of loading extension + *** failed to import extension "custompredicate" from $TESTTMP/custompredicate.py: intentional failure of loading extension hg: parse error: unknown identifier: custom1 [10] diff --git a/tests/test-rhg-no-generaldelta.t b/tests/test-rhg-no-generaldelta.t new file mode 100644 --- /dev/null +++ b/tests/test-rhg-no-generaldelta.t @@ -0,0 +1,46 @@ + $ NO_FALLBACK="env RHG_ON_UNSUPPORTED=abort" + + $ cat << EOF >> $HGRCPATH + > [format] + > sparse-revlog = no + > EOF + + $ hg init repo --config format.generaldelta=no --config format.usegeneraldelta=no + $ cd repo + $ (echo header; seq.py 20) > f + $ hg commit -q -Am initial + $ (echo header; seq.py 20; echo footer) > f + $ hg commit -q -Am x + $ hg update ".^" + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ (seq.py 20; echo footer) > f + $ hg commit -q -Am y + $ hg debugdeltachain f --template '{rev} {prevrev} {deltatype}\n' + 0 -1 base + 1 0 prev + 2 1 prev + +rhg works on non-generaldelta revlogs: + + $ $NO_FALLBACK hg cat f -r . + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + footer diff --git a/tests/test-rhg-sparse-narrow.t b/tests/test-rhg-sparse-narrow.t new file mode 100644 --- /dev/null +++ b/tests/test-rhg-sparse-narrow.t @@ -0,0 +1,120 @@ +#require rhg + + $ NO_FALLBACK="env RHG_ON_UNSUPPORTED=abort" + +Rhg works well when sparse working copy is enabled. + + $ cd "$TESTTMP" + $ hg init repo-sparse + $ cd repo-sparse + $ cat > .hg/hgrc < [extensions] + > sparse= + > EOF + + $ echo a > show + $ echo x > hide + $ mkdir dir1 dir2 + $ echo x > dir1/x + $ echo y > dir1/y + $ echo z > dir2/z + + $ hg ci -Aqm 'initial' + $ hg debugsparse --include 'show' + $ ls -A + .hg + show + + $ tip=$(hg log -r . --template '{node}') + $ $NO_FALLBACK rhg files -r "$tip" + dir1/x + dir1/y + dir2/z + hide + show + $ $NO_FALLBACK rhg files + show + + $ $NO_FALLBACK rhg cat -r "$tip" hide + x + + $ cd .. + +We support most things when narrow is enabled, too, with a couple of caveats. + + $ . "$TESTDIR/narrow-library.sh" + $ real_hg=$RHG_FALLBACK_EXECUTABLE + + $ cat >> $HGRCPATH < [extensions] + > narrow= + > EOF + + $ hg clone --narrow ./repo-sparse repo-narrow --include dir1 + requesting all changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 2 changes to 2 files + new changesets 6d714a4a2998 + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + + $ cd repo-narrow + + $ $NO_FALLBACK rhg cat -r "$tip" dir1/x + x + $ "$real_hg" cat -r "$tip" dir1/x + x + +TODO: bad error message + + $ $NO_FALLBACK rhg cat -r "$tip" hide + abort: invalid revision identifier: 6d714a4a2998cbfd0620db44da58b749f6565d63 + [255] + $ "$real_hg" cat -r "$tip" hide + [1] + +A naive implementation of [rhg files] leaks the paths that are supposed to be +hidden by narrow, so we just fall back to hg. + + $ $NO_FALLBACK rhg files -r "$tip" + unsupported feature: rhg files -r is not supported in narrow clones + [252] + $ "$real_hg" files -r "$tip" + dir1/x + dir1/y + +Hg status needs to do some filtering based on narrow spec, so we don't +support it in rhg for narrow clones yet. + + $ mkdir dir2 + $ touch dir2/q + $ "$real_hg" status + $ $NO_FALLBACK rhg --config rhg.status=true status + unsupported feature: rhg status is not supported for sparse checkouts or narrow clones yet + [252] + +Adding "orphaned" index files: + + $ (cd ..; cp repo-sparse/.hg/store/data/hide.i repo-narrow/.hg/store/data/hide.i) + $ (cd ..; mkdir repo-narrow/.hg/store/data/dir2; cp repo-sparse/.hg/store/data/dir2/z.i repo-narrow/.hg/store/data/dir2/z.i) + $ "$real_hg" verify + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + checked 1 changesets with 2 changes to 2 files + + $ "$real_hg" files -r "$tip" + dir1/x + dir1/y + +# TODO: even though [hg files] hides the orphaned dir2/z, [hg cat] still shows it. +# rhg has the same issue, but at least it's not specific to rhg. +# This is despite [hg verify] succeeding above. + + $ $NO_FALLBACK rhg cat -r "$tip" dir2/z + z + $ "$real_hg" cat -r "$tip" dir2/z + z diff --git a/tests/test-rhg.t b/tests/test-rhg.t --- a/tests/test-rhg.t +++ b/tests/test-rhg.t @@ -168,13 +168,12 @@ Fallback to Python $ rhg cat original --exclude="*.rs" original content - $ FALLBACK_EXE="$RHG_FALLBACK_EXECUTABLE" - $ unset RHG_FALLBACK_EXECUTABLE - $ rhg cat original --exclude="*.rs" + $ (unset RHG_FALLBACK_EXECUTABLE; rhg cat original --exclude="*.rs") abort: 'rhg.on-unsupported=fallback' without 'rhg.fallback-executable' set. [255] - $ RHG_FALLBACK_EXECUTABLE="$FALLBACK_EXE" - $ export RHG_FALLBACK_EXECUTABLE + + $ (unset RHG_FALLBACK_EXECUTABLE; rhg cat original) + original content $ rhg cat original --exclude="*.rs" --config rhg.fallback-executable=false [1] @@ -244,6 +243,7 @@ Requirements persistent-nodemap revlog-compression-zstd (zstd !) revlogv1 + share-safe sparserevlog store @@ -368,9 +368,9 @@ The blackbox extension is supported $ echo "maxsize = 1" >> $HGRCPATH $ $NO_FALLBACK rhg files > /dev/null $ cat .hg/blackbox.log - ????/??/?? ??:??:??.??? * @d3873e73d99ef67873dac33fbcc66268d5d2b6f4 (*)> (rust) files exited 0 after 0.??? seconds (glob) + ????-??-?? ??:??:??.??? * @d3873e73d99ef67873dac33fbcc66268d5d2b6f4 (*)> (rust) files exited 0 after 0.??? seconds (glob) $ cat .hg/blackbox.log.1 - ????/??/?? ??:??:??.??? * @d3873e73d99ef67873dac33fbcc66268d5d2b6f4 (*)> (rust) files (glob) + ????-??-?? ??:??:??.??? * @d3873e73d99ef67873dac33fbcc66268d5d2b6f4 (*)> (rust) files (glob) Subrepos are not supported @@ -381,3 +381,13 @@ Subrepos are not supported $ rhg files a $ rm .hgsub + +The `:required` extension suboptions are correctly ignored + + $ echo "[extensions]" >> $HGRCPATH + $ echo "blackbox:required = yes" >> $HGRCPATH + $ rhg files + a + $ echo "*:required = yes" >> $HGRCPATH + $ rhg files + a diff --git a/tests/test-run-tests.t b/tests/test-run-tests.t --- a/tests/test-run-tests.t +++ b/tests/test-run-tests.t @@ -21,7 +21,7 @@ Define a helper to avoid the install ste error paths #if symlink - $ ln -s `which true` hg + $ ln -s "/"bin"/"true hg $ "$PYTHON" $TESTDIR/run-tests.py --with-hg=./hg ^warning: --with-hg should specify an hg script, not: (true|coreutils)$ (re) running 0 tests using 0 parallel processes @@ -176,14 +176,19 @@ test diff colorisation running 1 tests using 1 parallel processes \x1b[38;5;124m--- $TESTTMP/test-failure.t\x1b[39m (esc) - \x1b[38;5;34m+++ $TESTTMP/test-failure.t.err\x1b[39m (esc) + \x1b[38;5;28m+++ $TESTTMP/test-failure.t.err\x1b[39m (esc) (pygments211 !) + \x1b[38;5;34m+++ $TESTTMP/test-failure.t.err\x1b[39m (esc) (no-pygments211 !) \x1b[38;5;90;01m@@ -1,4 +1,4 @@\x1b[39;00m (esc) - $ echo "bar-baz"; echo "bar-bad"; echo foo - \x1b[38;5;34m+ bar*baz (glob)\x1b[39m (esc) - bar*bad (glob) + \x1b[38;5;250m \x1b[39m $ echo "bar-baz"; echo "bar-bad"; echo foo (esc) (pygments211 !) + $ echo "bar-baz"; echo "bar-bad"; echo foo (no-pygments211 !) + \x1b[38;5;28m+ bar*baz (glob)\x1b[39m (esc) (pygments211 !) + \x1b[38;5;34m+ bar*baz (glob)\x1b[39m (esc) (no-pygments211 !) + \x1b[38;5;250m \x1b[39m bar*bad (glob) (esc) (pygments211 !) + bar*bad (glob) (no-pygments211 !) \x1b[38;5;124m- bar*baz (glob)\x1b[39m (esc) \x1b[38;5;124m- | fo (re)\x1b[39m (esc) - \x1b[38;5;34m+ foo\x1b[39m (esc) + \x1b[38;5;28m+ foo\x1b[39m (esc) (pygments211 !) + \x1b[38;5;34m+ foo\x1b[39m (esc) (no-pygments211 !) \x1b[38;5;88mERROR: \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m output changed\x1b[39m (esc) ! diff --git a/tests/test-share-safe.t b/tests/test-share-safe.t --- a/tests/test-share-safe.t +++ b/tests/test-share-safe.t @@ -244,7 +244,8 @@ Disable zstd related tests because its n $ echo "use-persistent-nodemap=True" >> .hg/hgrc $ hg debugupgraderepo --run -q -R ../shared1 - abort: cannot upgrade repository; unsupported source requirement: shared + abort: cannot use these actions on a share repository: persistent-nodemap + (upgrade the main repository directly) [255] $ hg debugupgraderepo --run -q @@ -363,10 +364,7 @@ Upgrade preserved: dotencode, use-dirstate-v2, fncache, generaldelta, revlogv1, sparserevlog, store (dirstate-v2 !) added: share-safe - processed revlogs: - - all-filelogs - - changelog - - manifest + no revlogs to process $ hg debugupgraderepo --run upgrade will perform the following actions: @@ -379,10 +377,7 @@ Upgrade 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 + no revlogs to process beginning upgrade... repository locked and read-only @@ -457,10 +452,7 @@ Test that downgrading works too preserved: dotencode, use-dirstate-v2, fncache, generaldelta, revlogv1, sparserevlog, store (dirstate-v2 !) removed: share-safe - processed revlogs: - - all-filelogs - - changelog - - manifest + no revlogs to process $ hg debugupgraderepo --run upgrade will perform the following actions: @@ -470,10 +462,7 @@ Test that downgrading works too preserved: dotencode, use-dirstate-v2, fncache, generaldelta, revlogv1, sparserevlog, store (dirstate-v2 !) removed: share-safe - processed revlogs: - - all-filelogs - - changelog - - manifest + no revlogs to process beginning upgrade... repository locked and read-only @@ -556,10 +545,7 @@ Testing automatic upgrade of shares when preserved: dotencode, use-dirstate-v2, fncache, generaldelta, revlogv1, sparserevlog, store (dirstate-v2 !) added: share-safe - processed revlogs: - - all-filelogs - - changelog - - manifest + no revlogs to process 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 diff --git a/tests/test-share.t b/tests/test-share.t --- a/tests/test-share.t +++ b/tests/test-share.t @@ -161,7 +161,7 @@ Cloning a shared repo via bundle2 result $ cd .. $ hg clone -q --stream ssh://user@dummy/`pwd`/repo2 cloned-via-bundle2 - $ cat ./cloned-via-bundle2/.hg/requires | grep "shared" + $ hg -R cloned-via-bundle2 debugrequires | grep "shared" [1] $ hg id --cwd cloned-via-bundle2 -r tip c2e0ac586386 tip @@ -173,7 +173,7 @@ test unshare command $ test -d .hg/store $ test -f .hg/sharedpath [1] - $ grep shared .hg/requires + $ hg debugrequires | grep shared [1] $ hg unshare abort: this is not a shared repo @@ -208,10 +208,11 @@ test shared clones using relative paths $ hg share -U --relative thisdir/abs thisdir/rel $ cat thisdir/rel/.hg/sharedpath ../../orig/.hg (no-eol) - $ grep shared thisdir/*/.hg/requires - thisdir/abs/.hg/requires:shared - thisdir/rel/.hg/requires:relshared - thisdir/rel/.hg/requires:shared + $ hg debugrequires -R thisdir/abs/ | grep shared + shared + $ hg debugrequires -R thisdir/rel/ | grep shared + relshared + shared test that relative shared paths aren't relative to $PWD @@ -241,7 +242,7 @@ test unshare relshared repo $ test -d .hg/store $ test -f .hg/sharedpath [1] - $ grep shared .hg/requires + $ hg debugrequires | grep shared [1] $ hg unshare abort: this is not a shared repo @@ -284,3 +285,25 @@ Test sharing a repository which was crea $ hg share nostore sharednostore abort: cannot create shared repository as source was created with 'format.usestore' config disabled [255] + +Check that (safe) share can control wc-specific format variant at creation time +------------------------------------------------------------------------------- + +#if no-rust + + $ cat << EOF >> $HGRCPATH + > [storage] + > dirstate-v2.slow-path = allow + > EOF + +#endif + + $ hg init repo-safe-d1 --config format.use-share-safe=yes --config format.exp-rc-dirstate-v2=no + $ hg debugformat -R repo-safe-d1 | grep dirstate-v2 + dirstate-v2: no + + $ hg share repo-safe-d1 share-safe-d2 --config format.use-share-safe=yes --config format.exp-rc-dirstate-v2=yes + updating working directory + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg debugformat -R share-safe-d2 | grep dirstate-v2 + dirstate-v2: yes diff --git a/tests/test-shelve.t b/tests/test-shelve.t --- a/tests/test-shelve.t +++ b/tests/test-shelve.t @@ -419,11 +419,11 @@ ensure that we have a merge with unresol +++ b/a/a @@ -1,2 +1,6 @@ a - +<<<<<<< working-copy: 2377350b6337 - shelve: pending changes temporary commit + +<<<<<<< working-copy: 2377350b6337 - shelve: pending changes temporary commit c +======= +a - +>>>>>>> shelve: 203c9f771d2b - shelve: changes to: [mq]: second.patch + +>>>>>>> shelved change: 203c9f771d2b - shelve: changes to: [mq]: second.patch diff --git a/b/b b/b.rename/b rename from b/b rename to b.rename/b @@ -1385,8 +1385,8 @@ Abort unshelve while merging (issue5123) unshelving change 'default-01' rebasing shelved changes merging bar1 + warning: conflicts while merging bar1! (edit, then use 'hg resolve --mark') merging bar2 - warning: conflicts while merging bar1! (edit, then use 'hg resolve --mark') warning: conflicts while merging bar2! (edit, then use 'hg resolve --mark') unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue') [240] diff --git a/tests/test-shelve2.t b/tests/test-shelve2.t --- a/tests/test-shelve2.t +++ b/tests/test-shelve2.t @@ -253,11 +253,11 @@ unshelve and conflicts with tracked and M f ? f.orig $ cat f - <<<<<<< working-copy: d44eae5c3d33 - shelve: pending changes temporary commit + <<<<<<< working-copy: d44eae5c3d33 - shelve: pending changes temporary commit g ======= f - >>>>>>> shelve: aef214a5229c - shelve: changes to: commit stuff + >>>>>>> shelved change: aef214a5229c - shelve: changes to: commit stuff $ cat f.orig g $ hg unshelve --abort -t false @@ -295,11 +295,11 @@ unshelve and conflicts with tracked and M f ? f.orig $ cat f - <<<<<<< working-copy: 6b563750f973 - test: intermediate other change + <<<<<<< working-copy: 6b563750f973 - test: intermediate other change g ======= f - >>>>>>> shelve: aef214a5229c - shelve: changes to: commit stuff + >>>>>>> shelved change: aef214a5229c - shelve: changes to: commit stuff $ cat f.orig g @@ -986,9 +986,9 @@ Demonstrate that the labels are correct [240] $ cat foo r0 - <<<<<<< working-copy: 0b2fcf2a90e9 - shelve: pending changes temporary commit + <<<<<<< working-copy: 0b2fcf2a90e9 - shelve: pending changes temporary commit this is in wdir, conflicts with shelve ======= this will be shelved - >>>>>>> shelve: 9c072a2163db - shelve: changes to: r0 + >>>>>>> shelved change: 9c072a2163db - shelve: changes to: r0 $ cd .. diff --git a/tests/test-sidedata.t b/tests/test-sidedata.t --- a/tests/test-sidedata.t +++ b/tests/test-sidedata.t @@ -49,78 +49,22 @@ Check that we can upgrade to sidedata ------------------------------------- $ hg init up-no-side-data --config experimental.revlogv2=no - $ hg debugformat -v -R up-no-side-data - format-variant repo config default - fncache: yes yes yes - dirstate-v2: no no no - 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 no no + $ hg debugformat -v -R up-no-side-data | egrep 'changelog-v2|revlog-v2' revlog-v2: no no no changelog-v2: no no no - plain-cl-delta: yes yes yes - 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 experimental.revlogv2=enable-unstable-format-and-corrupt-my-data - format-variant repo config default - fncache: yes yes yes - dirstate-v2: no no no - 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 no no + $ hg debugformat -v -R up-no-side-data --config experimental.revlogv2=enable-unstable-format-and-corrupt-my-data | egrep 'changelog-v2|revlog-v2' revlog-v2: no yes no changelog-v2: no no no - plain-cl-delta: yes yes yes - compression: zlib zlib zlib (no-zstd !) - compression: zstd zstd zstd (zstd !) - compression-level: default default default $ hg debugupgraderepo -R up-no-side-data --config experimental.revlogv2=enable-unstable-format-and-corrupt-my-data > /dev/null Check that we can downgrade from sidedata ----------------------------------------- $ hg init up-side-data --config experimental.revlogv2=enable-unstable-format-and-corrupt-my-data - $ hg debugformat -v -R up-side-data - format-variant repo config default - fncache: yes yes yes - dirstate-v2: no no no - 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 no no + $ hg debugformat -v -R up-side-data | egrep 'changelog-v2|revlog-v2' revlog-v2: yes no no changelog-v2: no no no - plain-cl-delta: yes yes yes - compression: zlib zlib zlib (no-zstd !) - compression: zstd zstd zstd (zstd !) - compression-level: default default default - $ hg debugformat -v -R up-side-data --config experimental.revlogv2=no - format-variant repo config default - fncache: yes yes yes - dirstate-v2: no no no - 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 no no + $ hg debugformat -v -R up-side-data --config experimental.revlogv2=no | egrep 'changelog-v2|revlog-v2' revlog-v2: yes no no changelog-v2: no no no - plain-cl-delta: yes yes yes - compression: zlib zlib zlib (no-zstd !) - compression: zstd zstd zstd (zstd !) - compression-level: default default default $ hg debugupgraderepo -R up-side-data --config experimental.revlogv2=no > /dev/null diff --git a/tests/test-simplemerge.py b/tests/test-simplemerge.py --- a/tests/test-simplemerge.py +++ b/tests/test-simplemerge.py @@ -48,9 +48,6 @@ class Merge3(simplemerge.Merge3Text): ) -CantReprocessAndShowBase = simplemerge.CantReprocessAndShowBase - - def split_lines(t): return util.stringio(t).readlines() @@ -179,7 +176,9 @@ class TestMerge3(TestCase): self.assertEqual(list(m3.merge_regions()), [(b'a', 0, 2)]) - self.assertEqual(list(m3.merge_lines()), [b'aaa', b'bbb']) + self.assertEqual( + simplemerge.render_minimized(m3), ([b'aaa', b'bbb'], False) + ) def test_no_conflicts(self): """No conflicts because only one side changed""" @@ -204,7 +203,9 @@ class TestMerge3(TestCase): [b'aaa\n', b'bbb\n'], ) - self.assertEqual(b''.join(m3.merge_lines()), b'aaa\nbbb\n222\n') + self.assertEqual( + b''.join(simplemerge.render_minimized(m3)[0]), b'aaa\nbbb\n222\n' + ) def test_append_b(self): m3 = Merge3( @@ -213,7 +214,9 @@ class TestMerge3(TestCase): [b'aaa\n', b'bbb\n', b'222\n'], ) - self.assertEqual(b''.join(m3.merge_lines()), b'aaa\nbbb\n222\n') + self.assertEqual( + b''.join(simplemerge.render_minimized(m3)[0]), b'aaa\nbbb\n222\n' + ) def test_append_agreement(self): m3 = Merge3( @@ -222,7 +225,9 @@ class TestMerge3(TestCase): [b'aaa\n', b'bbb\n', b'222\n'], ) - self.assertEqual(b''.join(m3.merge_lines()), b'aaa\nbbb\n222\n') + self.assertEqual( + b''.join(simplemerge.render_minimized(m3)[0]), b'aaa\nbbb\n222\n' + ) def test_append_clash(self): m3 = Merge3( @@ -231,7 +236,8 @@ class TestMerge3(TestCase): [b'aaa\n', b'bbb\n', b'333\n'], ) - ml = m3.merge_lines( + ml, conflicts = simplemerge.render_minimized( + m3, name_a=b'a', name_b=b'b', start_marker=b'<<', @@ -250,7 +256,8 @@ class TestMerge3(TestCase): [b'aaa\n', b'222\n', b'bbb\n'], ) - ml = m3.merge_lines( + ml, conflicts = simplemerge.render_minimized( + m3, name_a=b'a', name_b=b'b', start_marker=b'<<', @@ -285,12 +292,13 @@ class TestMerge3(TestCase): list(m3.merge_groups()), [ (b'unchanged', [b'aaa\n']), - (b'conflict', [], [b'111\n'], [b'222\n']), + (b'conflict', ([], [b'111\n'], [b'222\n'])), (b'unchanged', [b'bbb\n']), ], ) - ml = m3.merge_lines( + ml, conflicts = simplemerge.render_minimized( + m3, name_a=b'a', name_b=b'b', start_marker=b'<<', @@ -338,7 +346,7 @@ bbb def test_merge_poem(self): """Test case from diff3 manual""" m3 = Merge3(TZU, LAO, TAO) - ml = list(m3.merge_lines(b'LAO', b'TAO')) + ml, conflicts = simplemerge.render_minimized(m3, b'LAO', b'TAO') self.log(b'merge result:') self.log(b''.join(ml)) self.assertEqual(ml, MERGED_RESULT) @@ -356,11 +364,11 @@ bbb other_text.splitlines(True), this_text.splitlines(True), ) - m_lines = m3.merge_lines(b'OTHER', b'THIS') + m_lines, conflicts = simplemerge.render_minimized(m3, b'OTHER', b'THIS') self.assertEqual( b'<<<<<<< OTHER\r\nc\r\n=======\r\nb\r\n' b'>>>>>>> THIS\r\n'.splitlines(True), - list(m_lines), + m_lines, ) def test_mac_text(self): @@ -372,11 +380,11 @@ bbb other_text.splitlines(True), this_text.splitlines(True), ) - m_lines = m3.merge_lines(b'OTHER', b'THIS') + m_lines, conflicts = simplemerge.render_minimized(m3, b'OTHER', b'THIS') self.assertEqual( b'<<<<<<< OTHER\rc\r=======\rb\r' b'>>>>>>> THIS\r'.splitlines(True), - list(m_lines), + m_lines, ) diff --git a/tests/test-single-head-obsolescence-named-branch-A1.t b/tests/test-single-head-obsolescence-named-branch-A1.t new file mode 100644 --- /dev/null +++ b/tests/test-single-head-obsolescence-named-branch-A1.t @@ -0,0 +1,114 @@ +========================================= +Testing single head enforcement: Case A-1 +========================================= + +A repository is set to only accept a single head per name (typically named +branch). However, obsolete changesets can make this enforcement more +complicated, because they can be kept visible by other changeset on other +branch. + +This case is part of a series of tests checking this behavior. + +Category A: Involving obsolescence +TestCase 1: A fully obsolete branch kept visible by another one + +.. old-state: +.. +.. * 2 changesets on branch default +.. * 2 changesets on branch Z on top of them +.. +.. new-state: +.. +.. * 2 changesets on branch Z at the same location +.. * 2 changesets on branch default superseding the other ones +.. +.. expected-result: +.. +.. * only one head detected +.. +.. graph-summary: +.. +.. D ● (branch Z) +.. | +.. C ● (branch Z) +.. | +.. B ø⇠◔ B' +.. | | +.. A ø⇠◔ A' +.. |/ +.. ● + + $ . $TESTDIR/testlib/push-checkheads-util.sh + + $ cat >> $HGRCPATH << EOF + > [command-templates] + > log = "{node|short} [{branch}] ({phase}): {desc}\n" + > EOF + +Test setup +---------- + + $ mkdir A1 + $ cd A1 + $ setuprepos single-head + creating basic server and client repo + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd client + $ mkcommit B0 + $ hg branch Z + marked working directory as branch Z + (branches are permanent and global, did you want a bookmark?) + $ mkcommit C0 + $ mkcommit D0 + $ hg push --new-branch + pushing to $TESTTMP/A1/server + searching for changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 3 changes to 3 files + $ hg up 0 + 0 files updated, 0 files merged, 4 files removed, 0 files unresolved + $ mkcommit A1 + created new head + $ mkcommit B1 + $ hg debugobsolete `getid "desc(A0)"` `getid "desc(A1)"` + 1 new obsolescence markers + obsoleted 1 changesets + 3 new orphan changesets + $ hg debugobsolete `getid "desc(B0)"` `getid "desc(B1)"` + 1 new obsolescence markers + obsoleted 1 changesets + $ hg heads + 262c8c798096 [default] (draft): B1 + cdf1dbb37a67 [Z] (draft): D0 + $ hg log -G --hidden + @ 262c8c798096 [default] (draft): B1 + | + o f6082bc4ffef [default] (draft): A1 + | + | * cdf1dbb37a67 [Z] (draft): D0 + | | + | * 3213e3e16c67 [Z] (draft): C0 + | | + | x d73caddc5533 [default] (draft): B0 + | | + | x 8aaa48160adc [default] (draft): A0 + |/ + o 1e4be0697311 [default] (public): root + + +Actual testing +-------------- + + $ hg push -r 'desc("B1")' + pushing to $TESTTMP/A1/server + searching for changes + adding changesets + adding manifests + adding file changes + added 2 changesets with 2 changes to 2 files (+1 heads) + 2 new obsolescence markers + obsoleted 2 changesets + 2 new orphan changesets diff --git a/tests/test-single-head-obsolescence-named-branch-A2.t b/tests/test-single-head-obsolescence-named-branch-A2.t new file mode 100644 --- /dev/null +++ b/tests/test-single-head-obsolescence-named-branch-A2.t @@ -0,0 +1,113 @@ +========================================= +Testing single head enforcement: Case A-2 +========================================= + +A repository is set to only accept a single head per name (typically named +branch). However, obsolete changesets can make this enforcement more +complicated, because they can be kept visible by other changeset on other +branch. + +This case is part of a series of tests checking this behavior. + +Category A: Involving obsolescence +TestCase 2: A branch is split in two, effectively creating two heads + +.. old-state: +.. +.. * 2 changesets on branch default +.. * 2 changesets on branch Z on top of them +.. +.. new-state: +.. +.. * 2 changesets on branch Z at the same location +.. * 1 changeset on branch default unchanged +.. * 1 changeset on branch default superseding the other ones +.. +.. expected-result: +.. +.. * two heads detected +.. +.. graph-summary: +.. +.. D ● (branch Z) +.. | +.. C ● (branch Z) +.. | +.. B ø⇠◔ B' +.. | | +.. A ● | +.. |/ +.. ● + + $ . $TESTDIR/testlib/push-checkheads-util.sh + + $ cat >> $HGRCPATH << EOF + > [command-templates] + > log = "{node|short} [{branch}] ({phase}): {desc}\n" + > EOF + +Test setup +---------- + + $ mkdir A2 + $ cd A2 + $ setuprepos single-head + creating basic server and client repo + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd client + $ mkcommit B0 + $ hg branch Z + marked working directory as branch Z + (branches are permanent and global, did you want a bookmark?) + $ mkcommit C0 + $ mkcommit D0 + $ hg push --new-branch + pushing to $TESTTMP/A2/server + searching for changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 3 changes to 3 files + $ hg up 0 + 0 files updated, 0 files merged, 4 files removed, 0 files unresolved + $ mkcommit B1 + created new head + $ hg debugobsolete `getid "desc(B0)"` `getid "desc(B1)"` + 1 new obsolescence markers + obsoleted 1 changesets + 2 new orphan changesets + $ hg heads + 25c56d33e4c4 [default] (draft): B1 + cdf1dbb37a67 [Z] (draft): D0 + 8aaa48160adc [default] (draft): A0 + $ hg log -G --hidden + @ 25c56d33e4c4 [default] (draft): B1 + | + | * cdf1dbb37a67 [Z] (draft): D0 + | | + | * 3213e3e16c67 [Z] (draft): C0 + | | + | x d73caddc5533 [default] (draft): B0 + | | + | o 8aaa48160adc [default] (draft): A0 + |/ + o 1e4be0697311 [default] (public): root + + +Actual testing +-------------- + +(force push to make sure we get the changeset on the remote) + + $ hg push -r 'desc("B1")' --force + pushing to $TESTTMP/A2/server + searching for changes + adding changesets + adding manifests + adding file changes + transaction abort! + rollback completed + abort: rejecting multiple heads on branch "default" + (2 heads: 8aaa48160adc 25c56d33e4c4) + [255] diff --git a/tests/test-single-head-obsolescence-named-branch-A3.t b/tests/test-single-head-obsolescence-named-branch-A3.t new file mode 100644 --- /dev/null +++ b/tests/test-single-head-obsolescence-named-branch-A3.t @@ -0,0 +1,120 @@ +========================================= +Testing single head enforcement: Case A-3 +========================================= + +A repository is set to only accept a single head per name (typically named +branch). However, obsolete changesets can make this enforcement more +complicated, because they can be kept visible by other changeset on other +branch. + +This case is part of a series of tests checking this behavior. + +Category A: Involving obsolescence +TestCase 3: Full superseding of a branch interleaved with another + +.. old-state: +.. +.. * 2 changesets on branch default +.. * 2 changesets on branch Z interleaved with the other +.. +.. new-state: +.. +.. * 2 changesets on branch Z at the same location +.. * 2 changesets on branch default superseding the other ones +.. +.. expected-result: +.. +.. * only one head detected +.. +.. graph-summary: +.. +.. D ● (branch Z) +.. | +.. C ø⇠◔ C' +.. | | +.. B ● | (branch Z) +.. | | +.. A ø⇠◔ A' +.. |/ +.. ● + + $ . $TESTDIR/testlib/push-checkheads-util.sh + + $ cat >> $HGRCPATH << EOF + > [command-templates] + > log = "{node|short} [{branch}] ({phase}): {desc}\n" + > EOF + +Test setup +---------- + + $ mkdir A3 + $ cd A3 + $ setuprepos single-head + creating basic server and client repo + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd client + $ hg branch Z + marked working directory as branch Z + (branches are permanent and global, did you want a bookmark?) + $ mkcommit B0 + $ hg branch default --force + marked working directory as branch default + $ mkcommit C0 + created new head + $ hg branch Z --force + marked working directory as branch Z + $ mkcommit D0 + created new head + $ hg push --new-branch + pushing to $TESTTMP/A3/server + searching for changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 3 changes to 3 files + $ hg up 0 + 0 files updated, 0 files merged, 4 files removed, 0 files unresolved + $ mkcommit A1 + created new head + $ mkcommit C1 + $ hg debugobsolete `getid "desc(A0)"` `getid "desc(A1)"` + 1 new obsolescence markers + obsoleted 1 changesets + 3 new orphan changesets + $ hg debugobsolete `getid "desc(C0)"` `getid "desc(C1)"` + 1 new obsolescence markers + obsoleted 1 changesets + $ hg heads + 0c76bc104656 [default] (draft): C1 + 78578c4306ce [Z] (draft): D0 + $ hg log -G --hidden + @ 0c76bc104656 [default] (draft): C1 + | + o f6082bc4ffef [default] (draft): A1 + | + | * 78578c4306ce [Z] (draft): D0 + | | + | x afc55ba2ce61 [default] (draft): C0 + | | + | * 93e5c1321ece [Z] (draft): B0 + | | + | x 8aaa48160adc [default] (draft): A0 + |/ + o 1e4be0697311 [default] (public): root + + +Actual testing +-------------- + + $ hg push -r 'desc("C1")' + pushing to $TESTTMP/A3/server + searching for changes + adding changesets + adding manifests + adding file changes + added 2 changesets with 2 changes to 2 files (+1 heads) + 2 new obsolescence markers + obsoleted 2 changesets + 2 new orphan changesets diff --git a/tests/test-single-head-obsolescence-named-branch-A4.t b/tests/test-single-head-obsolescence-named-branch-A4.t new file mode 100644 --- /dev/null +++ b/tests/test-single-head-obsolescence-named-branch-A4.t @@ -0,0 +1,117 @@ +========================================= +Testing single head enforcement: Case A-4 +========================================= + +A repository is set to only accept a single head per name (typically named +branch). However, obsolete changesets can make this enforcement more +complicated, because they can be kept visible by other changeset on other +branch. + +This case is part of a series of tests checking this behavior. + +Category A: Involving obsolescence +TestCase 4: Partial rewrite of a branch to deinterleave it + +.. old-state: +.. +.. * 2 changesets on branch default +.. * 2 changesets on branch Z interleaved with the other one +.. +.. new-state: +.. +.. * 2 changesets on branch Z at the same location +.. * 1 changeset on default untouched (the lower one) +.. * 1 changeset on default moved on the other one +.. +.. expected-result: +.. +.. * only one head detected +.. +.. graph-summary: +.. +.. D ● (branch Z) +.. | +.. C ø⇠◔ C' +.. | | +.. B ● | (branch Z) +.. |/ +.. A ● +.. | +.. ● + + $ . $TESTDIR/testlib/push-checkheads-util.sh + + $ cat >> $HGRCPATH << EOF + > [command-templates] + > log = "{node|short} [{branch}] ({phase}): {desc}\n" + > EOF + +Test setup +---------- + + $ mkdir A4 + $ cd A4 + $ setuprepos single-head + creating basic server and client repo + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd client + $ hg branch Z + marked working directory as branch Z + (branches are permanent and global, did you want a bookmark?) + $ mkcommit B0 + $ hg branch default --force + marked working directory as branch default + $ mkcommit C0 + created new head + $ hg branch Z --force + marked working directory as branch Z + $ mkcommit D0 + created new head + $ hg push --new-branch + pushing to $TESTTMP/A4/server + searching for changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 3 changes to 3 files + $ hg up 'desc("A0")' + 0 files updated, 0 files merged, 3 files removed, 0 files unresolved + $ mkcommit C1 + created new head + $ hg debugobsolete `getid "desc(C0)"` `getid "desc(C1)"` + 1 new obsolescence markers + obsoleted 1 changesets + 1 new orphan changesets + $ hg heads + cfe9ed94fa4a [default] (draft): C1 + 78578c4306ce [Z] (draft): D0 + $ hg log -G --hidden + @ cfe9ed94fa4a [default] (draft): C1 + | + | * 78578c4306ce [Z] (draft): D0 + | | + | x afc55ba2ce61 [default] (draft): C0 + | | + | o 93e5c1321ece [Z] (draft): B0 + |/ + o 8aaa48160adc [default] (draft): A0 + | + o 1e4be0697311 [default] (public): root + + +Actual testing +-------------- + +(force push to make sure we get the changeset on the remote) + + $ hg push -r 'desc("C1")' --force + pushing to $TESTTMP/A4/server + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files (+1 heads) + 1 new obsolescence markers + obsoleted 1 changesets + 1 new orphan changesets diff --git a/tests/test-single-head-obsolescence-named-branch-A5.t b/tests/test-single-head-obsolescence-named-branch-A5.t new file mode 100644 --- /dev/null +++ b/tests/test-single-head-obsolescence-named-branch-A5.t @@ -0,0 +1,108 @@ +========================================= +Testing single head enforcement: Case A-5 +========================================= + +A repository is set to only accept a single head per name (typically named +branch). However, obsolete changesets can make this enforcement more +complicated, because they can be kept visible by other changeset on other +branch. + +This case is part of a series of tests checking this behavior. + +Category A: Involving obsolescence +TestCase 5: Obsoleting a merge reveals two heads + +.. old-state: +.. +.. * 3 changesets on branch default (2 on their own branch + 1 merge) +.. * 1 changeset on branch Z (children of the merge) +.. +.. new-state: +.. +.. * 2 changesets on branch default (merge is obsolete) each a head +.. * 1 changeset on branch Z keeping the merge visible +.. +.. expected-result: +.. +.. * 2 heads detected (because we skip the merge) +.. +.. graph-summary: +.. +.. C ● (branch Z) +.. | +.. M ⊗ +.. |\ +.. A ● ● B +.. |/ +.. ● + + $ . $TESTDIR/testlib/push-checkheads-util.sh + + $ cat >> $HGRCPATH << EOF + > [command-templates] + > log = "{node|short} [{branch}] ({phase}): {desc}\n" + > EOF + +Test setup +---------- + + $ mkdir A5 + $ cd A5 + $ setuprepos single-head + creating basic server and client repo + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd client + $ hg up 0 + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ mkcommit B0 + created new head + $ hg merge + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -m 'M0' + $ hg branch Z + marked working directory as branch Z + (branches are permanent and global, did you want a bookmark?) + $ mkcommit C0 + $ hg push --new-branch + pushing to $TESTTMP/A5/server + searching for changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 2 changes to 2 files + $ hg debugobsolete `getid "desc(M0)"` --record-parents + 1 new obsolescence markers + obsoleted 1 changesets + 1 new orphan changesets + $ hg heads + 61c95483cc12 [Z] (draft): C0 + 74ff5441d343 [default] (draft): B0 + 8aaa48160adc [default] (draft): A0 + $ hg log -G --hidden + @ 61c95483cc12 [Z] (draft): C0 + | + x 14d3d4d41d1a [default] (draft): M0 + |\ + | o 74ff5441d343 [default] (draft): B0 + | | + o | 8aaa48160adc [default] (draft): A0 + |/ + o 1e4be0697311 [default] (public): root + + +Actual testing +-------------- + +(force push to make sure we get the changeset on the remote) + + $ hg push -r 'desc("C0")' --force + pushing to $TESTTMP/A5/server + searching for changes + no changes found + transaction abort! + rollback completed + abort: rejecting multiple heads on branch "default" + (2 heads: 8aaa48160adc 74ff5441d343) + [255] diff --git a/tests/test-sparse-clear.t b/tests/test-sparse-clear.t --- a/tests/test-sparse-clear.t +++ b/tests/test-sparse-clear.t @@ -42,7 +42,7 @@ Clear rules when there are includes Clear rules when there are excludes - $ hg debugsparse --exclude *.sparse + $ hg debugsparse -X base.sparse -X webpage.sparse $ ls -A .hg data.py diff --git a/tests/test-sparse-profiles.t b/tests/test-sparse-profiles.t --- a/tests/test-sparse-profiles.t +++ b/tests/test-sparse-profiles.t @@ -128,8 +128,8 @@ Verify conflicting merge pulls in the co $ hg merge 1 temporarily included 2 file(s) in the sparse checkout for merging merging backend.sparse + warning: conflicts while merging backend.sparse! (edit, then use 'hg resolve --mark') merging data.py - warning: conflicts while merging backend.sparse! (edit, then use 'hg resolve --mark') warning: conflicts while merging data.py! (edit, then use 'hg resolve --mark') 0 files updated, 0 files merged, 0 files removed, 2 files unresolved use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon @@ -197,8 +197,8 @@ Verify rebase conflicts pulls in the con rebasing 1:a2b1de640a62 "edit profile" temporarily included 2 file(s) in the sparse checkout for merging merging backend.sparse + warning: conflicts while merging backend.sparse! (edit, then use 'hg resolve --mark') merging data.py - warning: conflicts while merging backend.sparse! (edit, then use 'hg resolve --mark') warning: conflicts while merging data.py! (edit, then use 'hg resolve --mark') unresolved conflicts (see 'hg resolve', then 'hg rebase --continue') [240] diff --git a/tests/test-sparse-requirement.t b/tests/test-sparse-requirement.t --- a/tests/test-sparse-requirement.t +++ b/tests/test-sparse-requirement.t @@ -16,7 +16,7 @@ Enable sparse profile - $ cat .hg/requires + $ hg debugrequires dotencode dirstate-v2 (dirstate-v2 !) fncache @@ -24,6 +24,7 @@ Enable sparse profile persistent-nodemap (rust !) revlog-compression-zstd (zstd !) revlogv1 + share-safe sparserevlog store testonly-simplestore (reposimplestore !) @@ -36,7 +37,7 @@ Enable sparse profile Requirement for sparse added when sparse is enabled - $ cat .hg/requires + $ hg debugrequires --config extensions.sparse= dotencode dirstate-v2 (dirstate-v2 !) exp-sparse @@ -45,6 +46,7 @@ Requirement for sparse added when sparse persistent-nodemap (rust !) revlog-compression-zstd (zstd !) revlogv1 + share-safe sparserevlog store testonly-simplestore (reposimplestore !) @@ -59,7 +61,7 @@ Requirement for sparse is removed when s $ hg debugsparse --reset --config extensions.sparse= - $ cat .hg/requires + $ hg debugrequires dotencode dirstate-v2 (dirstate-v2 !) fncache @@ -67,6 +69,7 @@ Requirement for sparse is removed when s persistent-nodemap (rust !) revlog-compression-zstd (zstd !) revlogv1 + share-safe sparserevlog store testonly-simplestore (reposimplestore !) diff --git a/tests/test-sparse.t b/tests/test-sparse.t --- a/tests/test-sparse.t +++ b/tests/test-sparse.t @@ -147,7 +147,7 @@ Verify deleting sparseness while a file Verify deleting sparseness with --force brings back files - $ hg debugsparse --delete -f 'show*' + $ hg debugsparse -f --delete 'show*' pending changes to 'hide' $ ls -A .hg @@ -170,7 +170,7 @@ Verify editing sparseness fails if pendi Verify adding sparseness hides files - $ hg debugsparse --exclude -f 'hide*' + $ hg debugsparse -f --exclude 'hide*' pending changes to 'hide' $ ls -A .hg @@ -254,6 +254,15 @@ Verify merge fails if merging excluded f hide* +Multiple -I and -X can be passed at once + + $ hg debugsparse --reset -I '*2' -X 'hide2' + $ ls -A + .hg + hide.orig + show2 + $ hg debugsparse --reset -X 'hide*' + Verify strip -k resets dirstate correctly $ hg status diff --git a/tests/test-sqlitestore.t b/tests/test-sqlitestore.t --- a/tests/test-sqlitestore.t +++ b/tests/test-sqlitestore.t @@ -13,7 +13,7 @@ the instability. New repo should not use SQLite by default $ hg init empty-no-sqlite - $ cat empty-no-sqlite/.hg/requires + $ hg debugrequires -R empty-no-sqlite dotencode dirstate-v2 (dirstate-v2 !) fncache @@ -21,13 +21,14 @@ New repo should not use SQLite by defaul persistent-nodemap (rust !) revlog-compression-zstd (zstd !) revlogv1 + share-safe sparserevlog store storage.new-repo-backend=sqlite is recognized $ hg --config storage.new-repo-backend=sqlite init empty-sqlite - $ cat empty-sqlite/.hg/requires + $ hg debugrequires -R empty-sqlite dotencode dirstate-v2 (dirstate-v2 !) exp-sqlite-001 @@ -38,6 +39,7 @@ storage.new-repo-backend=sqlite is recog persistent-nodemap (rust !) revlog-compression-zstd (zstd !) revlogv1 + share-safe sparserevlog store @@ -49,7 +51,7 @@ storage.new-repo-backend=sqlite is recog Can force compression to zlib $ hg --config storage.sqlite.compression=zlib init empty-zlib - $ cat empty-zlib/.hg/requires + $ hg debugrequires -R empty-zlib dotencode dirstate-v2 (dirstate-v2 !) exp-sqlite-001 @@ -59,13 +61,14 @@ Can force compression to zlib persistent-nodemap (rust !) revlog-compression-zstd (zstd !) revlogv1 + share-safe sparserevlog store Can force compression to none $ hg --config storage.sqlite.compression=none init empty-none - $ cat empty-none/.hg/requires + $ hg debugrequires -R empty-none dotencode dirstate-v2 (dirstate-v2 !) exp-sqlite-001 @@ -75,6 +78,7 @@ Can force compression to none persistent-nodemap (rust !) revlog-compression-zstd (zstd !) revlogv1 + share-safe sparserevlog store diff --git a/tests/test-ssh-bundle1.t b/tests/test-ssh-bundle1.t --- a/tests/test-ssh-bundle1.t +++ b/tests/test-ssh-bundle1.t @@ -1,16 +1,6 @@ This test is a duplicate of 'test-http.t' feel free to factor out parts that are not bundle1/bundle2 specific. -#testcases sshv1 sshv2 - -#if sshv2 - $ cat >> $HGRCPATH << EOF - > [experimental] - > sshpeer.advertise-v2 = true - > sshserver.support-v2 = true - > EOF -#endif - $ cat << EOF >> $HGRCPATH > [devel] > # This test is dedicated to interaction through old bundle @@ -483,15 +473,11 @@ debug output $ hg pull --debug ssh://user@dummy/remote pulling from ssh://user@dummy/remote running .* ".*[/\\]dummyssh" ['"]user@dummy['"] ['"]hg -R remote serve --stdio['"] (re) - sending upgrade request: * proto=exp-ssh-v2-0003 (glob) (sshv2 !) sending hello command sending between command - 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 (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 !) + remote: \d+ (re) + remote: capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (re) + remote: 1 sending protocaps command preparing listkeys for "bookmarks" sending listkeys command diff --git a/tests/test-ssh-clone-r.t b/tests/test-ssh-clone-r.t --- a/tests/test-ssh-clone-r.t +++ b/tests/test-ssh-clone-r.t @@ -1,15 +1,5 @@ This test tries to exercise the ssh functionality with a dummy script -#testcases sshv1 sshv2 - -#if sshv2 - $ cat >> $HGRCPATH << EOF - > [experimental] - > sshpeer.advertise-v2 = true - > sshserver.support-v2 = true - > EOF -#endif - creating 'remote' repo $ hg init remote diff --git a/tests/test-ssh-proto-unbundle.t b/tests/test-ssh-proto-unbundle.t --- a/tests/test-ssh-proto-unbundle.t +++ b/tests/test-ssh-proto-unbundle.t @@ -5,13 +5,6 @@ persistent-nodemap is not enabled by def > use-persistent-nodemap = no > EOF - $ cat > hgrc-sshv2 << EOF - > %include $HGRCPATH - > [experimental] - > sshpeer.advertise-v2 = true - > sshserver.support-v2 = true - > EOF - $ debugwireproto() { > commands=`cat -` > echo 'testing ssh1' @@ -20,12 +13,6 @@ persistent-nodemap is not enabled by def > if [ -n "$1" ]; then > hg --config extensions.strip= strip --no-backup -r "all() - ::${tip}" > fi - > echo "" - > echo 'testing ssh2' - > echo "${commands}" | HGRCPATH=$TESTTMP/hgrc-sshv2 hg --verbose debugwireproto --localssh --noreadstderr - > if [ -n "$1" ]; then - > hg --config extensions.strip= strip --no-backup -r "all() - ::${tip}" - > fi > } Generate some bundle files @@ -63,9 +50,9 @@ Test pushing bundle1 payload to a server i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) o> readline() -> 2: o> 1\n o> readline() -> 1: @@ -103,56 +90,6 @@ Test pushing bundle1 payload to a server e> read(-1) -> 115: e> abort: incompatible Mercurial client; bundle2 required\n e> (see https://www.mercurial-scm.org/wiki/IncompatibleClient)\n - - testing ssh2 - creating ssh peer from handshake results - i> write(171) -> 171: - i> upgrade * proto=exp-ssh-v2-0003\n (glob) - i> hello\n - i> between\n - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - i> flush() -> None - o> readline() -> 62: - o> upgraded * exp-ssh-v2-0003\n (glob) - o> readline() -> 4: - 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 - i> write(9) -> 9: - i> unbundle\n - i> write(9) -> 9: - i> heads 10\n - i> write(10) -> 10: 666f726365 - i> flush() -> None - o> readline() -> 2: - o> 0\n - i> write(4) -> 4: - i> 426\n - i> write(426) -> 426: - i> HG10UN\x00\x00\x00\x9eh\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>cba485ca3678256e044428f70f58291196f6e9de\n - i> test\n - i> 0 0\n - i> foo\n - i> \n - i> initial\x00\x00\x00\x00\x00\x00\x00\x8d\xcb\xa4\x85\xca6x%n\x04D(\xf7\x0fX)\x11\x96\xf6\xe9\xde\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe\n - i> \x00\x00\x00\x00\x00\x00\x00\x07foo\x00\x00\x00b6/\xef(L\xe2\xca\x02\xae\xcc\x8d\xe6\xd5\xe8\xa1\xc3\xaf\x05V\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x020\n - i> \x00\x00\x00\x00\x00\x00\x00\x00 - i> write(2) -> 2: - i> 0\n - i> flush() -> None - o> readline() -> 2: - o> 0\n - o> readline() -> 2: - o> 1\n - o> read(1) -> 1: 0 - result: 0 - remote output: - e> read(-1) -> 115: - e> abort: incompatible Mercurial client; bundle2 required\n - e> (see https://www.mercurial-scm.org/wiki/IncompatibleClient)\n $ cd .. @@ -242,9 +179,9 @@ ui.write() in hook is redirected to stde i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) o> readline() -> 2: o> 1\n o> readline() -> 1: @@ -287,61 +224,6 @@ ui.write() in hook is redirected to stde e> transaction abort!\n e> rollback completed\n e> abort: pretxnchangegroup.fail hook failed\n - - testing ssh2 - creating ssh peer from handshake results - i> write(171) -> 171: - i> upgrade * proto=exp-ssh-v2-0003\n (glob) - i> hello\n - i> between\n - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - i> flush() -> None - o> readline() -> 62: - o> upgraded * exp-ssh-v2-0003\n (glob) - o> readline() -> 4: - 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 - i> write(9) -> 9: - i> unbundle\n - i> write(9) -> 9: - i> heads 10\n - i> write(10) -> 10: 666f726365 - i> flush() -> None - o> readline() -> 2: - o> 0\n - i> write(4) -> 4: - i> 426\n - i> write(426) -> 426: - i> HG10UN\x00\x00\x00\x9eh\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>cba485ca3678256e044428f70f58291196f6e9de\n - i> test\n - i> 0 0\n - i> foo\n - i> \n - i> initial\x00\x00\x00\x00\x00\x00\x00\x8d\xcb\xa4\x85\xca6x%n\x04D(\xf7\x0fX)\x11\x96\xf6\xe9\xde\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe\n - i> \x00\x00\x00\x00\x00\x00\x00\x07foo\x00\x00\x00b6/\xef(L\xe2\xca\x02\xae\xcc\x8d\xe6\xd5\xe8\xa1\xc3\xaf\x05V\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x020\n - i> \x00\x00\x00\x00\x00\x00\x00\x00 - i> write(2) -> 2: - i> 0\n - i> flush() -> None - o> readline() -> 2: - o> 0\n - o> readline() -> 2: - o> 1\n - o> read(1) -> 1: 0 - result: 0 - remote output: - e> read(-1) -> 151: - e> adding changesets\n - e> adding manifests\n - e> adding file changes\n - e> ui.write 1 line\n - e> transaction abort!\n - e> rollback completed\n - e> abort: pretxnchangegroup.fail hook failed\n And a variation that writes multiple lines using ui.write @@ -365,10 +247,10 @@ And a variation that writes multiple lin i> pairs 81\n i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None - o> readline() -> 4: - 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\(\) -> \d+: (re) + o> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) o> readline() -> 2: o> 1\n o> readline() -> 1: @@ -412,62 +294,6 @@ And a variation that writes multiple lin e> transaction abort!\n e> rollback completed\n e> abort: pretxnchangegroup.fail hook failed\n - - testing ssh2 - creating ssh peer from handshake results - i> write(171) -> 171: - i> upgrade * proto=exp-ssh-v2-0003\n (glob) - i> hello\n - i> between\n - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - i> flush() -> None - o> readline() -> 62: - o> upgraded * exp-ssh-v2-0003\n (glob) - o> readline() -> 4: - 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 - i> write(9) -> 9: - i> unbundle\n - i> write(9) -> 9: - i> heads 10\n - i> write(10) -> 10: 666f726365 - i> flush() -> None - o> readline() -> 2: - o> 0\n - i> write(4) -> 4: - i> 426\n - i> write(426) -> 426: - i> HG10UN\x00\x00\x00\x9eh\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>cba485ca3678256e044428f70f58291196f6e9de\n - i> test\n - i> 0 0\n - i> foo\n - i> \n - i> initial\x00\x00\x00\x00\x00\x00\x00\x8d\xcb\xa4\x85\xca6x%n\x04D(\xf7\x0fX)\x11\x96\xf6\xe9\xde\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe\n - i> \x00\x00\x00\x00\x00\x00\x00\x07foo\x00\x00\x00b6/\xef(L\xe2\xca\x02\xae\xcc\x8d\xe6\xd5\xe8\xa1\xc3\xaf\x05V\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x020\n - i> \x00\x00\x00\x00\x00\x00\x00\x00 - i> write(2) -> 2: - i> 0\n - i> flush() -> None - o> readline() -> 2: - o> 0\n - o> readline() -> 2: - o> 1\n - o> read(1) -> 1: 0 - result: 0 - remote output: - e> read(-1) -> 173: - e> adding changesets\n - e> adding manifests\n - e> adding file changes\n - e> ui.write 2 lines 1\n - e> ui.write 2 lines 2\n - e> transaction abort!\n - e> rollback completed\n - e> abort: pretxnchangegroup.fail hook failed\n And a variation that does a ui.flush() after writing output @@ -492,9 +318,9 @@ And a variation that does a ui.flush() a i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) o> readline() -> 2: o> 1\n o> readline() -> 1: @@ -537,61 +363,6 @@ And a variation that does a ui.flush() a e> transaction abort!\n e> rollback completed\n e> abort: pretxnchangegroup.fail hook failed\n - - testing ssh2 - creating ssh peer from handshake results - i> write(171) -> 171: - i> upgrade * proto=exp-ssh-v2-0003\n (glob) - i> hello\n - i> between\n - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - i> flush() -> None - o> readline() -> 62: - o> upgraded * exp-ssh-v2-0003\n (glob) - o> readline() -> 4: - 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 - i> write(9) -> 9: - i> unbundle\n - i> write(9) -> 9: - i> heads 10\n - i> write(10) -> 10: 666f726365 - i> flush() -> None - o> readline() -> 2: - o> 0\n - i> write(4) -> 4: - i> 426\n - i> write(426) -> 426: - i> HG10UN\x00\x00\x00\x9eh\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>cba485ca3678256e044428f70f58291196f6e9de\n - i> test\n - i> 0 0\n - i> foo\n - i> \n - i> initial\x00\x00\x00\x00\x00\x00\x00\x8d\xcb\xa4\x85\xca6x%n\x04D(\xf7\x0fX)\x11\x96\xf6\xe9\xde\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe\n - i> \x00\x00\x00\x00\x00\x00\x00\x07foo\x00\x00\x00b6/\xef(L\xe2\xca\x02\xae\xcc\x8d\xe6\xd5\xe8\xa1\xc3\xaf\x05V\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x020\n - i> \x00\x00\x00\x00\x00\x00\x00\x00 - i> write(2) -> 2: - i> 0\n - i> flush() -> None - o> readline() -> 2: - o> 0\n - o> readline() -> 2: - o> 1\n - o> read(1) -> 1: 0 - result: 0 - remote output: - e> read(-1) -> 157: - e> adding changesets\n - e> adding manifests\n - e> adding file changes\n - e> ui.write 1 line flush\n - e> transaction abort!\n - e> rollback completed\n - e> abort: pretxnchangegroup.fail hook failed\n Multiple writes + flush @@ -616,9 +387,9 @@ Multiple writes + flush i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) o> readline() -> 2: o> 1\n o> readline() -> 1: @@ -662,62 +433,6 @@ Multiple writes + flush e> transaction abort!\n e> rollback completed\n e> abort: pretxnchangegroup.fail hook failed\n - - testing ssh2 - creating ssh peer from handshake results - i> write(171) -> 171: - i> upgrade * proto=exp-ssh-v2-0003\n (glob) - i> hello\n - i> between\n - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - i> flush() -> None - o> readline() -> 62: - o> upgraded * exp-ssh-v2-0003\n (glob) - o> readline() -> 4: - 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 - i> write(9) -> 9: - i> unbundle\n - i> write(9) -> 9: - i> heads 10\n - i> write(10) -> 10: 666f726365 - i> flush() -> None - o> readline() -> 2: - o> 0\n - i> write(4) -> 4: - i> 426\n - i> write(426) -> 426: - i> HG10UN\x00\x00\x00\x9eh\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>cba485ca3678256e044428f70f58291196f6e9de\n - i> test\n - i> 0 0\n - i> foo\n - i> \n - i> initial\x00\x00\x00\x00\x00\x00\x00\x8d\xcb\xa4\x85\xca6x%n\x04D(\xf7\x0fX)\x11\x96\xf6\xe9\xde\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe\n - i> \x00\x00\x00\x00\x00\x00\x00\x07foo\x00\x00\x00b6/\xef(L\xe2\xca\x02\xae\xcc\x8d\xe6\xd5\xe8\xa1\xc3\xaf\x05V\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x020\n - i> \x00\x00\x00\x00\x00\x00\x00\x00 - i> write(2) -> 2: - i> 0\n - i> flush() -> None - o> readline() -> 2: - o> 0\n - o> readline() -> 2: - o> 1\n - o> read(1) -> 1: 0 - result: 0 - remote output: - e> read(-1) -> 161: - e> adding changesets\n - e> adding manifests\n - e> adding file changes\n - e> ui.write 1st\n - e> ui.write 2nd\n - e> transaction abort!\n - e> rollback completed\n - e> abort: pretxnchangegroup.fail hook failed\n ui.write() + ui.write_err() output is captured @@ -742,9 +457,9 @@ ui.write() + ui.write_err() output is ca i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) o> readline() -> 2: o> 1\n o> readline() -> 1: @@ -790,64 +505,7 @@ ui.write() + ui.write_err() output is ca e> transaction abort!\n e> rollback completed\n e> abort: pretxnchangegroup.fail hook failed\n - - testing ssh2 - creating ssh peer from handshake results - i> write(171) -> 171: - i> upgrade * proto=exp-ssh-v2-0003\n (glob) - i> hello\n - i> between\n - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - i> flush() -> None - o> readline() -> 62: - o> upgraded * exp-ssh-v2-0003\n (glob) - o> readline() -> 4: - 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 - i> write(9) -> 9: - i> unbundle\n - i> write(9) -> 9: - i> heads 10\n - i> write(10) -> 10: 666f726365 - i> flush() -> None - o> readline() -> 2: - o> 0\n - i> write(4) -> 4: - i> 426\n - i> write(426) -> 426: - i> HG10UN\x00\x00\x00\x9eh\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>cba485ca3678256e044428f70f58291196f6e9de\n - i> test\n - i> 0 0\n - i> foo\n - i> \n - i> initial\x00\x00\x00\x00\x00\x00\x00\x8d\xcb\xa4\x85\xca6x%n\x04D(\xf7\x0fX)\x11\x96\xf6\xe9\xde\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe\n - i> \x00\x00\x00\x00\x00\x00\x00\x07foo\x00\x00\x00b6/\xef(L\xe2\xca\x02\xae\xcc\x8d\xe6\xd5\xe8\xa1\xc3\xaf\x05V\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x020\n - i> \x00\x00\x00\x00\x00\x00\x00\x00 - i> write(2) -> 2: - i> 0\n - i> flush() -> None - o> readline() -> 2: - o> 0\n - o> readline() -> 2: - o> 1\n - o> read(1) -> 1: 0 - result: 0 - remote output: - e> read(-1) -> 187: - e> adding changesets\n - e> adding manifests\n - e> adding file changes\n - e> ui.write 1\n - e> ui.write_err 1\n - e> ui.write 2\n - e> ui.write_err 2\n - e> transaction abort!\n - e> rollback completed\n - e> abort: pretxnchangegroup.fail hook failed\n + print() output is captured @@ -872,9 +530,9 @@ print() output is captured i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) o> readline() -> 2: o> 1\n o> readline() -> 1: @@ -917,61 +575,6 @@ print() output is captured e> transaction abort!\n e> rollback completed\n e> abort: pretxnchangegroup.fail hook failed\n - - testing ssh2 - creating ssh peer from handshake results - i> write(171) -> 171: - i> upgrade * proto=exp-ssh-v2-0003\n (glob) - i> hello\n - i> between\n - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - i> flush() -> None - o> readline() -> 62: - o> upgraded * exp-ssh-v2-0003\n (glob) - o> readline() -> 4: - 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 - i> write(9) -> 9: - i> unbundle\n - i> write(9) -> 9: - i> heads 10\n - i> write(10) -> 10: 666f726365 - i> flush() -> None - o> readline() -> 2: - o> 0\n - i> write(4) -> 4: - i> 426\n - i> write(426) -> 426: - i> HG10UN\x00\x00\x00\x9eh\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>cba485ca3678256e044428f70f58291196f6e9de\n - i> test\n - i> 0 0\n - i> foo\n - i> \n - i> initial\x00\x00\x00\x00\x00\x00\x00\x8d\xcb\xa4\x85\xca6x%n\x04D(\xf7\x0fX)\x11\x96\xf6\xe9\xde\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe\n - i> \x00\x00\x00\x00\x00\x00\x00\x07foo\x00\x00\x00b6/\xef(L\xe2\xca\x02\xae\xcc\x8d\xe6\xd5\xe8\xa1\xc3\xaf\x05V\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x020\n - i> \x00\x00\x00\x00\x00\x00\x00\x00 - i> write(2) -> 2: - i> 0\n - i> flush() -> None - o> readline() -> 2: - o> 0\n - o> readline() -> 2: - o> 1\n - o> read(1) -> 1: 0 - result: 0 - remote output: - e> read(-1) -> 148: - e> adding changesets\n - e> adding manifests\n - e> adding file changes\n - e> printed line\n - e> transaction abort!\n - e> rollback completed\n - e> abort: pretxnchangegroup.fail hook failed\n Mixed print() and ui.write() are both captured @@ -996,9 +599,9 @@ Mixed print() and ui.write() are both ca i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) o> readline() -> 2: o> 1\n o> readline() -> 1: @@ -1044,64 +647,6 @@ Mixed print() and ui.write() are both ca e> transaction abort!\n e> rollback completed\n e> abort: pretxnchangegroup.fail hook failed\n - - testing ssh2 - creating ssh peer from handshake results - i> write(171) -> 171: - i> upgrade * proto=exp-ssh-v2-0003\n (glob) - i> hello\n - i> between\n - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - i> flush() -> None - o> readline() -> 62: - o> upgraded * exp-ssh-v2-0003\n (glob) - o> readline() -> 4: - 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 - i> write(9) -> 9: - i> unbundle\n - i> write(9) -> 9: - i> heads 10\n - i> write(10) -> 10: 666f726365 - i> flush() -> None - o> readline() -> 2: - o> 0\n - i> write(4) -> 4: - i> 426\n - i> write(426) -> 426: - i> HG10UN\x00\x00\x00\x9eh\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>cba485ca3678256e044428f70f58291196f6e9de\n - i> test\n - i> 0 0\n - i> foo\n - i> \n - i> initial\x00\x00\x00\x00\x00\x00\x00\x8d\xcb\xa4\x85\xca6x%n\x04D(\xf7\x0fX)\x11\x96\xf6\xe9\xde\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe\n - i> \x00\x00\x00\x00\x00\x00\x00\x07foo\x00\x00\x00b6/\xef(L\xe2\xca\x02\xae\xcc\x8d\xe6\xd5\xe8\xa1\xc3\xaf\x05V\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x020\n - i> \x00\x00\x00\x00\x00\x00\x00\x00 - i> write(2) -> 2: - i> 0\n - i> flush() -> None - o> readline() -> 2: - o> 0\n - o> readline() -> 2: - o> 1\n - o> read(1) -> 1: 0 - result: 0 - remote output: - e> read(-1) -> 173: - e> adding changesets\n - e> adding manifests\n - e> adding file changes\n - e> print 1\n - e> ui.write 1\n - e> print 2\n - e> ui.write 2\n - e> transaction abort!\n - e> rollback completed\n - e> abort: pretxnchangegroup.fail hook failed\n print() to stdout and stderr both get captured @@ -1126,9 +671,9 @@ print() to stdout and stderr both get ca i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) o> readline() -> 2: o> 1\n o> readline() -> 1: @@ -1174,64 +719,6 @@ print() to stdout and stderr both get ca e> transaction abort!\n e> rollback completed\n e> abort: pretxnchangegroup.fail hook failed\n - - testing ssh2 - creating ssh peer from handshake results - i> write(171) -> 171: - i> upgrade * proto=exp-ssh-v2-0003\n (glob) - i> hello\n - i> between\n - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - i> flush() -> None - o> readline() -> 62: - o> upgraded * exp-ssh-v2-0003\n (glob) - o> readline() -> 4: - 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 - i> write(9) -> 9: - i> unbundle\n - i> write(9) -> 9: - i> heads 10\n - i> write(10) -> 10: 666f726365 - i> flush() -> None - o> readline() -> 2: - o> 0\n - i> write(4) -> 4: - i> 426\n - i> write(426) -> 426: - i> HG10UN\x00\x00\x00\x9eh\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>cba485ca3678256e044428f70f58291196f6e9de\n - i> test\n - i> 0 0\n - i> foo\n - i> \n - i> initial\x00\x00\x00\x00\x00\x00\x00\x8d\xcb\xa4\x85\xca6x%n\x04D(\xf7\x0fX)\x11\x96\xf6\xe9\xde\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe\n - i> \x00\x00\x00\x00\x00\x00\x00\x07foo\x00\x00\x00b6/\xef(L\xe2\xca\x02\xae\xcc\x8d\xe6\xd5\xe8\xa1\xc3\xaf\x05V\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x020\n - i> \x00\x00\x00\x00\x00\x00\x00\x00 - i> write(2) -> 2: - i> 0\n - i> flush() -> None - o> readline() -> 2: - o> 0\n - o> readline() -> 2: - o> 1\n - o> read(1) -> 1: 0 - result: 0 - remote output: - e> read(-1) -> 171: - e> adding changesets\n - e> adding manifests\n - e> adding file changes\n - e> stdout 1\n - e> stderr 1\n - e> stdout 2\n - e> stderr 2\n - e> transaction abort!\n - e> rollback completed\n - e> abort: pretxnchangegroup.fail hook failed\n Shell hook writing to stdout has output captured @@ -1262,9 +749,9 @@ Shell hook writing to stdout has output i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) o> readline() -> 2: o> 1\n o> readline() -> 1: @@ -1308,63 +795,6 @@ Shell hook writing to stdout has output e> transaction abort!\n e> rollback completed\n e> abort: pretxnchangegroup.fail hook exited with status 1\n - - testing ssh2 - creating ssh peer from handshake results - i> write(171) -> 171: - i> upgrade * proto=exp-ssh-v2-0003\n (glob) - i> hello\n - i> between\n - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - i> flush() -> None - o> readline() -> 62: - o> upgraded * exp-ssh-v2-0003\n (glob) - o> readline() -> 4: - 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 - i> write(9) -> 9: - i> unbundle\n - i> write(9) -> 9: - i> heads 10\n - i> write(10) -> 10: 666f726365 - i> flush() -> None - o> readline() -> 2: - o> 0\n - i> write(4) -> 4: - i> 426\n - i> write(426) -> 426: - i> HG10UN\x00\x00\x00\x9eh\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>cba485ca3678256e044428f70f58291196f6e9de\n - i> test\n - i> 0 0\n - i> foo\n - i> \n - i> initial\x00\x00\x00\x00\x00\x00\x00\x8d\xcb\xa4\x85\xca6x%n\x04D(\xf7\x0fX)\x11\x96\xf6\xe9\xde\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe\n - i> \x00\x00\x00\x00\x00\x00\x00\x07foo\x00\x00\x00b6/\xef(L\xe2\xca\x02\xae\xcc\x8d\xe6\xd5\xe8\xa1\xc3\xaf\x05V\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x020\n - i> \x00\x00\x00\x00\x00\x00\x00\x00 - i> write(2) -> 2: - i> 0\n - i> flush() -> None - o> readline() -> 2: - o> 0\n - o> readline() -> 2: - o> 1\n - o> read(1) -> 1: 0 - result: 0 - remote output: - e> read(-1) -> 167: - e> adding changesets\n - e> adding manifests\n - e> adding file changes\n - e> stdout 1\n - e> stdout 2\n - e> transaction abort!\n - e> rollback completed\n - e> abort: pretxnchangegroup.fail hook exited with status 1\n - Shell hook writing to stderr has output captured $ cat > $TESTTMP/hook.sh << EOF @@ -1389,9 +819,9 @@ Shell hook writing to stderr has output i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) o> readline() -> 2: o> 1\n o> readline() -> 1: @@ -1435,63 +865,6 @@ Shell hook writing to stderr has output e> transaction abort!\n e> rollback completed\n e> abort: pretxnchangegroup.fail hook exited with status 1\n - - testing ssh2 - creating ssh peer from handshake results - i> write(171) -> 171: - i> upgrade * proto=exp-ssh-v2-0003\n (glob) - i> hello\n - i> between\n - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - i> flush() -> None - o> readline() -> 62: - o> upgraded * exp-ssh-v2-0003\n (glob) - o> readline() -> 4: - 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 - i> write(9) -> 9: - i> unbundle\n - i> write(9) -> 9: - i> heads 10\n - i> write(10) -> 10: 666f726365 - i> flush() -> None - o> readline() -> 2: - o> 0\n - i> write(4) -> 4: - i> 426\n - i> write(426) -> 426: - i> HG10UN\x00\x00\x00\x9eh\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>cba485ca3678256e044428f70f58291196f6e9de\n - i> test\n - i> 0 0\n - i> foo\n - i> \n - i> initial\x00\x00\x00\x00\x00\x00\x00\x8d\xcb\xa4\x85\xca6x%n\x04D(\xf7\x0fX)\x11\x96\xf6\xe9\xde\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe\n - i> \x00\x00\x00\x00\x00\x00\x00\x07foo\x00\x00\x00b6/\xef(L\xe2\xca\x02\xae\xcc\x8d\xe6\xd5\xe8\xa1\xc3\xaf\x05V\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x020\n - i> \x00\x00\x00\x00\x00\x00\x00\x00 - i> write(2) -> 2: - i> 0\n - i> flush() -> None - o> readline() -> 2: - o> 0\n - o> readline() -> 2: - o> 1\n - o> read(1) -> 1: 0 - result: 0 - remote output: - e> read(-1) -> 167: - e> adding changesets\n - e> adding manifests\n - e> adding file changes\n - e> stderr 1\n - e> stderr 2\n - e> transaction abort!\n - e> rollback completed\n - e> abort: pretxnchangegroup.fail hook exited with status 1\n - Shell hook writing to stdout and stderr has output captured $ cat > $TESTTMP/hook.sh << EOF @@ -1518,9 +891,9 @@ Shell hook writing to stdout and stderr i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) o> readline() -> 2: o> 1\n o> readline() -> 1: @@ -1566,65 +939,6 @@ Shell hook writing to stdout and stderr e> transaction abort!\n e> rollback completed\n e> abort: pretxnchangegroup.fail hook exited with status 1\n - - testing ssh2 - creating ssh peer from handshake results - i> write(171) -> 171: - i> upgrade * proto=exp-ssh-v2-0003\n (glob) - i> hello\n - i> between\n - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - i> flush() -> None - o> readline() -> 62: - o> upgraded * exp-ssh-v2-0003\n (glob) - o> readline() -> 4: - 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 - i> write(9) -> 9: - i> unbundle\n - i> write(9) -> 9: - i> heads 10\n - i> write(10) -> 10: 666f726365 - i> flush() -> None - o> readline() -> 2: - o> 0\n - i> write(4) -> 4: - i> 426\n - i> write(426) -> 426: - i> HG10UN\x00\x00\x00\x9eh\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>cba485ca3678256e044428f70f58291196f6e9de\n - i> test\n - i> 0 0\n - i> foo\n - i> \n - i> initial\x00\x00\x00\x00\x00\x00\x00\x8d\xcb\xa4\x85\xca6x%n\x04D(\xf7\x0fX)\x11\x96\xf6\xe9\xde\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe\n - i> \x00\x00\x00\x00\x00\x00\x00\x07foo\x00\x00\x00b6/\xef(L\xe2\xca\x02\xae\xcc\x8d\xe6\xd5\xe8\xa1\xc3\xaf\x05V\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x020\n - i> \x00\x00\x00\x00\x00\x00\x00\x00 - i> write(2) -> 2: - i> 0\n - i> flush() -> None - o> readline() -> 2: - o> 0\n - o> readline() -> 2: - o> 1\n - o> read(1) -> 1: 0 - result: 0 - remote output: - e> read(-1) -> 185: - e> adding changesets\n - e> adding manifests\n - e> adding file changes\n - e> stdout 1\n - e> stderr 1\n - e> stdout 2\n - e> stderr 2\n - e> transaction abort!\n - e> rollback completed\n - e> abort: pretxnchangegroup.fail hook exited with status 1\n - Shell and Python hooks writing to stdout and stderr have output captured $ cat > $TESTTMP/hook.sh << EOF @@ -1657,9 +971,9 @@ Shell and Python hooks writing to stdout i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) o> readline() -> 2: o> 1\n o> readline() -> 1: @@ -1709,69 +1023,6 @@ Shell and Python hooks writing to stdout e> transaction abort!\n e> rollback completed\n e> abort: pretxnchangegroup.b hook failed\n - - testing ssh2 - creating ssh peer from handshake results - i> write(171) -> 171: - i> upgrade * proto=exp-ssh-v2-0003\n (glob) - i> hello\n - i> between\n - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - i> flush() -> None - o> readline() -> 62: - o> upgraded * exp-ssh-v2-0003\n (glob) - o> readline() -> 4: - 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 - i> write(9) -> 9: - i> unbundle\n - i> write(9) -> 9: - i> heads 10\n - i> write(10) -> 10: 666f726365 - i> flush() -> None - o> readline() -> 2: - o> 0\n - i> write(4) -> 4: - i> 426\n - i> write(426) -> 426: - i> HG10UN\x00\x00\x00\x9eh\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>cba485ca3678256e044428f70f58291196f6e9de\n - i> test\n - i> 0 0\n - i> foo\n - i> \n - i> initial\x00\x00\x00\x00\x00\x00\x00\x8d\xcb\xa4\x85\xca6x%n\x04D(\xf7\x0fX)\x11\x96\xf6\xe9\xde\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe\n - i> \x00\x00\x00\x00\x00\x00\x00\x07foo\x00\x00\x00b6/\xef(L\xe2\xca\x02\xae\xcc\x8d\xe6\xd5\xe8\xa1\xc3\xaf\x05V\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x020\n - i> \x00\x00\x00\x00\x00\x00\x00\x00 - i> write(2) -> 2: - i> 0\n - i> flush() -> None - o> readline() -> 2: - o> 0\n - o> readline() -> 2: - o> 1\n - o> read(1) -> 1: 0 - result: 0 - remote output: - e> read(-1) -> 228: - e> adding changesets\n - e> adding manifests\n - e> adding file changes\n - e> shell stdout 1\n - e> shell stderr 1\n - e> shell stdout 2\n - e> shell stderr 2\n - e> stdout 1\n - e> stderr 1\n - e> stdout 2\n - e> stderr 2\n - e> transaction abort!\n - e> rollback completed\n - e> abort: pretxnchangegroup.b hook failed\n - $ cd .. Pushing a bundle1 with no output @@ -1795,9 +1046,9 @@ Pushing a bundle1 with no output i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) o> readline() -> 2: o> 1\n o> readline() -> 1: @@ -1837,59 +1088,6 @@ Pushing a bundle1 with no output e> adding manifests\n e> adding file changes\n e> added 1 changesets with 1 changes to 1 files\n - - testing ssh2 - creating ssh peer from handshake results - i> write(171) -> 171: - i> upgrade * proto=exp-ssh-v2-0003\n (glob) - i> hello\n - i> between\n - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - i> flush() -> None - o> readline() -> 62: - o> upgraded * exp-ssh-v2-0003\n (glob) - o> readline() -> 4: - 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 - i> write(9) -> 9: - i> unbundle\n - i> write(9) -> 9: - i> heads 10\n - i> write(10) -> 10: 666f726365 - i> flush() -> None - o> readline() -> 2: - o> 0\n - i> write(4) -> 4: - i> 426\n - i> write(426) -> 426: - i> HG10UN\x00\x00\x00\x9eh\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>cba485ca3678256e044428f70f58291196f6e9de\n - i> test\n - i> 0 0\n - i> foo\n - i> \n - i> initial\x00\x00\x00\x00\x00\x00\x00\x8d\xcb\xa4\x85\xca6x%n\x04D(\xf7\x0fX)\x11\x96\xf6\xe9\xde\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe\n - i> \x00\x00\x00\x00\x00\x00\x00\x07foo\x00\x00\x00b6/\xef(L\xe2\xca\x02\xae\xcc\x8d\xe6\xd5\xe8\xa1\xc3\xaf\x05V\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x020\n - i> \x00\x00\x00\x00\x00\x00\x00\x00 - i> write(2) -> 2: - i> 0\n - i> flush() -> None - o> readline() -> 2: - o> 0\n - o> readline() -> 2: - o> 1\n - o> read(1) -> 1: 1 - result: 1 - remote output: - e> read(-1) -> 100: - e> adding changesets\n - e> adding manifests\n - e> adding file changes\n - e> added 1 changesets with 1 changes to 1 files\n - $ cd .. Pushing a bundle1 with ui.write() and ui.write_err() @@ -1925,9 +1123,9 @@ Pushing a bundle1 with ui.write() and ui i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) o> readline() -> 2: o> 1\n o> readline() -> 1: @@ -1971,59 +1169,3 @@ Pushing a bundle1 with ui.write() and ui e> ui.write 2\n e> ui.write_err 2\n e> added 1 changesets with 1 changes to 1 files\n - - testing ssh2 - creating ssh peer from handshake results - i> write(171) -> 171: - i> upgrade * proto=exp-ssh-v2-0003\n (glob) - i> hello\n - i> between\n - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - i> flush() -> None - o> readline() -> 62: - o> upgraded * exp-ssh-v2-0003\n (glob) - o> readline() -> 4: - 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 - i> write(9) -> 9: - i> unbundle\n - i> write(9) -> 9: - i> heads 10\n - i> write(10) -> 10: 666f726365 - i> flush() -> None - o> readline() -> 2: - o> 0\n - i> write(4) -> 4: - i> 426\n - i> write(426) -> 426: - i> HG10UN\x00\x00\x00\x9eh\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>cba485ca3678256e044428f70f58291196f6e9de\n - i> test\n - i> 0 0\n - i> foo\n - i> \n - i> initial\x00\x00\x00\x00\x00\x00\x00\x8d\xcb\xa4\x85\xca6x%n\x04D(\xf7\x0fX)\x11\x96\xf6\xe9\xde\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe\n - i> \x00\x00\x00\x00\x00\x00\x00\x07foo\x00\x00\x00b6/\xef(L\xe2\xca\x02\xae\xcc\x8d\xe6\xd5\xe8\xa1\xc3\xaf\x05V\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x98b\x13\xbdD\x85\xeaQS55\xe3\xfc\x9ex\x00zq\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x020\n - i> \x00\x00\x00\x00\x00\x00\x00\x00 - i> write(2) -> 2: - i> 0\n - i> flush() -> None - o> readline() -> 2: - o> 0\n - o> readline() -> 2: - o> 1\n - o> read(1) -> 1: 1 - result: 1 - remote output: - e> read(-1) -> 152: - e> adding changesets\n - e> adding manifests\n - e> adding file changes\n - e> ui.write 1\n - e> ui.write_err 1\n - e> ui.write 2\n - e> ui.write_err 2\n - e> added 1 changesets with 1 changes to 1 files\n diff --git a/tests/test-ssh-proto.t b/tests/test-ssh-proto.t --- a/tests/test-ssh-proto.t +++ b/tests/test-ssh-proto.t @@ -7,13 +7,6 @@ persistent-nodemap is not enabled by def > use-persistent-nodemap = no > EOF - $ cat > hgrc-sshv2 << EOF - > %include $HGRCPATH - > [experimental] - > sshpeer.advertise-v2 = true - > sshserver.support-v2 = true - > EOF - Helper function to run protocol tests against multiple protocol versions. This is easier than using #testcases because managing differences between protocols with inline conditional output is hard to read. @@ -22,9 +15,6 @@ protocols with inline conditional output > commands=`cat -` > echo 'testing ssh1' > echo "${commands}" | hg --verbose debugwireproto --localssh - > echo "" - > echo 'testing ssh2' - > echo "${commands}" | HGRCPATH=$TESTTMP/hgrc-sshv2 hg --verbose debugwireproto --localssh > } $ cat >> $HGRCPATH << EOF @@ -54,9 +44,6 @@ Raw peers don't perform any activity $ hg debugwireproto --localssh --peer ssh1 << EOF > EOF creating ssh peer for wire protocol version 1 - $ hg debugwireproto --localssh --peer ssh2 << EOF - > EOF - creating ssh peer for wire protocol version 2 Test a normal behaving server, for sanity @@ -68,8 +55,8 @@ Test a normal behaving server, for sanit devel-peer-request: pairs: 81 bytes sending hello command sending between command - 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: \d+ (re) + remote: capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (re) remote: 1 devel-peer-request: protocaps devel-peer-request: caps: * bytes (glob) @@ -90,9 +77,9 @@ Server should answer the "hello" command i> write(6) -> 6: i> hello\n o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) `hg debugserve --sshstdio` works @@ -100,8 +87,8 @@ Server should answer the "hello" command $ hg debugserve --sshstdio << EOF > hello > EOF - 444 - capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + \d+ (re) + capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (re) I/O logging works @@ -110,25 +97,25 @@ I/O logging works > EOF e> flush() -> None o> write(4) -> 4: - 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 - 444 - capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + o> \d+\\n (re) + o> write\(\d+\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) + \d+ (re) + capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (re) o> flush() -> None $ hg debugserve --sshstdio --logiofile $TESTTMP/io << EOF > hello > EOF - 444 - capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash + \d+ (re) + capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (re) $ cat $TESTTMP/io e> flush() -> None o> write(4) -> 4: - 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> \d+\\n (re) + o> write\(\d+\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) o> flush() -> None $ cd .. @@ -153,9 +140,9 @@ reply with empty response to the "betwee i> write(6) -> 6: i> hello\n o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) i> write(98) -> 98: i> between\n i> pairs 81\n @@ -190,8 +177,8 @@ SSH banner is not printed by default, ig remote: banner: line 7 remote: banner: line 8 remote: banner: line 9 - 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: \d+ (re) + remote: capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (re) remote: 1 devel-peer-request: protocaps devel-peer-request: caps: * bytes (glob) @@ -248,9 +235,9 @@ And test the banner with the raw protoco o> readline() -> 15: o> banner: line 9\n o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) i> write(98) -> 98: i> between\n i> pairs 81\n @@ -299,13 +286,13 @@ Sending an unknown command to the server i> write(6) -> 6: i> hello\n o> readline() -> 4: - o> 444\n + o> \d+\\n (re) i> write(98) -> 98: i> between\n i> pairs 81\n i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - 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\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) o> readline() -> 2: o> 1\n @@ -317,8 +304,8 @@ Sending an unknown command to the server sending hello command sending between command remote: 0 - 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: \d+ (re) + remote: capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (re) remote: 1 devel-peer-request: protocaps devel-peer-request: caps: * bytes (glob) @@ -366,9 +353,9 @@ Send multiple unknown commands before he i> write(6) -> 6: i> hello\n o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) i> write(98) -> 98: i> between\n i> pairs 81\n @@ -390,8 +377,8 @@ Send multiple unknown commands before he remote: 0 remote: 0 remote: 0 - 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: \d+ (re) + remote: capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (re) remote: 1 devel-peer-request: protocaps devel-peer-request: caps: * bytes (glob) @@ -447,9 +434,9 @@ Send an unknown command before hello tha i> write(6) -> 6: i> hello\n o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) i> write(98) -> 98: i> between\n i> pairs 81\n @@ -494,9 +481,9 @@ Send an unknown command having an argume i> write(6) -> 6: i> hello\n o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) i> write(98) -> 98: i> between\n i> pairs 81\n @@ -539,9 +526,9 @@ Send an unknown command having an argume i> write(6) -> 6: i> hello\n o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) i> write(98) -> 98: i> between\n i> pairs 81\n @@ -609,9 +596,9 @@ Dictionary value for unknown command i> write(6) -> 6: i> hello\n o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) Incomplete dictionary send @@ -691,9 +678,9 @@ Send a command line with spaces i> write(6) -> 6: i> hello\n o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) i> write(98) -> 98: i> between\n i> pairs 81\n @@ -725,9 +712,9 @@ Send a command line with spaces i> write(6) -> 6: i> hello\n o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) i> write(98) -> 98: i> between\n i> pairs 81\n @@ -768,9 +755,9 @@ Send a command line with spaces i> write(6) -> 6: i> hello\n o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) i> write(98) -> 98: i> between\n i> pairs 81\n @@ -797,9 +784,9 @@ Send an unknown command after the "betwe i> write(6) -> 6: i> hello\n o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) i> write(105) -> 105: i> between\n i> pairs 81\n @@ -838,9 +825,9 @@ And one with arguments i> pairs 81\n i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) o> readline() -> 2: o> 1\n o> readline() -> 1: @@ -887,9 +874,9 @@ Send a valid command before the handshak o> readline() -> 41: o> 68986213bd4485ea51533535e3fc9e78007a711f\n o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) o> readline() -> 2: o> 1\n @@ -914,411 +901,7 @@ And a variation that doesn't send the be o> readline() -> 41: o> 68986213bd4485ea51533535e3fc9e78007a711f\n o> readline() -> 4: - o> 444\n - -Send an upgrade request to a server that doesn't support that command - - $ hg debugwireproto --localssh --peer raw << EOF - > raw - > upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=irrelevant1%2Cirrelevant2\n - > readline - > raw - > hello\n - > between\n - > pairs 81\n - > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - > readline - > readline - > readline - > readline - > EOF - using raw connection to peer - i> write(77) -> 77: - i> upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=irrelevant1%2Cirrelevant2\n - o> readline() -> 2: - o> 0\n - i> write(104) -> 104: - i> hello\n - i> between\n - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - o> readline() -> 4: - 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 - o> readline() -> 1: - o> \n - - $ cd .. - - $ hg --config experimental.sshpeer.advertise-v2=true --debug debugpeer ssh://user@dummy/server - running .* ".*[/\\]dummyssh" ['"]user@dummy['"] ['"]hg -R server serve --stdio['"] (re) - sending upgrade request: * proto=exp-ssh-v2-0003 (glob) - devel-peer-request: hello+between - devel-peer-request: pairs: 81 bytes - sending hello command - sending between command - remote: 0 - 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 - devel-peer-request: caps: * bytes (glob) - sending protocaps command - url: ssh://user@dummy/server - local: no - pushable: yes - -Enable version 2 support on server. We need to do this in hgrc because we can't -use --config with `hg serve --stdio`. - - $ cat >> server/.hg/hgrc << EOF - > [experimental] - > sshserver.support-v2 = true - > EOF - -Send an upgrade request to a server that supports upgrade - - $ cd server - - $ hg debugwireproto --localssh --peer raw << EOF - > raw - > upgrade this-is-some-token proto=exp-ssh-v2-0003\n - > hello\n - > between\n - > pairs 81\n - > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - > readline - > readline - > readline - > EOF - using raw connection to peer - i> write(153) -> 153: - i> upgrade this-is-some-token proto=exp-ssh-v2-0003\n - i> hello\n - i> between\n - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - o> readline() -> 44: - o> upgraded this-is-some-token exp-ssh-v2-0003\n - o> readline() -> 4: - 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 .. - - $ hg --config experimental.sshpeer.advertise-v2=true --debug debugpeer ssh://user@dummy/server - running .* ".*[/\\]dummyssh" ['"]user@dummy['"] ['"]hg -R server serve --stdio['"] (re) - sending upgrade request: * proto=exp-ssh-v2-0003 (glob) - devel-peer-request: hello+between - devel-peer-request: pairs: 81 bytes - sending hello command - sending between command - protocol upgraded to exp-ssh-v2-0003 - remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash - devel-peer-request: protocaps - devel-peer-request: caps: * bytes (glob) - sending protocaps command - url: ssh://user@dummy/server - local: no - pushable: yes - -Verify the peer has capabilities - - $ hg --config experimental.sshpeer.advertise-v2=true --debug debugcapabilities ssh://user@dummy/server - running .* ".*[/\\]dummyssh" ['"]user@dummy['"] ['"]hg -R server serve --stdio['"] (re) - sending upgrade request: * proto=exp-ssh-v2-0003 (glob) - devel-peer-request: hello+between - devel-peer-request: pairs: 81 bytes - sending hello command - sending between command - protocol upgraded to exp-ssh-v2-0003 - remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash - devel-peer-request: protocaps - devel-peer-request: caps: * bytes (glob) - sending protocaps command - Main capabilities: - batch - branchmap - $USUAL_BUNDLE2_CAPS$ - changegroupsubset - getbundle - known - lookup - protocaps - pushkey - streamreqs=generaldelta,revlogv1,sparserevlog - unbundle=HG10GZ,HG10BZ,HG10UN - unbundlehash - Bundle2 capabilities: - HG20 - bookmarks - changegroup - 01 - 02 - checkheads - related - digests - md5 - sha1 - sha512 - error - abort - unsupportedcontent - pushraced - pushkey - hgtagsfnodes - listkeys - phases - heads - pushkey - remote-changegroup - http - https - stream - v2 - -Command after upgrade to version 2 is processed - - $ cd server - - $ hg debugwireproto --localssh --peer raw << EOF - > raw - > upgrade this-is-some-token proto=exp-ssh-v2-0003\n - > hello\n - > between\n - > pairs 81\n - > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - > readline - > readline - > readline - > raw - > hello\n - > readline - > readline - > EOF - using raw connection to peer - i> write(153) -> 153: - i> upgrade this-is-some-token proto=exp-ssh-v2-0003\n - i> hello\n - i> between\n - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - o> readline() -> 44: - o> upgraded this-is-some-token exp-ssh-v2-0003\n - o> readline() -> 4: - 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> 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 - - $ hg debugwireproto --localssh --peer raw << EOF - > raw - > upgrade this-is-some-token proto=exp-ssh-v2-0003\n - > hello\n - > between\n - > pairs 81\n - > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - > readline - > readline - > readline - > raw - > upgrade another-token proto=irrelevant\n - > hello\n - > readline - > readavailable - > EOF - using raw connection to peer - i> write(153) -> 153: - i> upgrade this-is-some-token proto=exp-ssh-v2-0003\n - i> hello\n - i> between\n - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - o> readline() -> 44: - o> upgraded this-is-some-token exp-ssh-v2-0003\n - o> readline() -> 4: - 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 - i> hello\n - o> readline() -> 1: - o> \n - e> read(-1) -> 42: - e> cannot upgrade protocols multiple times\n - e> -\n - -Malformed upgrade request line (not exactly 3 space delimited tokens) - - $ hg debugwireproto --localssh --peer raw << EOF - > raw - > upgrade\n - > readline - > EOF - using raw connection to peer - i> write(8) -> 8: - i> upgrade\n - o> readline() -> 2: - o> 0\n - - $ hg debugwireproto --localssh --peer raw << EOF - > raw - > upgrade token\n - > readline - > EOF - using raw connection to peer - i> write(14) -> 14: - i> upgrade token\n - o> readline() -> 2: - o> 0\n - - $ hg debugwireproto --localssh --peer raw << EOF - > raw - > upgrade token foo=bar extra-token\n - > readline - > EOF - using raw connection to peer - i> write(34) -> 34: - i> upgrade token foo=bar extra-token\n - o> readline() -> 2: - o> 0\n - -Upgrade request to unsupported protocol is ignored - - $ hg debugwireproto --localssh --peer raw << EOF - > raw - > upgrade this-is-some-token proto=unknown1,unknown2\n - > readline - > raw - > hello\n - > readline - > readline - > raw - > between\n - > pairs 81\n - > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - > readline - > readline - > EOF - using raw connection to peer - i> write(51) -> 51: - i> upgrade this-is-some-token proto=unknown1,unknown2\n - o> readline() -> 2: - o> 0\n - i> write(6) -> 6: - i> hello\n - o> readline() -> 4: - 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 - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - o> readline() -> 2: - o> 1\n - o> readline() -> 1: - o> \n - -Upgrade request must be followed by hello + between - - $ hg debugwireproto --localssh --peer raw << EOF - > raw - > upgrade token proto=exp-ssh-v2-0003\n - > invalid\n - > readline - > readavailable - > EOF - using raw connection to peer - i> write(44) -> 44: - i> upgrade token proto=exp-ssh-v2-0003\n - i> invalid\n - o> readline() -> 1: - o> \n - e> read(-1) -> 46: - e> malformed handshake protocol: missing hello\n - e> -\n - - $ hg debugwireproto --localssh --peer raw << EOF - > raw - > upgrade token proto=exp-ssh-v2-0003\n - > hello\n - > invalid\n - > readline - > readavailable - > EOF - using raw connection to peer - i> write(50) -> 50: - i> upgrade token proto=exp-ssh-v2-0003\n - i> hello\n - i> invalid\n - o> readline() -> 1: - o> \n - e> read(-1) -> 48: - e> malformed handshake protocol: missing between\n - e> -\n - - $ hg debugwireproto --localssh --peer raw << EOF - > raw - > upgrade token proto=exp-ssh-v2-0003\n - > hello\n - > between\n - > invalid\n - > readline - > readavailable - > EOF - using raw connection to peer - i> write(58) -> 58: - i> upgrade token proto=exp-ssh-v2-0003\n - i> hello\n - i> between\n - i> invalid\n - o> readline() -> 1: - o> \n - e> read(-1) -> 49: - e> malformed handshake protocol: missing pairs 81\n - e> -\n - -Legacy commands are not exposed to version 2 of protocol - -TODO re-enable these once we're back to actually using v2 commands - -$ hg --config experimental.sshpeer.advertise-v2=true debugwireproto --localssh << EOF -> command branches -> nodes 0000000000000000000000000000000000000000 -> EOF -creating ssh peer from handshake results -sending branches command -response: - -$ hg --config experimental.sshpeer.advertise-v2=true debugwireproto --localssh << EOF -> command changegroup -> roots 0000000000000000000000000000000000000000 -> EOF -creating ssh peer from handshake results -sending changegroup command -response: - -$ hg --config experimental.sshpeer.advertise-v2=true debugwireproto --localssh << EOF -> command changegroupsubset -> bases 0000000000000000000000000000000000000000 -> heads 0000000000000000000000000000000000000000 -> EOF -creating ssh peer from handshake results -sending changegroupsubset command -response: + o> \d+\\n (re) $ cd .. @@ -1339,9 +922,9 @@ Test listkeys for listing namespaces i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) o> readline() -> 2: o> 1\n o> readline() -> 1: @@ -1364,41 +947,6 @@ Test listkeys for listing namespaces b'namespaces': b'', b'phases': b'' } - - testing ssh2 - creating ssh peer from handshake results - i> write(171) -> 171: - i> upgrade * proto=exp-ssh-v2-0003\n (glob) - i> hello\n - i> between\n - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - i> flush() -> None - o> readline() -> 62: - o> upgraded * exp-ssh-v2-0003\n (glob) - o> readline() -> 4: - 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 - i> write(9) -> 9: - i> listkeys\n - i> write(13) -> 13: - i> namespace 10\n - i> write(10) -> 10: namespaces - i> flush() -> None - o> bufferedreadline() -> 3: - o> 30\n - o> bufferedread(30) -> 30: - o> bookmarks\t\n - o> namespaces\t\n - o> phases\t - response: { - b'bookmarks': b'', - b'namespaces': b'', - b'phases': b'' - } $ cd .. @@ -1427,9 +975,9 @@ With no bookmarks set i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) o> readline() -> 2: o> 1\n o> readline() -> 1: @@ -1444,33 +992,6 @@ With no bookmarks set o> bufferedreadline() -> 2: o> 0\n response: {} - - testing ssh2 - creating ssh peer from handshake results - i> write(171) -> 171: - i> upgrade * proto=exp-ssh-v2-0003\n (glob) - i> hello\n - i> between\n - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - i> flush() -> None - o> readline() -> 62: - o> upgraded * exp-ssh-v2-0003\n (glob) - o> readline() -> 4: - 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 - i> write(9) -> 9: - i> listkeys\n - i> write(12) -> 12: - i> namespace 9\n - i> write(9) -> 9: bookmarks - i> flush() -> None - o> bufferedreadline() -> 2: - o> 0\n - response: {} With a single bookmark set @@ -1488,9 +1009,9 @@ With a single bookmark set i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) o> readline() -> 2: o> 1\n o> readline() -> 1: @@ -1508,36 +1029,6 @@ With a single bookmark set response: { b'bookA': b'68986213bd4485ea51533535e3fc9e78007a711f' } - - testing ssh2 - creating ssh peer from handshake results - i> write(171) -> 171: - i> upgrade * proto=exp-ssh-v2-0003\n (glob) - i> hello\n - i> between\n - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - i> flush() -> None - o> readline() -> 62: - o> upgraded * exp-ssh-v2-0003\n (glob) - o> readline() -> 4: - 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 - i> write(9) -> 9: - i> listkeys\n - i> write(12) -> 12: - i> namespace 9\n - i> write(9) -> 9: bookmarks - i> flush() -> None - o> bufferedreadline() -> 3: - o> 46\n - o> bufferedread(46) -> 46: bookA\t68986213bd4485ea51533535e3fc9e78007a711f - response: { - b'bookA': b'68986213bd4485ea51533535e3fc9e78007a711f' - } With multiple bookmarks set @@ -1555,9 +1046,9 @@ With multiple bookmarks set i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) o> readline() -> 2: o> 1\n o> readline() -> 1: @@ -1578,39 +1069,6 @@ With multiple bookmarks set b'bookA': b'68986213bd4485ea51533535e3fc9e78007a711f', b'bookB': b'1880f3755e2e52e3199e0ee5638128b08642f34d' } - - testing ssh2 - creating ssh peer from handshake results - i> write(171) -> 171: - i> upgrade * proto=exp-ssh-v2-0003\n (glob) - i> hello\n - i> between\n - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - i> flush() -> None - o> readline() -> 62: - o> upgraded * exp-ssh-v2-0003\n (glob) - o> readline() -> 4: - 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 - i> write(9) -> 9: - i> listkeys\n - i> write(12) -> 12: - i> namespace 9\n - i> write(9) -> 9: bookmarks - i> flush() -> None - o> bufferedreadline() -> 3: - o> 93\n - o> bufferedread(93) -> 93: - o> bookA\t68986213bd4485ea51533535e3fc9e78007a711f\n - o> bookB\t1880f3755e2e52e3199e0ee5638128b08642f34d - response: { - b'bookA': b'68986213bd4485ea51533535e3fc9e78007a711f', - b'bookB': b'1880f3755e2e52e3199e0ee5638128b08642f34d' - } Test pushkey for bookmarks @@ -1630,9 +1088,9 @@ Test pushkey for bookmarks i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) o> readline() -> 2: o> 1\n o> readline() -> 1: @@ -1657,43 +1115,6 @@ Test pushkey for bookmarks o> bufferedread(2) -> 2: o> 1\n response: True - - testing ssh2 - creating ssh peer from handshake results - i> write(171) -> 171: - i> upgrade * proto=exp-ssh-v2-0003\n (glob) - i> hello\n - i> between\n - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - i> flush() -> None - o> readline() -> 62: - o> upgraded * exp-ssh-v2-0003\n (glob) - o> readline() -> 4: - 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 - i> write(8) -> 8: - i> pushkey\n - i> write(6) -> 6: - i> key 6\n - i> write(6) -> 6: remote - i> write(12) -> 12: - i> namespace 9\n - i> write(9) -> 9: bookmarks - i> write(7) -> 7: - i> new 40\n - i> write(40) -> 40: 68986213bd4485ea51533535e3fc9e78007a711f - i> write(6) -> 6: - i> old 0\n - i> flush() -> None - o> bufferedreadline() -> 2: - o> 2\n - o> bufferedread(2) -> 2: - o> 1\n - response: True $ hg bookmarks bookA 0:68986213bd44 @@ -1722,9 +1143,9 @@ Phases on empty repo i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) o> readline() -> 2: o> 1\n o> readline() -> 1: @@ -1742,36 +1163,6 @@ Phases on empty repo response: { b'publishing': b'True' } - - testing ssh2 - creating ssh peer from handshake results - i> write(171) -> 171: - i> upgrade * proto=exp-ssh-v2-0003\n (glob) - i> hello\n - i> between\n - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - i> flush() -> None - o> readline() -> 62: - o> upgraded * exp-ssh-v2-0003\n (glob) - o> readline() -> 4: - 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 - i> write(9) -> 9: - i> listkeys\n - i> write(12) -> 12: - i> namespace 6\n - i> write(6) -> 6: phases - i> flush() -> None - o> bufferedreadline() -> 3: - o> 15\n - o> bufferedread(15) -> 15: publishing\tTrue - response: { - b'publishing': b'True' - } Create some commits @@ -1805,9 +1196,9 @@ Two draft heads i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) o> readline() -> 2: o> 1\n o> readline() -> 1: @@ -1830,41 +1221,6 @@ Two draft heads b'c4750011d906c18ea2f0527419cbc1a544435150': b'1', b'publishing': b'True' } - - testing ssh2 - creating ssh peer from handshake results - i> write(171) -> 171: - i> upgrade * proto=exp-ssh-v2-0003\n (glob) - i> hello\n - i> between\n - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - i> flush() -> None - o> readline() -> 62: - o> upgraded * exp-ssh-v2-0003\n (glob) - o> readline() -> 4: - 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 - i> write(9) -> 9: - i> listkeys\n - i> write(12) -> 12: - i> namespace 6\n - i> write(6) -> 6: phases - i> flush() -> None - o> bufferedreadline() -> 4: - o> 101\n - o> bufferedread(101) -> 101: - o> 20b8a89289d80036e6c4e87c2083e3bea1586637\t1\n - o> c4750011d906c18ea2f0527419cbc1a544435150\t1\n - o> publishing\tTrue - response: { - b'20b8a89289d80036e6c4e87c2083e3bea1586637': b'1', - b'c4750011d906c18ea2f0527419cbc1a544435150': b'1', - b'publishing': b'True' - } Single draft head @@ -1882,9 +1238,9 @@ Single draft head i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) o> readline() -> 2: o> 1\n o> readline() -> 1: @@ -1905,39 +1261,6 @@ Single draft head b'c4750011d906c18ea2f0527419cbc1a544435150': b'1', b'publishing': b'True' } - - testing ssh2 - creating ssh peer from handshake results - i> write(171) -> 171: - i> upgrade * proto=exp-ssh-v2-0003\n (glob) - i> hello\n - i> between\n - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - i> flush() -> None - o> readline() -> 62: - o> upgraded * exp-ssh-v2-0003\n (glob) - o> readline() -> 4: - 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 - i> write(9) -> 9: - i> listkeys\n - i> write(12) -> 12: - i> namespace 6\n - i> write(6) -> 6: phases - i> flush() -> None - o> bufferedreadline() -> 3: - o> 58\n - o> bufferedread(58) -> 58: - o> c4750011d906c18ea2f0527419cbc1a544435150\t1\n - o> publishing\tTrue - response: { - b'c4750011d906c18ea2f0527419cbc1a544435150': b'1', - b'publishing': b'True' - } All public heads @@ -1955,9 +1278,9 @@ All public heads i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) o> readline() -> 2: o> 1\n o> readline() -> 1: @@ -1975,36 +1298,6 @@ All public heads response: { b'publishing': b'True' } - - testing ssh2 - creating ssh peer from handshake results - i> write(171) -> 171: - i> upgrade * proto=exp-ssh-v2-0003\n (glob) - i> hello\n - i> between\n - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - i> flush() -> None - o> readline() -> 62: - o> upgraded * exp-ssh-v2-0003\n (glob) - o> readline() -> 4: - 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 - i> write(9) -> 9: - i> listkeys\n - i> write(12) -> 12: - i> namespace 6\n - i> write(6) -> 6: phases - i> flush() -> None - o> bufferedreadline() -> 3: - o> 15\n - o> bufferedread(15) -> 15: publishing\tTrue - response: { - b'publishing': b'True' - } Setting public phase via pushkey @@ -2026,9 +1319,9 @@ Setting public phase via pushkey i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) o> readline() -> 2: o> 1\n o> readline() -> 1: @@ -2054,44 +1347,6 @@ Setting public phase via pushkey o> bufferedread(2) -> 2: o> 1\n response: True - - testing ssh2 - creating ssh peer from handshake results - i> write(171) -> 171: - i> upgrade * proto=exp-ssh-v2-0003\n (glob) - i> hello\n - i> between\n - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - i> flush() -> None - o> readline() -> 62: - o> upgraded * exp-ssh-v2-0003\n (glob) - o> readline() -> 4: - 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 - i> write(8) -> 8: - i> pushkey\n - i> write(7) -> 7: - i> key 40\n - i> write(40) -> 40: 7127240a084fd9dc86fe8d1f98e26229161ec82b - i> write(12) -> 12: - i> namespace 6\n - i> write(6) -> 6: phases - i> write(6) -> 6: - i> new 1\n - i> write(1) -> 1: 0 - i> write(6) -> 6: - i> old 1\n - i> write(1) -> 1: 1 - i> flush() -> None - o> bufferedreadline() -> 2: - o> 2\n - o> bufferedread(2) -> 2: - o> 1\n - response: True $ hg phase . 4: public @@ -2133,9 +1388,9 @@ Test batching of requests i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 i> flush() -> None o> readline() -> 4: - 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> \d+\\n (re) + o> readline\(\) -> \d+: (re) + o> capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\\n (re) o> readline() -> 2: o> 1\n o> readline() -> 1: @@ -2160,40 +1415,3 @@ Test batching of requests response #0: bfebe6bd38eebc6f8202e419c1171268987ea6a6 4ee3fcef1c800fa2bf23e20af7c83ff111d9c7ab\n response #1: bookA\t4ee3fcef1c800fa2bf23e20af7c83ff111d9c7ab\nbookB\tbfebe6bd38eebc6f8202e419c1171268987ea6a6 response #2: 4ee3fcef1c800fa2bf23e20af7c83ff111d9c7ab\t1\nbfebe6bd38eebc6f8202e419c1171268987ea6a6\t1\npublishing\tTrue - - testing ssh2 - creating ssh peer from handshake results - i> write(171) -> 171: - i> upgrade * proto=exp-ssh-v2-0003\n (glob) - i> hello\n - i> between\n - i> pairs 81\n - i> 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 - i> flush() -> None - o> readline() -> 62: - o> upgraded * exp-ssh-v2-0003\n (glob) - o> readline() -> 4: - 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 - i> write(6) -> 6: - i> batch\n - i> write(4) -> 4: - i> * 0\n - i> write(8) -> 8: - i> cmds 61\n - i> write(61) -> 61: heads ;listkeys namespace=bookmarks;listkeys namespace=phases - i> flush() -> None - o> bufferedreadline() -> 4: - o> 278\n - o> bufferedread(278) -> 278: - o> bfebe6bd38eebc6f8202e419c1171268987ea6a6 4ee3fcef1c800fa2bf23e20af7c83ff111d9c7ab\n - o> ;bookA\t4ee3fcef1c800fa2bf23e20af7c83ff111d9c7ab\n - o> bookB\tbfebe6bd38eebc6f8202e419c1171268987ea6a6;4ee3fcef1c800fa2bf23e20af7c83ff111d9c7ab\t1\n - o> bfebe6bd38eebc6f8202e419c1171268987ea6a6\t1\n - o> publishing\tTrue - response #0: bfebe6bd38eebc6f8202e419c1171268987ea6a6 4ee3fcef1c800fa2bf23e20af7c83ff111d9c7ab\n - response #1: bookA\t4ee3fcef1c800fa2bf23e20af7c83ff111d9c7ab\nbookB\tbfebe6bd38eebc6f8202e419c1171268987ea6a6 - response #2: 4ee3fcef1c800fa2bf23e20af7c83ff111d9c7ab\t1\nbfebe6bd38eebc6f8202e419c1171268987ea6a6\t1\npublishing\tTrue diff --git a/tests/test-ssh.t b/tests/test-ssh.t --- a/tests/test-ssh.t +++ b/tests/test-ssh.t @@ -1,13 +1,3 @@ -#testcases sshv1 sshv2 - -#if sshv2 - $ cat >> $HGRCPATH << EOF - > [experimental] - > sshpeer.advertise-v2 = true - > sshserver.support-v2 = true - > EOF -#endif - This test tries to exercise the ssh functionality with a dummy script creating 'remote' repo @@ -537,17 +527,13 @@ debug output $ hg pull --debug ssh://user@dummy/remote --config devel.debug.peer-request=yes pulling from ssh://user@dummy/remote running .* ".*[/\\]dummyssh" ['"]user@dummy['"] ['"]hg -R remote serve --stdio['"] (re) - sending upgrade request: * proto=exp-ssh-v2-0003 (glob) (sshv2 !) devel-peer-request: hello+between devel-peer-request: pairs: 81 bytes sending hello command sending between command - 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 (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 !) + remote: \d+ (re) + remote: capabilities: batch branchmap \$USUAL_BUNDLE2_CAPS\$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=[^ ,]+(,[^ ,]+)* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (re) + remote: 1 devel-peer-request: protocaps devel-peer-request: caps: * bytes (glob) sending protocaps command diff --git a/tests/test-static-http.t b/tests/test-static-http.t --- a/tests/test-static-http.t +++ b/tests/test-static-http.t @@ -95,7 +95,7 @@ trying clone -r $ cd .. $ hg clone -r doesnotexist static-http://localhost:$HGPORT/remote local0 abort: unknown revision 'doesnotexist' - [255] + [10] $ hg clone -r 0 static-http://localhost:$HGPORT/remote local0 adding changesets adding manifests @@ -240,6 +240,7 @@ List of files accessed over HTTP: /.hg/store/data/a.i /.hg/store/data/~2ehgsub.i (py37 !) /.hg/store/data/~2ehgsubstate.i (py37 !) + /.hg/store/requires /notarepo/.hg/00changelog.i /notarepo/.hg/requires /remote-with-names/.hg/bookmarks @@ -255,6 +256,8 @@ List of files accessed over HTTP: /remote-with-names/.hg/store/data/%7E2ehgtags.i (no-py37 !) /remote-with-names/.hg/store/data/foo.i /remote-with-names/.hg/store/data/~2ehgtags.i (py37 !) + /remote-with-names/.hg/store/obsstore + /remote-with-names/.hg/store/requires /remote/.hg/bookmarks /remote/.hg/bookmarks.current /remote/.hg/cache/branch2-base @@ -274,12 +277,15 @@ List of files accessed over HTTP: /remote/.hg/store/data/quux.i /remote/.hg/store/data/~2edotfile%20with%20spaces.i (py37 !) /remote/.hg/store/data/~2ehgtags.i (py37 !) + /remote/.hg/store/obsstore + /remote/.hg/store/requires /remotempty/.hg/bookmarks /remotempty/.hg/bookmarks.current /remotempty/.hg/dirstate /remotempty/.hg/requires /remotempty/.hg/store/00changelog.i /remotempty/.hg/store/00manifest.i + /remotempty/.hg/store/requires /sub/.hg/bookmarks /sub/.hg/bookmarks.current /sub/.hg/cache/hgtagsfnodes1 @@ -290,3 +296,4 @@ List of files accessed over HTTP: /sub/.hg/store/data/%7E2ehgtags.i (no-py37 !) /sub/.hg/store/data/test.i /sub/.hg/store/data/~2ehgtags.i (py37 !) + /sub/.hg/store/requires diff --git a/tests/test-status-color.t b/tests/test-status-color.t --- a/tests/test-status-color.t +++ b/tests/test-status-color.t @@ -311,8 +311,8 @@ test unknown color $ hg --config color.status.modified=periwinkle status ignoring unknown color/effect 'periwinkle' (configured in color.status.modified) - ignoring unknown color/effect 'periwinkle' (configured in color.status.modified) - ignoring unknown color/effect 'periwinkle' (configured in color.status.modified) + ignoring unknown color/effect 'periwinkle' (configured in color.status.modified) (no-rhg !) + ignoring unknown color/effect 'periwinkle' (configured in color.status.modified) (no-rhg !) M modified \x1b[0;32;1mA \x1b[0m\x1b[0;32;1madded\x1b[0m (esc) \x1b[0;32;1mA \x1b[0m\x1b[0;32;1mcopied\x1b[0m (esc) @@ -375,8 +375,8 @@ test 'resolve -l' created new head $ hg merge merging a + warning: conflicts while merging a! (edit, then use 'hg resolve --mark') merging b - warning: conflicts while merging a! (edit, then use 'hg resolve --mark') warning: conflicts while merging b! (edit, then use 'hg resolve --mark') 0 files updated, 0 files merged, 0 files removed, 2 files unresolved use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon diff --git a/tests/test-status-tracked-key.t b/tests/test-status-tracked-key.t new file mode 100644 --- /dev/null +++ b/tests/test-status-tracked-key.t @@ -0,0 +1,204 @@ +=============================== +Test the "tracked hint" feature +=============================== + +The tracked hint feature provide a file that get updated when the set of tracked +files get updated. + +basic setup + + $ cat << EOF >> $HGRCPATH + > [format] + > use-dirstate-tracked-hint=yes + > EOF + + $ hg init tracked-hint-test + $ cd tracked-hint-test + $ hg debugbuilddag '.+10' -n + $ hg log -G -T '{rev} {desc} {files}\n' + o 10 r10 nf10 + | + o 9 r9 nf9 + | + o 8 r8 nf8 + | + o 7 r7 nf7 + | + o 6 r6 nf6 + | + o 5 r5 nf5 + | + o 4 r4 nf4 + | + o 3 r3 nf3 + | + o 2 r2 nf2 + | + o 1 r1 nf1 + | + o 0 r0 nf0 + + $ hg up tip + 11 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg files + nf0 + nf1 + nf10 + nf2 + nf3 + nf4 + nf5 + nf6 + nf7 + nf8 + nf9 + +key-file exists +----------- + +The tracked hint file should exist + + $ ls -1 .hg/dirstate* + .hg/dirstate + .hg/dirstate-tracked-hint + +key-file stay the same if the tracked set is unchanged +------------------------------------------------------ + +(copy its content for later comparison) + + $ cp .hg/dirstate-tracked-hint ../key-bck + $ echo foo >> nf0 + $ sleep 1 + $ hg status + M nf0 + $ diff --brief .hg/dirstate-tracked-hint ../key-bck + $ hg revert -C nf0 + $ sleep 1 + $ hg status + $ diff --brief .hg/dirstate-tracked-hint ../key-bck + +key-file change if the tracked set is changed manually +------------------------------------------------------ + +adding a file to tracking + + $ cp .hg/dirstate-tracked-hint ../key-bck + $ echo x > x + $ hg add x + $ diff --brief .hg/dirstate-tracked-hint ../key-bck + Files .hg/dirstate-tracked-hint and ../key-bck differ + [1] + +remove a file from tracking +(forget) + + $ cp .hg/dirstate-tracked-hint ../key-bck + $ hg forget x + $ diff --brief .hg/dirstate-tracked-hint ../key-bck + Files .hg/dirstate-tracked-hint and ../key-bck differ + [1] + +(remove) + + $ cp .hg/dirstate-tracked-hint ../key-bck + $ hg remove nf1 + $ diff --brief .hg/dirstate-tracked-hint ../key-bck + Files .hg/dirstate-tracked-hint and ../key-bck differ + [1] + +key-file changes on revert (when applicable) +-------------------------------------------- + + $ cp .hg/dirstate-tracked-hint ../key-bck + $ hg status + R nf1 + ? x + $ hg revert --all + undeleting nf1 + $ hg status + ? x + $ diff --brief .hg/dirstate-tracked-hint ../key-bck + Files .hg/dirstate-tracked-hint and ../key-bck differ + [1] + + +`hg update` does affect the key-file (when needed) +-------------------------------------------------- + +update changing the tracked set + +(removing) + + $ cp .hg/dirstate-tracked-hint ../key-bck + $ hg status --rev . --rev '.#generations[-1]' + R nf10 + $ hg up '.#generations[-1]' + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ diff --brief .hg/dirstate-tracked-hint ../key-bck + Files .hg/dirstate-tracked-hint and ../key-bck differ + [1] + +(adding) + + $ cp .hg/dirstate-tracked-hint ../key-bck + $ hg status --rev . --rev '.#generations[1]' + A nf10 + $ hg up '.#generations[1]' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ diff --brief .hg/dirstate-tracked-hint ../key-bck + Files .hg/dirstate-tracked-hint and ../key-bck differ + [1] + +update not affecting the tracked set + + $ echo foo >> nf0 + $ hg commit -m foo + + $ cp .hg/dirstate-tracked-hint ../key-bck + $ hg status --rev . --rev '.#generations[-1]' + M nf0 + $ hg up '.#generations[-1]' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ diff --brief .hg/dirstate-tracked-hint ../key-bck + +Test upgrade and downgrade +========================== + + $ ls .hg/dirstate-tracked-hint + .hg/dirstate-tracked-hint + $ hg debugrequires | grep 'tracked' + dirstate-tracked-key-v1 + +downgrade + + $ hg debugupgraderepo --config format.use-dirstate-tracked-hint=no --run --quiet + upgrade will perform the following actions: + + requirements + preserved: * (glob) + removed: dirstate-tracked-key-v1 + + no revlogs to process + + $ ls -1 .hg/dirstate-tracked-hint + ls: cannot access '.hg/dirstate-tracked-hint': $ENOENT$ + [2] + $ hg debugrequires | grep 'tracked' + [1] + +upgrade + + $ hg debugupgraderepo --config format.use-dirstate-tracked-hint=yes --run --quiet + upgrade will perform the following actions: + + requirements + preserved: * (glob) + added: dirstate-tracked-key-v1 + + no revlogs to process + + $ ls -1 .hg/dirstate-tracked-hint + .hg/dirstate-tracked-hint + $ hg debugrequires | grep 'tracked' + dirstate-tracked-key-v1 diff --git a/tests/test-status.t b/tests/test-status.t --- a/tests/test-status.t +++ b/tests/test-status.t @@ -218,6 +218,13 @@ hg status: ! deleted ? unknown +hg status -n: + $ env RHG_ON_UNSUPPORTED=abort hg status -n + added + removed + deleted + unknown + hg status modified added removed deleted unknown never-existed ignored: $ hg status modified added removed deleted unknown never-existed ignored @@ -934,6 +941,7 @@ It is still not set when there are unkno Now the directory is eligible for caching, so its mtime is save in the dirstate $ rm subdir/unknown + $ sleep 0.1 # ensure the kernel’s internal clock for mtimes has ticked $ hg status $ hg debugdirstate --all --no-dates | grep '^ ' 0 -1 set subdir diff --git a/tests/test-stream-bundle-v2.t b/tests/test-stream-bundle-v2.t --- a/tests/test-stream-bundle-v2.t +++ b/tests/test-stream-bundle-v2.t @@ -45,15 +45,13 @@ The extension requires a repo (currently $ 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) (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 no-dirstate-v2 !) - stream2 -- {bytecount: 1693, filecount: 11, requirements: dotencode%2Cuse-dirstate-v2%2Cfncache%2Cgeneraldelta%2Cpersistent-nodemap%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog%2Cstore} (mandatory: True) (dirstate-v2 !) + stream2 -- {bytecount: 1693, filecount: 11, requirements: generaldelta%2Crevlogv1%2Csparserevlog} (mandatory: True) (no-zstd !) + stream2 -- {bytecount: 1693, filecount: 11, requirements: generaldelta%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog} (mandatory: True) (zstd no-rust !) + stream2 -- {bytecount: 1693, filecount: 11, requirements: generaldelta%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog} (mandatory: True) (rust !) $ hg debugbundle --spec bundle.hg - 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 no-dirstate-v2 !) - none-v2;stream=v2;requirements%3Ddotencode%2Cuse-dirstate-v2%2Cfncache%2Cgeneraldelta%2Cpersistent-nodemap%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog%2Cstore (dirstate-v2 !) + none-v2;stream=v2;requirements%3Dgeneraldelta%2Crevlogv1%2Csparserevlog (no-zstd !) + none-v2;stream=v2;requirements%3Dgeneraldelta%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog (zstd no-rust !) + none-v2;stream=v2;requirements%3Dgeneraldelta%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog (rust !) Test that we can apply the bundle as a stream clone bundle diff --git a/tests/test-subrepo-deep-nested-change.t b/tests/test-subrepo-deep-nested-change.t --- a/tests/test-subrepo-deep-nested-change.t +++ b/tests/test-subrepo-deep-nested-change.t @@ -192,7 +192,7 @@ Clone main 3 files updated, 0 files merged, 0 files removed, 0 files unresolved Largefiles is NOT enabled in the clone if the source repo doesn't require it - $ grep largefiles cloned/.hg/hgrc + $ hg debugrequires -R cloned | grep largefiles [1] Checking cloned repo ids @@ -776,7 +776,7 @@ The local repo enables largefiles if a l extensions.largefiles= $ hg --config extensions.largefiles= clone -qU . ../lfclone - $ grep largefiles ../lfclone/.hg/requires + $ hg debugrequires -R ../lfclone | grep largefiles largefiles Find an exact match to a standin (should archive nothing) diff --git a/tests/test-subrepo.t b/tests/test-subrepo.t --- a/tests/test-subrepo.t +++ b/tests/test-subrepo.t @@ -278,7 +278,7 @@ merge tests branchmerge: True, force: False, partial: False ancestor: 1f14a2e2d3ec, local: f0d2028bf86d+, remote: 1831e14459c4 starting 4 threads for background file closing (?) - .hgsubstate: versions differ -> m (premerge) + .hgsubstate: versions differ -> m subrepo merge f0d2028bf86d+ 1831e14459c4 1f14a2e2d3ec subrepo t: other changed, get t:6747d179aa9a688023c4b0cad32e4c92bb7f34ad:hg getting subrepo t @@ -304,7 +304,7 @@ merge tests branchmerge: True, force: False, partial: False ancestor: 1831e14459c4, local: e45c8b14af55+, remote: f94576341bcf starting 4 threads for background file closing (?) - .hgsubstate: versions differ -> m (premerge) + .hgsubstate: versions differ -> m subrepo merge e45c8b14af55+ f94576341bcf 1831e14459c4 subrepo t: both sides changed subrepository t diverged (local revision: 20a0db6fbf6c, remote revision: 7af322bc1198) @@ -317,13 +317,10 @@ merge tests ancestor: 6747d179aa9a, local: 20a0db6fbf6c+, remote: 7af322bc1198 starting 4 threads for background file closing (?) preserving t for resolve of t - t: versions differ -> m (premerge) + t: versions differ -> m picked tool ':merge' for t (binary False symlink False changedelete False) merging t my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a - t: versions differ -> m (merge) - picked tool ':merge' for t (binary False symlink False changedelete False) - my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a warning: conflicts while merging t! (edit, then use 'hg resolve --mark') 0 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 @@ -1021,37 +1018,21 @@ Issue1977: multirepo push should fail if test if untracked file is not overwritten -(this also tests that updated .hgsubstate is treated as "modified", -when 'merge.update()' is aborted before 'merge.recordupdates()', even -if none of mode, size and timestamp of it isn't changed on the -filesystem (see also issue4583)) +(this tests also has a change to update .hgsubstate and merge it within the +same second. It should mark is are modified , even if none of mode, size and +timestamp of it isn't changed on the filesystem (see also issue4583)) $ echo issue3276_ok > repo/s/b $ hg -R repo2 push -f -q - $ touch -t 200001010000 repo/.hgsubstate - $ cat >> repo/.hg/hgrc < [fakedirstatewritetime] - > # emulate invoking dirstate.write() via repo.status() - > # at 2000-01-01 00:00 - > fakenow = 200001010000 - > - > [extensions] - > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py - > EOF $ hg -R repo update b: untracked file differs abort: untracked files in working directory differ from files in requested revision (in subrepository "s") [255] - $ cat >> repo/.hg/hgrc < [extensions] - > fakedirstatewritetime = ! - > EOF $ cat repo/s/b issue3276_ok $ rm repo/s/b - $ touch -t 200001010000 repo/.hgsubstate $ hg -R repo revert --all reverting repo/.hgsubstate reverting subrepo s diff --git a/tests/test-tags.t b/tests/test-tags.t --- a/tests/test-tags.t +++ b/tests/test-tags.t @@ -146,12 +146,12 @@ Tag cache debug info written to blackbox $ hg identify b9154636be93 tip $ hg blackbox -l 6 - 1970/01/01 00:00:00 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> identify - 1970/01/01 00:00:00 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> writing 48 bytes to cache/hgtagsfnodes1 - 1970/01/01 00:00:00 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> 0/2 cache hits/lookups in * seconds (glob) - 1970/01/01 00:00:00 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> writing .hg/cache/tags2-visible with 1 tags - 1970/01/01 00:00:00 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> identify exited 0 after * seconds (glob) - 1970/01/01 00:00:00 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> blackbox -l 6 + 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> identify + 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> writing 48 bytes to cache/hgtagsfnodes1 + 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> 0/2 cache hits/lookups in * seconds (glob) + 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> writing .hg/cache/tags2-visible with 1 tags + 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> identify exited 0 after * seconds (glob) + 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> blackbox -l 6 Failure to acquire lock results in no write @@ -160,12 +160,12 @@ Failure to acquire lock results in no wr $ hg identify b9154636be93 tip $ hg blackbox -l 6 - 1970/01/01 00:00:00 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> identify - 1970/01/01 00:00:00 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> not writing .hg/cache/hgtagsfnodes1 because lock cannot be acquired - 1970/01/01 00:00:00 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> 0/2 cache hits/lookups in * seconds (glob) - 1970/01/01 00:00:00 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> writing .hg/cache/tags2-visible with 1 tags - 1970/01/01 00:00:00 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> identify exited 0 after * seconds (glob) - 1970/01/01 00:00:00 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> blackbox -l 6 + 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> identify + 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> not writing .hg/cache/hgtagsfnodes1 because lock cannot be acquired + 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> 0/2 cache hits/lookups in * seconds (glob) + 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> writing .hg/cache/tags2-visible with 1 tags + 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> identify exited 0 after * seconds (glob) + 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> blackbox -l 6 $ fnodescacheexists no fnodes cache @@ -226,9 +226,9 @@ Merge the two heads: 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) $ hg blackbox -l3 - 1970/01/01 00:00:00 bob @c8edf04160c7f731e4589d66ab3ab3486a64ac28 (5000)> merge 1 - 1970/01/01 00:00:00 bob @c8edf04160c7f731e4589d66ab3ab3486a64ac28+b9154636be938d3d431e75a7c906504a079bfe07 (5000)> merge 1 exited 0 after * seconds (glob) - 1970/01/01 00:00:00 bob @c8edf04160c7f731e4589d66ab3ab3486a64ac28+b9154636be938d3d431e75a7c906504a079bfe07 (5000)> blackbox -l3 + 1970-01-01 00:00:00.000 bob @c8edf04160c7f731e4589d66ab3ab3486a64ac28 (5000)> merge 1 + 1970-01-01 00:00:00.000 bob @c8edf04160c7f731e4589d66ab3ab3486a64ac28+b9154636be938d3d431e75a7c906504a079bfe07 (5000)> merge 1 exited 0 after * seconds (glob) + 1970-01-01 00:00:00.000 bob @c8edf04160c7f731e4589d66ab3ab3486a64ac28+b9154636be938d3d431e75a7c906504a079bfe07 (5000)> blackbox -l3 $ hg id c8edf04160c7+b9154636be93+ tip $ hg status @@ -364,12 +364,12 @@ Extra junk data at the end should get ov bar 1:78391a272241 $ hg blackbox -l 6 - 1970/01/01 00:00:00 bob @8dbfe60eff306a54259cfe007db9e330e7ecf866 (5000)> tags - 1970/01/01 00:00:00 bob @8dbfe60eff306a54259cfe007db9e330e7ecf866 (5000)> writing 24 bytes to cache/hgtagsfnodes1 - 1970/01/01 00:00:00 bob @8dbfe60eff306a54259cfe007db9e330e7ecf866 (5000)> 3/4 cache hits/lookups in * seconds (glob) - 1970/01/01 00:00:00 bob @8dbfe60eff306a54259cfe007db9e330e7ecf866 (5000)> writing .hg/cache/tags2-visible with 1 tags - 1970/01/01 00:00:00 bob @8dbfe60eff306a54259cfe007db9e330e7ecf866 (5000)> tags exited 0 after * seconds (glob) - 1970/01/01 00:00:00 bob @8dbfe60eff306a54259cfe007db9e330e7ecf866 (5000)> blackbox -l 6 + 1970-01-01 00:00:00.000 bob @8dbfe60eff306a54259cfe007db9e330e7ecf866 (5000)> tags + 1970-01-01 00:00:00.000 bob @8dbfe60eff306a54259cfe007db9e330e7ecf866 (5000)> writing 24 bytes to cache/hgtagsfnodes1 + 1970-01-01 00:00:00.000 bob @8dbfe60eff306a54259cfe007db9e330e7ecf866 (5000)> 3/4 cache hits/lookups in * seconds (glob) + 1970-01-01 00:00:00.000 bob @8dbfe60eff306a54259cfe007db9e330e7ecf866 (5000)> writing .hg/cache/tags2-visible with 1 tags + 1970-01-01 00:00:00.000 bob @8dbfe60eff306a54259cfe007db9e330e7ecf866 (5000)> tags exited 0 after * seconds (glob) + 1970-01-01 00:00:00.000 bob @8dbfe60eff306a54259cfe007db9e330e7ecf866 (5000)> blackbox -l 6 On junk data + missing cache entries, hg also overwrites the junk. @@ -474,12 +474,12 @@ Errors writing to .hgtags fnodes cache a bar 1:78391a272241 $ 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)> 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 + 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> tags + 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> couldn't write cache/hgtagsfnodes1: [Errno *] * (glob) + 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> 2/4 cache hits/lookups in * seconds (glob) + 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> writing .hg/cache/tags2-visible with 1 tags + 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> tags exited 0 after * seconds (glob) + 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> blackbox -l 6 $ chmod a+w .hg/cache/hgtagsfnodes1 @@ -489,12 +489,12 @@ Errors writing to .hgtags fnodes cache a bar 1:78391a272241 $ 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)> 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 + 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> tags + 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> writing 24 bytes to cache/hgtagsfnodes1 + 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> 2/4 cache hits/lookups in * seconds (glob) + 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> writing .hg/cache/tags2-visible with 1 tags + 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> tags exited 0 after * seconds (glob) + 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> blackbox -l 6 $ f --size .hg/cache/hgtagsfnodes1 .hg/cache/hgtagsfnodes1: size=168 @@ -518,11 +518,11 @@ Stripping doesn't truncate the tags cach bar 1:78391a272241 $ hg blackbox -l 5 - 1970/01/01 00:00:00 bob @0c192d7d5e6b78a714de54a2e9627952a877e25a (5000)> writing 24 bytes to cache/hgtagsfnodes1 - 1970/01/01 00:00:00 bob @0c192d7d5e6b78a714de54a2e9627952a877e25a (5000)> 2/4 cache hits/lookups in * seconds (glob) - 1970/01/01 00:00:00 bob @0c192d7d5e6b78a714de54a2e9627952a877e25a (5000)> writing .hg/cache/tags2-visible with 1 tags - 1970/01/01 00:00:00 bob @0c192d7d5e6b78a714de54a2e9627952a877e25a (5000)> tags exited 0 after * seconds (glob) - 1970/01/01 00:00:00 bob @0c192d7d5e6b78a714de54a2e9627952a877e25a (5000)> blackbox -l 5 + 1970-01-01 00:00:00.000 bob @0c192d7d5e6b78a714de54a2e9627952a877e25a (5000)> writing 24 bytes to cache/hgtagsfnodes1 + 1970-01-01 00:00:00.000 bob @0c192d7d5e6b78a714de54a2e9627952a877e25a (5000)> 2/4 cache hits/lookups in * seconds (glob) + 1970-01-01 00:00:00.000 bob @0c192d7d5e6b78a714de54a2e9627952a877e25a (5000)> writing .hg/cache/tags2-visible with 1 tags + 1970-01-01 00:00:00.000 bob @0c192d7d5e6b78a714de54a2e9627952a877e25a (5000)> tags exited 0 after * seconds (glob) + 1970-01-01 00:00:00.000 bob @0c192d7d5e6b78a714de54a2e9627952a877e25a (5000)> blackbox -l 5 $ f --size .hg/cache/hgtagsfnodes1 .hg/cache/hgtagsfnodes1: size=120 @@ -535,12 +535,12 @@ Stripping doesn't truncate the tags cach bar 1:78391a272241 $ hg blackbox -l 6 - 1970/01/01 00:00:00 bob @035f65efb448350f4772141702a81ab1df48c465 (5000)> tags - 1970/01/01 00:00:00 bob @035f65efb448350f4772141702a81ab1df48c465 (5000)> writing 24 bytes to cache/hgtagsfnodes1 - 1970/01/01 00:00:00 bob @035f65efb448350f4772141702a81ab1df48c465 (5000)> 3/4 cache hits/lookups in * seconds (glob) - 1970/01/01 00:00:00 bob @035f65efb448350f4772141702a81ab1df48c465 (5000)> writing .hg/cache/tags2-visible with 1 tags - 1970/01/01 00:00:00 bob @035f65efb448350f4772141702a81ab1df48c465 (5000)> tags exited 0 after * seconds (glob) - 1970/01/01 00:00:00 bob @035f65efb448350f4772141702a81ab1df48c465 (5000)> blackbox -l 6 + 1970-01-01 00:00:00.000 bob @035f65efb448350f4772141702a81ab1df48c465 (5000)> tags + 1970-01-01 00:00:00.000 bob @035f65efb448350f4772141702a81ab1df48c465 (5000)> writing 24 bytes to cache/hgtagsfnodes1 + 1970-01-01 00:00:00.000 bob @035f65efb448350f4772141702a81ab1df48c465 (5000)> 3/4 cache hits/lookups in * seconds (glob) + 1970-01-01 00:00:00.000 bob @035f65efb448350f4772141702a81ab1df48c465 (5000)> writing .hg/cache/tags2-visible with 1 tags + 1970-01-01 00:00:00.000 bob @035f65efb448350f4772141702a81ab1df48c465 (5000)> tags exited 0 after * seconds (glob) + 1970-01-01 00:00:00.000 bob @035f65efb448350f4772141702a81ab1df48c465 (5000)> blackbox -l 6 $ f --size .hg/cache/hgtagsfnodes1 .hg/cache/hgtagsfnodes1: size=144 diff --git a/tests/test-template-functions.t b/tests/test-template-functions.t --- a/tests/test-template-functions.t +++ b/tests/test-template-functions.t @@ -1295,10 +1295,10 @@ but a filtered one doesn't -1 $ hg log -T '{revset("%d", rev + 1)}\n' -r'tip' abort: unknown revision '3' - [255] + [10] $ hg log -T '{revset("%d", rev - 1)}\n' -r'null' abort: unknown revision '-2' - [255] + [10] Invalid arguments passed to revset() diff --git a/tests/test-transplant.t b/tests/test-transplant.t --- a/tests/test-transplant.t +++ b/tests/test-transplant.t @@ -1063,7 +1063,7 @@ timestamp of them isn't changed on the f $ cat r1 Y1 $ hg debugstate | grep ' r1$' - n 644 3 unset r1 + n 0 -1 unset r1 $ hg status -A r1 M r1 diff --git a/tests/test-treemanifest.t b/tests/test-treemanifest.t --- a/tests/test-treemanifest.t +++ b/tests/test-treemanifest.t @@ -5,7 +5,7 @@ Set up repo Requirements get set on init - $ grep treemanifest .hg/requires + $ hg debugrequires | grep treemanifest treemanifest Without directories, looks like any other repo @@ -229,7 +229,7 @@ Create clone with tree manifests enabled $ cd repo-mixed $ test -d .hg/store/meta [1] - $ grep treemanifest .hg/requires + $ hg debugrequires | grep treemanifest treemanifest Should be possible to push updates from flat to tree manifest repo @@ -373,7 +373,7 @@ Pushing from treemanifest repo to an emp > [experimental] > changegroup3=yes > EOF - $ grep treemanifest empty-repo/.hg/requires + $ hg debugrequires -R empty-repo | grep treemanifest [1] $ hg push -R repo -r 0 empty-repo pushing to empty-repo @@ -382,13 +382,13 @@ Pushing from treemanifest repo to an emp adding manifests adding file changes added 1 changesets with 2 changes to 2 files - $ grep treemanifest empty-repo/.hg/requires + $ hg debugrequires -R empty-repo | grep treemanifest treemanifest Pushing to an empty repo works $ hg --config experimental.treemanifest=1 init clone - $ grep treemanifest clone/.hg/requires + $ hg debugrequires -R clone | grep treemanifest treemanifest $ hg push -R repo clone pushing to clone @@ -397,7 +397,7 @@ Pushing to an empty repo works adding manifests adding file changes added 11 changesets with 15 changes to 10 files (+3 heads) - $ grep treemanifest clone/.hg/requires + $ hg debugrequires -R clone | grep treemanifest treemanifest $ hg -R clone verify checking changesets @@ -682,7 +682,7 @@ We can clone even with the knob turned o No server errors. $ cat deeprepo/errors.log requires got updated to include treemanifest - $ cat deepclone/.hg/requires | grep treemanifest + $ hg debugrequires -R deepclone | grep treemanifest treemanifest Tree manifest revlogs exist. $ find deepclone/.hg/store/meta | sort @@ -730,7 +730,7 @@ Create clones using old repo formats to updating to branch default 8 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd deeprepo-basicstore - $ grep store .hg/requires + $ hg debugrequires | grep store [1] $ hg serve -p $HGPORT1 -d --pid-file=hg.pid --errorlog=errors.log $ cat hg.pid >> $DAEMON_PIDS @@ -747,7 +747,7 @@ Create clones using old repo formats to updating to branch default 8 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd deeprepo-encodedstore - $ grep fncache .hg/requires + $ hg debugrequires | grep fncache [1] $ hg serve -p $HGPORT2 -d --pid-file=hg.pid --errorlog=errors.log $ cat hg.pid >> $DAEMON_PIDS @@ -829,11 +829,9 @@ Packed bundle $ hg -R deeprepo debugcreatestreamclonebundle repo-packed.hg 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 !) + bundle requirements:.* treemanifest(,.*)? (re) $ hg debugbundle --spec repo-packed.hg - none-packed1;requirements%3Dgeneraldelta%2Crevlogv1%2Csparserevlog%2Ctreemanifest (no-rust !) - none-packed1;requirements%3Dgeneraldelta%2Cpersistent-nodemap%2Crevlogv1%2Csparserevlog%2Ctreemanifest (rust !) + none-packed1;requirements%3D(.*%2C)?treemanifest(%2C.*)? (re) #endif diff --git a/tests/test-up-local-change.t b/tests/test-up-local-change.t --- a/tests/test-up-local-change.t +++ b/tests/test-up-local-change.t @@ -46,13 +46,10 @@ b: remote created -> g getting b preserving a for resolve of a - a: versions differ -> m (premerge) + a: versions differ -> m picked tool 'true' for a (binary False symlink False changedelete False) merging a my a@c19d34741b0a+ other a@1e71731e6fbb ancestor a@c19d34741b0a - a: versions differ -> m (merge) - picked tool 'true' for a (binary False symlink False changedelete False) - my a@c19d34741b0a+ other a@1e71731e6fbb ancestor a@c19d34741b0a launching merge tool: true *$TESTTMP/r2/a* * * (glob) merge tool returned: 0 1 files updated, 1 files merged, 0 files removed, 0 files unresolved @@ -72,13 +69,10 @@ removing b starting 4 threads for background file closing (?) preserving a for resolve of a - a: versions differ -> m (premerge) + a: versions differ -> m picked tool 'true' for a (binary False symlink False changedelete False) merging a my a@1e71731e6fbb+ other a@c19d34741b0a ancestor a@1e71731e6fbb - a: versions differ -> m (merge) - picked tool 'true' for a (binary False symlink False changedelete False) - my a@1e71731e6fbb+ other a@c19d34741b0a ancestor a@1e71731e6fbb launching merge tool: true *$TESTTMP/r2/a* * * (glob) merge tool returned: 0 0 files updated, 1 files merged, 1 files removed, 0 files unresolved @@ -95,13 +89,10 @@ b: remote created -> g getting b preserving a for resolve of a - a: versions differ -> m (premerge) + a: versions differ -> m picked tool 'true' for a (binary False symlink False changedelete False) merging a my a@c19d34741b0a+ other a@1e71731e6fbb ancestor a@c19d34741b0a - a: versions differ -> m (merge) - picked tool 'true' for a (binary False symlink False changedelete False) - my a@c19d34741b0a+ other a@1e71731e6fbb ancestor a@c19d34741b0a launching merge tool: true *$TESTTMP/r2/a* * * (glob) merge tool returned: 0 1 files updated, 1 files merged, 0 files removed, 0 files unresolved diff --git a/tests/test-update-branches.t b/tests/test-update-branches.t --- a/tests/test-update-branches.t +++ b/tests/test-update-branches.t @@ -158,47 +158,47 @@ Cases are run as shown in that table, ro parent=3 M sub/suba - $ revtest '-C dirty linear' dirty 1 2 -C + $ revtest '--clean dirty linear' dirty 1 2 --clean 2 files updated, 0 files merged, 0 files removed, 0 files unresolved parent=2 - $ revtest '-c dirty linear' dirty 1 2 -c + $ revtest '--check dirty linear' dirty 1 2 --check abort: uncommitted changes parent=1 M foo - $ revtest '-m dirty linear' dirty 1 2 -m + $ revtest '--merge dirty linear' dirty 1 2 --merge 1 files updated, 0 files merged, 0 files removed, 0 files unresolved parent=2 M foo - $ revtest '-m dirty cross' dirty 3 4 -m + $ revtest '--merge dirty cross' dirty 3 4 --merge 1 files updated, 0 files merged, 0 files removed, 0 files unresolved parent=4 M foo - $ revtest '-c dirtysub linear' dirtysub 1 2 -c + $ revtest '--check dirtysub linear' dirtysub 1 2 --check abort: uncommitted changes in subrepository "sub" parent=1 M sub/suba - $ norevtest '-c clean same' clean 2 -c + $ norevtest '--check clean same' clean 2 -c 0 files updated, 0 files merged, 0 files removed, 0 files unresolved updated to "bd10386d478c: 2" 1 other heads for branch "default" parent=2 - $ revtest '-cC dirty linear' dirty 1 2 -cC + $ revtest '--check --clean dirty linear' dirty 1 2 "--check --clean" abort: cannot specify both --clean and --check parent=1 M foo - $ revtest '-mc dirty linear' dirty 1 2 -mc + $ revtest '--merge -checkc dirty linear' dirty 1 2 "--merge --check" abort: cannot specify both --check and --merge parent=1 M foo - $ revtest '-mC dirty linear' dirty 1 2 -mC + $ revtest '--merge -clean dirty linear' dirty 1 2 "--merge --clean" abort: cannot specify both --clean and --merge parent=1 M foo @@ -211,12 +211,27 @@ Cases are run as shown in that table, ro parent=1 M foo - $ revtest 'none dirty linear' dirty 1 2 -c + $ revtest 'none dirty linear' dirty 1 2 --check + abort: uncommitted changes + parent=1 + M foo + + $ revtest '--merge none dirty linear' dirty 1 2 --check abort: uncommitted changes parent=1 M foo - $ revtest 'none dirty linear' dirty 1 2 -C + $ revtest '--merge none dirty linear' dirty 1 2 --merge + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + parent=2 + M foo + + $ revtest '--merge none dirty linear' dirty 1 2 --no-check + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + parent=2 + M foo + + $ revtest 'none dirty linear' dirty 1 2 --clean 2 files updated, 0 files merged, 0 files removed, 0 files unresolved parent=2 @@ -232,12 +247,17 @@ Cases are run as shown in that table, ro parent=2 M foo - $ revtest 'none dirty linear' dirty 1 2 -c + $ revtest 'none dirty linear' dirty 1 2 --check abort: uncommitted changes parent=1 M foo - $ revtest 'none dirty linear' dirty 1 2 -C + $ revtest 'none dirty linear' dirty 1 2 --no-merge + abort: uncommitted changes + parent=1 + M foo + + $ revtest 'none dirty linear' dirty 1 2 --clean 2 files updated, 0 files merged, 0 files removed, 0 files unresolved parent=2 @@ -272,14 +292,14 @@ Cases are run as shown in that table, ro # To mark files as resolved: hg resolve --mark FILE $ cat a - <<<<<<< working copy: 6efa171f091b - test: 3 + <<<<<<< working copy: 6efa171f091b - test: 3 three dirty - ||||||| base + ||||||| working copy parent: 6efa171f091b - test: 3 three ======= four - >>>>>>> destination: d047485b3896 b1 - test: 4 + >>>>>>> destination: d047485b3896 b1 - test: 4 $ rm a.orig $ echo 'update.check = noconflict' >> .hg/hgrc @@ -676,9 +696,8 @@ Test that 5 is not detected as a valid d (commit or update --clean to discard changes) [255] -Test that we don't crash when updating from a pruned changeset (i.e. has no -successors). Behavior should probably be that we update to the first -non-obsolete parent but that will be decided later. +Test that we update to the closest non-obsolete ancestor when updating from a +pruned changeset (i.e. that has no successors) $ hg id --debug -r 2 bd10386d478cd5a9faf2e604114c8e6da62d3889 $ hg up --quiet 0 @@ -686,21 +705,18 @@ non-obsolete parent but that will be dec $ hg debugobsolete bd10386d478cd5a9faf2e604114c8e6da62d3889 1 new obsolescence markers obsoleted 1 changesets + $ hg log -r '_destupdate()' + 1:0786582aa4b1 1 (no-eol) $ hg up - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - -Test experimental revset support - - $ hg log -r '_destupdate()' - 2:bd10386d478c 2 (no-eol) + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved Test that boolean flags allow --no-flag specification to override [defaults] $ cat >> $HGRCPATH < [defaults] > update = --check > EOF - $ hg co 2 + $ hg co 1 abort: uncommitted changes [20] - $ hg co --no-check 2 + $ hg co --no-check 1 0 files updated, 0 files merged, 0 files removed, 0 files unresolved diff --git a/tests/test-upgrade-repo.t b/tests/test-upgrade-repo.t --- a/tests/test-upgrade-repo.t +++ b/tests/test-upgrade-repo.t @@ -6,6 +6,8 @@ > [format] > # stabilize test accross variant > revlog-compression=zlib + > [storage] + > dirstate-v2.slow-path=allow > EOF store and revlogv1 are required in source @@ -30,12 +32,166 @@ store and revlogv1 are required in sourc Cannot upgrade shared repositories $ hg init share-parent + $ hg -R share-parent debugbuilddag -n .+9 + $ hg -R share-parent up tip + 10 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg -q share share-parent share-child - $ hg -R share-child debugupgraderepo - abort: cannot upgrade repository; unsupported source requirement: shared + $ hg -R share-child debugupgraderepo --config format.sparse-revlog=no + abort: cannot use these actions on a share repository: sparserevlog + (upgrade the main repository directly) [255] +Unless the action is compatible with share + + $ hg -R share-child debugupgraderepo --config format.use-dirstate-v2=yes --quiet + requirements + preserved: * (glob) + added: dirstate-v2 + + no revlogs to process + + + $ hg -R share-child debugupgraderepo --config format.use-dirstate-v2=yes --quiet --run + upgrade will perform the following actions: + + requirements + preserved: * (glob) + added: dirstate-v2 + + no revlogs to process + + $ hg debugformat -R share-child | grep dirstate-v2 + dirstate-v2: yes + $ hg debugformat -R share-parent | grep dirstate-v2 + dirstate-v2: no + $ hg status --all -R share-child + C nf0 + C nf1 + C nf2 + C nf3 + C nf4 + C nf5 + C nf6 + C nf7 + C nf8 + C nf9 + $ hg log -l 3 -R share-child + changeset: 9:0059eb38e4a4 + tag: tip + user: debugbuilddag + date: Thu Jan 01 00:00:09 1970 +0000 + summary: r9 + + changeset: 8:4d5be70c8130 + user: debugbuilddag + date: Thu Jan 01 00:00:08 1970 +0000 + summary: r8 + + changeset: 7:e60bfe72517e + user: debugbuilddag + date: Thu Jan 01 00:00:07 1970 +0000 + summary: r7 + + $ hg status --all -R share-parent + C nf0 + C nf1 + C nf2 + C nf3 + C nf4 + C nf5 + C nf6 + C nf7 + C nf8 + C nf9 + $ hg log -l 3 -R share-parent + changeset: 9:0059eb38e4a4 + tag: tip + user: debugbuilddag + date: Thu Jan 01 00:00:09 1970 +0000 + summary: r9 + + changeset: 8:4d5be70c8130 + user: debugbuilddag + date: Thu Jan 01 00:00:08 1970 +0000 + summary: r8 + + changeset: 7:e60bfe72517e + user: debugbuilddag + date: Thu Jan 01 00:00:07 1970 +0000 + summary: r7 + + + $ hg -R share-child debugupgraderepo --config format.use-dirstate-v2=no --quiet --run + upgrade will perform the following actions: + + requirements + preserved: * (glob) + removed: dirstate-v2 + + no revlogs to process + + $ hg debugformat -R share-child | grep dirstate-v2 + dirstate-v2: no + $ hg debugformat -R share-parent | grep dirstate-v2 + dirstate-v2: no + $ hg status --all -R share-child + C nf0 + C nf1 + C nf2 + C nf3 + C nf4 + C nf5 + C nf6 + C nf7 + C nf8 + C nf9 + $ hg log -l 3 -R share-child + changeset: 9:0059eb38e4a4 + tag: tip + user: debugbuilddag + date: Thu Jan 01 00:00:09 1970 +0000 + summary: r9 + + changeset: 8:4d5be70c8130 + user: debugbuilddag + date: Thu Jan 01 00:00:08 1970 +0000 + summary: r8 + + changeset: 7:e60bfe72517e + user: debugbuilddag + date: Thu Jan 01 00:00:07 1970 +0000 + summary: r7 + + $ hg status --all -R share-parent + C nf0 + C nf1 + C nf2 + C nf3 + C nf4 + C nf5 + C nf6 + C nf7 + C nf8 + C nf9 + $ hg log -l 3 -R share-parent + changeset: 9:0059eb38e4a4 + tag: tip + user: debugbuilddag + date: Thu Jan 01 00:00:09 1970 +0000 + summary: r9 + + changeset: 8:4d5be70c8130 + user: debugbuilddag + date: Thu Jan 01 00:00:08 1970 +0000 + summary: r8 + + changeset: 7:e60bfe72517e + user: debugbuilddag + date: Thu Jan 01 00:00:07 1970 +0000 + summary: r7 + + Do not yet support upgrading treemanifest repos $ hg --config experimental.treemanifest=true init treemanifest @@ -58,9 +214,10 @@ An upgrade of a repository created with format-variant repo fncache: yes dirstate-v2: no + tracked-hint: no dotencode: yes generaldelta: yes - share-safe: no + share-safe: yes sparserevlog: yes persistent-nodemap: no (no-rust !) persistent-nodemap: yes (rust !) @@ -74,9 +231,10 @@ An upgrade of a repository created with format-variant repo config default fncache: yes yes yes dirstate-v2: no no no + tracked-hint: no no no dotencode: yes yes yes generaldelta: yes yes yes - share-safe: no no no + share-safe: yes yes yes sparserevlog: yes yes yes persistent-nodemap: no no no (no-rust !) persistent-nodemap: yes yes no (rust !) @@ -91,9 +249,10 @@ An upgrade of a repository created with format-variant repo config default fncache: yes no yes dirstate-v2: no no no + tracked-hint: no no no dotencode: yes no yes generaldelta: yes yes yes - share-safe: no no no + share-safe: yes yes yes sparserevlog: yes yes yes persistent-nodemap: no no no (no-rust !) persistent-nodemap: yes yes no (rust !) @@ -108,9 +267,10 @@ An upgrade of a repository created with format-variant repo config default [formatvariant.name.mismatchconfig|fncache: ][formatvariant.repo.mismatchconfig| yes][formatvariant.config.special| no][formatvariant.default| yes] [formatvariant.name.uptodate|dirstate-v2: ][formatvariant.repo.uptodate| no][formatvariant.config.default| no][formatvariant.default| no] + [formatvariant.name.uptodate|tracked-hint: ][formatvariant.repo.uptodate| no][formatvariant.config.default| no][formatvariant.default| no] [formatvariant.name.mismatchconfig|dotencode: ][formatvariant.repo.mismatchconfig| yes][formatvariant.config.special| no][formatvariant.default| yes] [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|share-safe: ][formatvariant.repo.uptodate| yes][formatvariant.config.default| yes][formatvariant.default| yes] [formatvariant.name.uptodate|sparserevlog: ][formatvariant.repo.uptodate| yes][formatvariant.config.default| yes][formatvariant.default| yes] [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 !) @@ -136,6 +296,12 @@ An upgrade of a repository created with "repo": false }, { + "config": false, + "default": false, + "name": "tracked-hint", + "repo": false + }, + { "config": true, "default": true, "name": "dotencode", @@ -148,10 +314,10 @@ An upgrade of a repository created with "repo": true }, { - "config": false, - "default": false, + "config": true, + "default": true, "name": "share-safe", - "repo": false + "repo": true }, { "config": true, @@ -210,13 +376,10 @@ An upgrade of a repository created with performing an upgrade with "--run" will make the following changes: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-rust !) - preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store (rust !) + preserved: dotencode, fncache, generaldelta, revlogv1, share-safe, sparserevlog, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, share-safe, sparserevlog, store (rust !) - processed revlogs: - - all-filelogs - - changelog - - manifest + no revlogs to process additional optimizations are available by specifying "--optimize ": @@ -235,13 +398,10 @@ An upgrade of a repository created with $ hg debugupgraderepo --quiet requirements - preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-rust !) - preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store (rust !) + preserved: dotencode, fncache, generaldelta, revlogv1, share-safe, sparserevlog, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, share-safe, sparserevlog, store (rust !) - processed revlogs: - - all-filelogs - - changelog - - manifest + no revlogs to process --optimize can be used to add optimizations @@ -251,8 +411,8 @@ An upgrade of a repository created with performing an upgrade with "--run" will make the following changes: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-rust !) - preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store (rust !) + preserved: dotencode, fncache, generaldelta, revlogv1, share-safe, sparserevlog, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, share-safe, sparserevlog, store (rust !) optimisations: re-delta-parent @@ -283,8 +443,8 @@ modern form of the option performing an upgrade with "--run" will make the following changes: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-rust !) - preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store (rust !) + preserved: dotencode, fncache, generaldelta, revlogv1, share-safe, sparserevlog, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, share-safe, sparserevlog, store (rust !) optimisations: re-delta-parent @@ -309,8 +469,8 @@ modern form of the option $ hg debugupgrade --optimize re-delta-parent --quiet requirements - preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-rust !) - preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store (rust !) + preserved: dotencode, fncache, generaldelta, revlogv1, share-safe, sparserevlog, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, share-safe, sparserevlog, store (rust !) optimisations: re-delta-parent @@ -338,6 +498,7 @@ Various sub-optimal detections work format-variant repo fncache: no dirstate-v2: no + tracked-hint: no dotencode: no generaldelta: no share-safe: no @@ -353,9 +514,10 @@ Various sub-optimal detections work format-variant repo config default fncache: no yes yes dirstate-v2: no no no + tracked-hint: no no no dotencode: no yes yes generaldelta: no yes yes - share-safe: no no no + share-safe: no yes yes sparserevlog: no yes yes persistent-nodemap: no no no (no-rust !) persistent-nodemap: no yes no (rust !) @@ -370,9 +532,10 @@ Various sub-optimal detections work format-variant repo config default fncache: no yes yes dirstate-v2: no no no + tracked-hint: no no no dotencode: no yes yes generaldelta: no no yes - share-safe: no no no + share-safe: no yes yes sparserevlog: no no yes persistent-nodemap: no no no (no-rust !) persistent-nodemap: no yes no (rust !) @@ -387,9 +550,10 @@ Various sub-optimal detections work format-variant repo config default [formatvariant.name.mismatchconfig|fncache: ][formatvariant.repo.mismatchconfig| no][formatvariant.config.default| yes][formatvariant.default| yes] [formatvariant.name.uptodate|dirstate-v2: ][formatvariant.repo.uptodate| no][formatvariant.config.default| no][formatvariant.default| no] + [formatvariant.name.uptodate|tracked-hint: ][formatvariant.repo.uptodate| no][formatvariant.config.default| no][formatvariant.default| no] [formatvariant.name.mismatchconfig|dotencode: ][formatvariant.repo.mismatchconfig| no][formatvariant.config.default| yes][formatvariant.default| yes] [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.mismatchconfig|share-safe: ][formatvariant.repo.mismatchconfig| no][formatvariant.config.default| yes][formatvariant.default| yes] [formatvariant.name.mismatchdefault|sparserevlog: ][formatvariant.repo.mismatchdefault| no][formatvariant.config.special| no][formatvariant.default| yes] [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 !) @@ -401,6 +565,10 @@ Various sub-optimal detections work [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 + note: selecting all-filelogs for processing to change: dotencode + note: selecting all-manifestlogs for processing to change: dotencode + note: selecting changelog for processing to change: dotencode + repository lacks features recommended by current config options: fncache @@ -412,6 +580,9 @@ Various sub-optimal detections work generaldelta deltas within internal storage are unable to choose optimal revisions; repository is larger and slower than it could be; interaction with other repositories may require extra network and CPU resources, making "hg push" and "hg pull" slower + share-safe + old shared repositories do not share source repository requirements and config. This leads to various problems when the source repository format is upgraded or some new extensions are enabled. + 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. @@ -423,8 +594,8 @@ Various sub-optimal detections work requirements preserved: revlogv1, store - added: dotencode, fncache, generaldelta, sparserevlog (no-rust !) - added: dotencode, fncache, generaldelta, persistent-nodemap, sparserevlog (rust !) + added: dotencode, fncache, generaldelta, share-safe, sparserevlog (no-rust !) + added: dotencode, fncache, generaldelta, persistent-nodemap, share-safe, sparserevlog (rust !) fncache repository will be more resilient to storing certain paths and performance of certain operations should be improved @@ -435,6 +606,9 @@ Various sub-optimal detections work generaldelta repository storage will be able to create optimal deltas; new repository data will be smaller and read times should decrease; interacting with other repositories using this storage model should require less network and CPU resources, making "hg push" and "hg pull" faster + share-safe + Upgrades a repository to share-safe format so that future shares of this repository share its requirements and configs. + 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. @@ -463,8 +637,8 @@ Various sub-optimal detections work $ hg debugupgraderepo --quiet requirements preserved: revlogv1, store - added: dotencode, fncache, generaldelta, sparserevlog (no-rust !) - added: dotencode, fncache, generaldelta, persistent-nodemap, sparserevlog (rust !) + added: dotencode, fncache, generaldelta, share-safe, sparserevlog (no-rust !) + added: dotencode, fncache, generaldelta, persistent-nodemap, share-safe, sparserevlog (rust !) processed revlogs: - all-filelogs @@ -473,6 +647,10 @@ Various sub-optimal detections work $ hg --config format.dotencode=false debugupgraderepo + note: selecting all-filelogs for processing to change: fncache + note: selecting all-manifestlogs for processing to change: fncache + note: selecting changelog for processing to change: fncache + repository lacks features recommended by current config options: fncache @@ -481,6 +659,9 @@ Various sub-optimal detections work generaldelta deltas within internal storage are unable to choose optimal revisions; repository is larger and slower than it could be; interaction with other repositories may require extra network and CPU resources, making "hg push" and "hg pull" slower + share-safe + old shared repositories do not share source repository requirements and config. This leads to various problems when the source repository format is upgraded or some new extensions are enabled. + 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. @@ -497,8 +678,8 @@ Various sub-optimal detections work requirements preserved: revlogv1, store - added: fncache, generaldelta, sparserevlog (no-rust !) - added: fncache, generaldelta, persistent-nodemap, sparserevlog (rust !) + added: fncache, generaldelta, share-safe, sparserevlog (no-rust !) + added: fncache, generaldelta, persistent-nodemap, share-safe, sparserevlog (rust !) fncache repository will be more resilient to storing certain paths and performance of certain operations should be improved @@ -506,6 +687,9 @@ Various sub-optimal detections work generaldelta repository storage will be able to create optimal deltas; new repository data will be smaller and read times should decrease; interacting with other repositories using this storage model should require less network and CPU resources, making "hg push" and "hg pull" faster + share-safe + Upgrades a repository to share-safe format so that future shares of this repository share its requirements and configs. + 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. @@ -567,11 +751,15 @@ make sure we have a .d file .hg/store/data/f2.i $ hg debugupgraderepo --run --config format.sparse-revlog=false + note: selecting all-filelogs for processing to change: generaldelta + note: selecting all-manifestlogs for processing to change: generaldelta + note: selecting changelog for processing to change: generaldelta + upgrade will perform the following actions: requirements - preserved: dotencode, fncache, revlogv1, store (no-rust !) - preserved: dotencode, fncache, persistent-nodemap, revlogv1, store (rust !) + preserved: dotencode, fncache, revlogv1, share-safe, store (no-rust !) + preserved: dotencode, fncache, persistent-nodemap, revlogv1, share-safe, store (rust !) added: generaldelta generaldelta @@ -596,6 +784,7 @@ make sure we have a .d file finished migrating 3 changelog revisions; change in size: 0 bytes finished migrating 9 total revisions; total change in store size: -17 bytes copying phaseroots + copying requires 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 @@ -610,20 +799,24 @@ make sure we have a .d file Original requirements backed up $ cat .hg/upgradebackup.*/requires + share-safe + $ cat .hg/upgradebackup.*/store/requires dotencode fncache persistent-nodemap (rust !) revlogv1 store + upgradeinprogress generaldelta added to original requirements files - $ cat .hg/requires + $ hg debugrequires dotencode fncache generaldelta persistent-nodemap (rust !) revlogv1 + share-safe store store directory has files we expect @@ -634,6 +827,7 @@ store directory has files we expect data fncache phaseroots + requires undo undo.backupfiles undo.phaseroots @@ -662,6 +856,7 @@ old store should be backed up data fncache phaseroots + requires undo undo.backup.fncache undo.backupfiles @@ -671,11 +866,15 @@ unless --no-backup is passed $ rm -rf .hg/upgradebackup.*/ $ hg debugupgraderepo --run --no-backup + note: selecting all-filelogs for processing to change: sparserevlog + note: selecting all-manifestlogs for processing to change: sparserevlog + note: selecting changelog for processing to change: sparserevlog + upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, store (no-rust !) - preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, store (rust !) + preserved: dotencode, fncache, generaldelta, revlogv1, share-safe, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, share-safe, store (rust !) added: sparserevlog sparserevlog @@ -700,6 +899,7 @@ unless --no-backup is passed finished migrating 3 changelog revisions; change in size: 0 bytes finished migrating 9 total revisions; total change in store size: 0 bytes copying phaseroots + copying requires 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 @@ -716,8 +916,8 @@ We can restrict optimization to some rev upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-rust !) - preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store (rust !) + preserved: dotencode, fncache, generaldelta, revlogv1, share-safe, sparserevlog, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, share-safe, sparserevlog, store (rust !) optimisations: re-delta-parent @@ -746,6 +946,7 @@ We can restrict optimization to some rev finished migrating 3 changelog revisions; change in size: 0 bytes finished migrating 9 total revisions; total change in store size: 0 bytes copying phaseroots + copying requires 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 @@ -794,8 +995,8 @@ Check we can select negatively upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-rust !) - preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store (rust !) + preserved: dotencode, fncache, generaldelta, revlogv1, share-safe, sparserevlog, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, share-safe, sparserevlog, store (rust !) optimisations: re-delta-parent @@ -825,6 +1026,7 @@ Check we can select negatively finished migrating 3 changelog revisions; change in size: 0 bytes finished migrating 9 total revisions; total change in store size: 0 bytes copying phaseroots + copying requires 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 @@ -845,8 +1047,8 @@ Check that we can select changelog only upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-rust !) - preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store (rust !) + preserved: dotencode, fncache, generaldelta, revlogv1, share-safe, sparserevlog, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, share-safe, sparserevlog, store (rust !) optimisations: re-delta-parent @@ -875,6 +1077,7 @@ Check that we can select changelog only finished migrating 3 changelog revisions; change in size: 0 bytes finished migrating 9 total revisions; total change in store size: 0 bytes copying phaseroots + copying requires 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 @@ -895,8 +1098,8 @@ Check that we can select filelog only upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-rust !) - preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store (rust !) + preserved: dotencode, fncache, generaldelta, revlogv1, share-safe, sparserevlog, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, share-safe, sparserevlog, store (rust !) optimisations: re-delta-parent @@ -925,6 +1128,7 @@ Check that we can select filelog only finished migrating 3 changelog revisions; change in size: 0 bytes finished migrating 9 total revisions; total change in store size: 0 bytes copying phaseroots + copying requires 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 @@ -944,13 +1148,30 @@ Check you can't skip revlog clone during $ echo "[format]" > .hg/hgrc $ echo "sparse-revlog=no" >> .hg/hgrc + $ hg debugupgrade --optimize re-delta-parent --no-manifest --no-backup --quiet + warning: ignoring --no-manifest, as upgrade is changing: sparserevlog + + requirements + preserved: dotencode, fncache, generaldelta, revlogv1, share-safe, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, share-safe, store (rust !) + removed: sparserevlog + + optimisations: re-delta-parent + + processed revlogs: + - all-filelogs + - changelog + - manifest + $ hg debugupgrade --optimize re-delta-parent --run --manifest --no-backup --debug --traceback - ignoring revlogs selection flags, format requirements change: sparserevlog + note: selecting all-filelogs for processing to change: sparserevlog + note: selecting changelog for processing to change: sparserevlog + upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, store (no-rust !) - preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, store (rust !) + preserved: dotencode, fncache, generaldelta, revlogv1, share-safe, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, share-safe, store (rust !) removed: sparserevlog optimisations: re-delta-parent @@ -982,6 +1203,7 @@ Check you can't skip revlog clone during finished migrating 3 changelog revisions; change in size: 0 bytes finished migrating 9 total revisions; total change in store size: 0 bytes copying phaseroots + copying requires 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 @@ -1000,12 +1222,14 @@ Check you can't skip revlog clone during $ echo "sparse-revlog=yes" >> .hg/hgrc $ hg debugupgrade --optimize re-delta-parent --run --manifest --no-backup --debug --traceback - ignoring revlogs selection flags, format requirements change: sparserevlog + note: selecting all-filelogs for processing to change: sparserevlog + note: selecting changelog for processing to change: sparserevlog + upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, store (no-rust !) - preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, store (rust !) + preserved: dotencode, fncache, generaldelta, revlogv1, share-safe, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, share-safe, store (rust !) added: sparserevlog optimisations: re-delta-parent @@ -1040,6 +1264,7 @@ Check you can't skip revlog clone during finished migrating 3 changelog revisions; change in size: 0 bytes finished migrating 9 total revisions; total change in store size: 0 bytes copying phaseroots + copying requires 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 @@ -1070,8 +1295,8 @@ store files with special filenames aren' upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-rust !) - preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store (rust !) + preserved: dotencode, fncache, generaldelta, revlogv1, share-safe, sparserevlog, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, share-safe, sparserevlog, store (rust !) optimisations: re-delta-fulladd @@ -1098,6 +1323,7 @@ store files with special filenames aren' finished migrating 3 total revisions; total change in store size: 0 bytes copying .XX_special_filename copying phaseroots + copying requires 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 @@ -1129,25 +1355,27 @@ Check upgrading a large file repository $ touch foo $ hg add --large foo $ hg -q commit -m initial - $ cat .hg/requires + $ hg debugrequires dotencode fncache generaldelta largefiles persistent-nodemap (rust !) revlogv1 + share-safe sparserevlog store $ hg debugupgraderepo --run nothing to do - $ cat .hg/requires + $ hg debugrequires dotencode fncache generaldelta largefiles persistent-nodemap (rust !) revlogv1 + share-safe sparserevlog store @@ -1160,7 +1388,7 @@ Check upgrading a large file repository $ echo '123456789012345' > lfs.bin $ hg ci -Am 'lfs.bin' adding lfs.bin - $ grep lfs .hg/requires + $ hg debugrequires | grep lfs lfs $ find .hg/store/lfs -type f .hg/store/lfs/objects/d0/beab232adff5ba365880366ad30b1edb85c4c5372442b5d2fe27adc96d653f @@ -1168,7 +1396,7 @@ Check upgrading a large file repository $ hg debugupgraderepo --run nothing to do - $ grep lfs .hg/requires + $ hg debugrequires | grep lfs lfs $ find .hg/store/lfs -type f .hg/store/lfs/objects/d0/beab232adff5ba365880366ad30b1edb85c4c5372442b5d2fe27adc96d653f @@ -1236,8 +1464,8 @@ repository config is taken in account upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-rust !) - preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store (rust !) + preserved: dotencode, fncache, generaldelta, revlogv1, share-safe, sparserevlog, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, share-safe, sparserevlog, store (rust !) optimisations: re-delta-all @@ -1263,6 +1491,7 @@ repository config is taken in account finished migrating 3 changelog revisions; change in size: 0 bytes finished migrating 9 total revisions; total change in store size: -9 bytes copying phaseroots + copying requires 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 @@ -1293,12 +1522,13 @@ Check upgrading a sparse-revlog reposito $ touch foo $ hg add foo $ hg -q commit -m "foo" - $ cat .hg/requires + $ hg debugrequires dotencode fncache generaldelta persistent-nodemap (rust !) revlogv1 + share-safe store Check that we can add the sparse-revlog format requirement @@ -1306,8 +1536,8 @@ Check that we can add the sparse-revlog upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, store (no-rust !) - preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, store (rust !) + preserved: dotencode, fncache, generaldelta, revlogv1, share-safe, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, share-safe, store (rust !) added: sparserevlog processed revlogs: @@ -1315,12 +1545,13 @@ Check that we can add the sparse-revlog - changelog - manifest - $ cat .hg/requires + $ hg debugrequires dotencode fncache generaldelta persistent-nodemap (rust !) revlogv1 + share-safe sparserevlog store @@ -1329,8 +1560,8 @@ Check that we can remove the sparse-revl upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, store (no-rust !) - preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, store (rust !) + preserved: dotencode, fncache, generaldelta, revlogv1, share-safe, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, share-safe, store (rust !) removed: sparserevlog processed revlogs: @@ -1338,12 +1569,13 @@ Check that we can remove the sparse-revl - changelog - manifest - $ cat .hg/requires + $ hg debugrequires dotencode fncache generaldelta persistent-nodemap (rust !) revlogv1 + share-safe store #if zstd @@ -1357,8 +1589,8 @@ upgrade upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, store (no-rust !) - preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, store (rust !) + preserved: dotencode, fncache, generaldelta, revlogv1, share-safe, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, share-safe, store (rust !) added: revlog-compression-zstd, sparserevlog processed revlogs: @@ -1370,9 +1602,10 @@ upgrade format-variant repo config default fncache: yes yes yes dirstate-v2: no no no + tracked-hint: no no no dotencode: yes yes yes generaldelta: yes yes yes - share-safe: no no no + share-safe: yes yes yes sparserevlog: yes yes yes persistent-nodemap: no no no (no-rust !) persistent-nodemap: yes yes no (rust !) @@ -1383,13 +1616,14 @@ upgrade compression: zlib zlib zlib (no-zstd !) compression: zstd zlib zstd (zstd !) compression-level: default default default - $ cat .hg/requires + $ hg debugrequires dotencode fncache generaldelta persistent-nodemap (rust !) revlog-compression-zstd revlogv1 + share-safe sparserevlog store @@ -1399,8 +1633,8 @@ downgrade upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-rust !) - preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store (rust !) + preserved: dotencode, fncache, generaldelta, revlogv1, share-safe, sparserevlog, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, share-safe, sparserevlog, store (rust !) removed: revlog-compression-zstd processed revlogs: @@ -1412,9 +1646,10 @@ downgrade format-variant repo config default fncache: yes yes yes dirstate-v2: no no no + tracked-hint: no no no dotencode: yes yes yes generaldelta: yes yes yes - share-safe: no no no + share-safe: yes yes yes sparserevlog: yes yes yes persistent-nodemap: no no no (no-rust !) persistent-nodemap: yes yes no (rust !) @@ -1425,12 +1660,13 @@ downgrade compression: zlib zlib zlib (no-zstd !) compression: zlib zlib zstd (zstd !) compression-level: default default default - $ cat .hg/requires + $ hg debugrequires dotencode fncache generaldelta persistent-nodemap (rust !) revlogv1 + share-safe sparserevlog store @@ -1444,8 +1680,8 @@ upgrade from hgrc upgrade will perform the following actions: requirements - preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-rust !) - preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store (rust !) + preserved: dotencode, fncache, generaldelta, revlogv1, share-safe, sparserevlog, store (no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, share-safe, sparserevlog, store (rust !) added: revlog-compression-zstd processed revlogs: @@ -1457,9 +1693,10 @@ upgrade from hgrc format-variant repo config default fncache: yes yes yes dirstate-v2: no no no + tracked-hint: no no no dotencode: yes yes yes generaldelta: yes yes yes - share-safe: no no no + share-safe: yes yes yes sparserevlog: yes yes yes persistent-nodemap: no no no (no-rust !) persistent-nodemap: yes yes no (rust !) @@ -1470,13 +1707,14 @@ upgrade from hgrc compression: zlib zlib zlib (no-zstd !) compression: zstd zstd zstd (zstd !) compression-level: default default default - $ cat .hg/requires + $ hg debugrequires dotencode fncache generaldelta persistent-nodemap (rust !) revlog-compression-zstd revlogv1 + share-safe sparserevlog store @@ -1492,9 +1730,9 @@ upgrade upgrade will perform the following actions: requirements - 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 !) + preserved: dotencode, fncache, generaldelta, share-safe, store (no-zstd !) + preserved: dotencode, fncache, generaldelta, revlog-compression-zstd, share-safe, sparserevlog, store (zstd no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlog-compression-zstd, share-safe, sparserevlog, store (rust !) removed: revlogv1 added: exp-revlogv2.2 (zstd !) added: exp-revlogv2.2, sparserevlog (no-zstd !) @@ -1508,9 +1746,10 @@ upgrade format-variant repo config default fncache: yes yes yes dirstate-v2: no no no + tracked-hint: no no no dotencode: yes yes yes generaldelta: yes yes yes - share-safe: no no no + share-safe: yes yes yes sparserevlog: yes yes yes persistent-nodemap: no no no (no-rust !) persistent-nodemap: yes yes no (rust !) @@ -1521,13 +1760,14 @@ upgrade compression: zlib zlib zlib (no-zstd !) compression: zstd zstd zstd (zstd !) compression-level: default default default - $ cat .hg/requires + $ hg debugrequires dotencode exp-revlogv2.2 fncache generaldelta persistent-nodemap (rust !) revlog-compression-zstd (zstd !) + share-safe sparserevlog store $ hg debugsidedata -c 0 @@ -1541,9 +1781,9 @@ downgrade upgrade will perform the following actions: requirements - 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 !) + preserved: dotencode, fncache, generaldelta, share-safe, sparserevlog, store (no-zstd !) + preserved: dotencode, fncache, generaldelta, revlog-compression-zstd, share-safe, sparserevlog, store (zstd no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlog-compression-zstd, share-safe, sparserevlog, store (rust !) removed: exp-revlogv2.2 added: revlogv1 @@ -1556,9 +1796,10 @@ downgrade format-variant repo config default fncache: yes yes yes dirstate-v2: no no no + tracked-hint: no no no dotencode: yes yes yes generaldelta: yes yes yes - share-safe: no no no + share-safe: yes yes yes sparserevlog: yes yes yes persistent-nodemap: no no no (no-rust !) persistent-nodemap: yes yes no (rust !) @@ -1569,13 +1810,14 @@ downgrade compression: zlib zlib zlib (no-zstd !) compression: zstd zstd zstd (zstd !) compression-level: default default default - $ cat .hg/requires + $ hg debugrequires dotencode fncache generaldelta persistent-nodemap (rust !) revlog-compression-zstd (zstd !) revlogv1 + share-safe sparserevlog store $ hg debugsidedata -c 0 @@ -1590,9 +1832,9 @@ upgrade from hgrc upgrade will perform the following actions: requirements - 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 !) + preserved: dotencode, fncache, generaldelta, share-safe, sparserevlog, store (no-zstd !) + preserved: dotencode, fncache, generaldelta, revlog-compression-zstd, share-safe, sparserevlog, store (zstd no-rust !) + preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlog-compression-zstd, share-safe, sparserevlog, store (rust !) removed: revlogv1 added: exp-revlogv2.2 @@ -1605,9 +1847,10 @@ upgrade from hgrc format-variant repo config default fncache: yes yes yes dirstate-v2: no no no + tracked-hint: no no no dotencode: yes yes yes generaldelta: yes yes yes - share-safe: no no no + share-safe: yes yes yes sparserevlog: yes yes yes persistent-nodemap: no no no (no-rust !) persistent-nodemap: yes yes no (rust !) @@ -1618,13 +1861,14 @@ upgrade from hgrc compression: zlib zlib zlib (no-zstd !) compression: zstd zstd zstd (zstd !) compression-level: default default default - $ cat .hg/requires + $ hg debugrequires dotencode exp-revlogv2.2 fncache generaldelta persistent-nodemap (rust !) revlog-compression-zstd (zstd !) + share-safe sparserevlog store $ hg debugsidedata -c 0 @@ -1657,10 +1901,7 @@ Upgrade to dirstate-v2 dirstate-v2 "hg status" will be faster - processed revlogs: - - all-filelogs - - changelog - - manifest + no revlogs to process beginning upgrade... repository locked and read-only @@ -1686,10 +1927,7 @@ Downgrade from dirstate-v2 preserved: * (glob) removed: dirstate-v2 - processed revlogs: - - all-filelogs - - changelog - - manifest + no revlogs to process beginning upgrade... repository locked and read-only @@ -1724,10 +1962,7 @@ upgrade dirstate-v2 "hg status" will be faster - processed revlogs: - - all-filelogs - - changelog - - manifest + no revlogs to process beginning upgrade... repository locked and read-only @@ -1748,10 +1983,7 @@ downgrade preserved: * (glob) removed: dirstate-v2 - processed revlogs: - - all-filelogs - - changelog - - manifest + no revlogs to process beginning upgrade... repository locked and read-only diff --git a/tests/test-walk.t b/tests/test-walk.t --- a/tests/test-walk.t +++ b/tests/test-walk.t @@ -299,10 +299,10 @@ f mammals/skunk skunk $ hg debugwalk -v .hg abort: path 'mammals/.hg' is inside nested repo 'mammals' - [255] + [10] $ hg debugwalk -v ../.hg abort: path contains illegal component: .hg - [255] + [10] $ cd .. $ hg debugwalk -v -Ibeans @@ -410,16 +410,16 @@ [255] $ hg debugwalk -v .hg abort: path contains illegal component: .hg - [255] + [10] $ hg debugwalk -v beans/../.hg abort: path contains illegal component: .hg - [255] + [10] $ hg debugwalk -v beans/../.hg/data abort: path contains illegal component: .hg/data - [255] + [10] $ hg debugwalk -v beans/.hg abort: path 'beans/.hg' is inside nested repo 'beans' - [255] + [10] Test explicit paths and excludes: diff --git a/tests/test-wireproto-caching.t b/tests/test-wireproto-caching.t deleted file mode 100644 --- a/tests/test-wireproto-caching.t +++ /dev/null @@ -1,468 +0,0 @@ - $ . $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] - > track = simplecache - > EOF - $ hg init server - $ enablehttpv2 server - $ cd server - $ cat >> .hg/hgrc << EOF - > [extensions] - > simplecache = $TESTDIR/wireprotosimplecache.py - > EOF - - $ echo a0 > a - $ echo b0 > b - $ hg -q commit -A -m 'commit 0' - $ echo a1 > a - $ hg commit -m 'commit 1' - $ echo b1 > b - $ hg commit -m 'commit 2' - $ echo a2 > a - $ echo b2 > b - $ hg commit -m 'commit 3' - - $ hg log -G -T '{rev}:{node} {desc}' - @ 3:50590a86f3ff5d1e9a1624a7a6957884565cc8e8 commit 3 - | - o 2:4d01eda50c6ac5f7e89cbe1880143a32f559c302 commit 2 - | - o 1:4432d83626e8a98655f062ec1f2a43b07f7fbbb0 commit 1 - | - o 0:3390ef850073fbc2f0dfff2244342c8e9229013a commit 0 - - - $ hg --debug debugindex -m - rev linkrev nodeid p1 p2 - 0 0 992f4779029a3df8d0666d00bb924f69634e2641 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 - 1 1 a988fb43583e871d1ed5750ee074c6d840bbbfc8 992f4779029a3df8d0666d00bb924f69634e2641 0000000000000000000000000000000000000000 - 2 2 a8853dafacfca6fc807055a660d8b835141a3bb4 a988fb43583e871d1ed5750ee074c6d840bbbfc8 0000000000000000000000000000000000000000 - 3 3 3fe11dfbb13645782b0addafbe75a87c210ffddc a8853dafacfca6fc807055a660d8b835141a3bb4 0000000000000000000000000000000000000000 - - $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log - $ cat hg.pid > $DAEMON_PIDS - -Performing the same request should result in same result, with 2nd response -coming from cache. - - $ sendhttpv2peer << EOF - > command manifestdata - > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41'] - > tree eval:b'' - > fields eval:[b'parents'] - > EOF - creating http peer for wire protocol version 2 - sending manifestdata command - response: gen[ - { - b'totalitems': 1 - }, - { - b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A', - b'parents': [ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ] - } - ] - - $ sendhttpv2peer << EOF - > command manifestdata - > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41'] - > tree eval:b'' - > fields eval:[b'parents'] - > EOF - creating http peer for wire protocol version 2 - sending manifestdata command - response: gen[ - { - b'totalitems': 1 - }, - { - b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A', - b'parents': [ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ] - } - ] - -Sending different request doesn't yield cache hit. - - $ sendhttpv2peer << EOF - > command manifestdata - > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41', b'\xa9\x88\xfb\x43\x58\x3e\x87\x1d\x1e\xd5\x75\x0e\xe0\x74\xc6\xd8\x40\xbb\xbf\xc8'] - > tree eval:b'' - > fields eval:[b'parents'] - > EOF - creating http peer for wire protocol version 2 - sending manifestdata command - response: gen[ - { - b'totalitems': 2 - }, - { - b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A', - b'parents': [ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ] - }, - { - b'node': b'\xa9\x88\xfbCX>\x87\x1d\x1e\xd5u\x0e\xe0t\xc6\xd8@\xbb\xbf\xc8', - b'parents': [ - b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ] - } - ] - - $ cat .hg/blackbox.log - *> cacher constructed for manifestdata (glob) - *> cache miss for 47abb8efa5f01b8964d74917793ad2464db0fa2c (glob) - *> storing cache entry for 47abb8efa5f01b8964d74917793ad2464db0fa2c (glob) - *> cacher constructed for manifestdata (glob) - *> cache hit for 47abb8efa5f01b8964d74917793ad2464db0fa2c (glob) - *> cacher constructed for manifestdata (glob) - *> cache miss for 37326a83e9843f15161fce9d1e92d06b795d5e8e (glob) - *> storing cache entry for 37326a83e9843f15161fce9d1e92d06b795d5e8e (glob) - - $ cat error.log - - $ killdaemons.py - $ rm .hg/blackbox.log - -Try with object caching mode - - $ cat >> .hg/hgrc << EOF - > [simplecache] - > cacheobjects = true - > EOF - - $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log - $ cat hg.pid > $DAEMON_PIDS - - $ sendhttpv2peer << EOF - > command manifestdata - > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41'] - > tree eval:b'' - > fields eval:[b'parents'] - > EOF - creating http peer for wire protocol version 2 - sending manifestdata command - response: gen[ - { - b'totalitems': 1 - }, - { - b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A', - b'parents': [ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ] - } - ] - - $ sendhttpv2peer << EOF - > command manifestdata - > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41'] - > tree eval:b'' - > fields eval:[b'parents'] - > EOF - creating http peer for wire protocol version 2 - sending manifestdata command - response: gen[ - { - b'totalitems': 1 - }, - { - b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A', - b'parents': [ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ] - } - ] - - $ cat .hg/blackbox.log - *> cacher constructed for manifestdata (glob) - *> cache miss for 47abb8efa5f01b8964d74917793ad2464db0fa2c (glob) - *> storing cache entry for 47abb8efa5f01b8964d74917793ad2464db0fa2c (glob) - *> cacher constructed for manifestdata (glob) - *> cache hit for 47abb8efa5f01b8964d74917793ad2464db0fa2c (glob) - - $ cat error.log - - $ killdaemons.py - $ rm .hg/blackbox.log - -A non-cacheable command does not instantiate cacher - - $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log - $ cat hg.pid > $DAEMON_PIDS - $ sendhttpv2peer << EOF - > command capabilities - > EOF - creating http peer for wire protocol version 2 - sending capabilities command - response: gen[ - { - b'commands': { - b'branchmap': { - b'args': {}, - b'permissions': [ - b'pull' - ] - }, - b'capabilities': { - b'args': {}, - b'permissions': [ - b'pull' - ] - }, - b'changesetdata': { - b'args': { - b'fields': { - b'default': set([]), - b'required': False, - b'type': b'set', - b'validvalues': set([ - b'bookmarks', - b'parents', - b'phase', - b'revision' - ]) - }, - b'revisions': { - b'required': True, - b'type': b'list' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'filedata': { - b'args': { - b'fields': { - b'default': set([]), - b'required': False, - b'type': b'set', - b'validvalues': set([ - b'linknode', - b'parents', - b'revision' - ]) - }, - b'haveparents': { - b'default': False, - b'required': False, - b'type': b'bool' - }, - b'nodes': { - b'required': True, - b'type': b'list' - }, - b'path': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'filesdata': { - b'args': { - b'fields': { - b'default': set([]), - b'required': False, - b'type': b'set', - b'validvalues': set([ - b'firstchangeset', - b'linknode', - b'parents', - b'revision' - ]) - }, - b'haveparents': { - b'default': False, - b'required': False, - b'type': b'bool' - }, - b'pathfilter': { - b'default': None, - b'required': False, - b'type': b'dict' - }, - b'revisions': { - b'required': True, - b'type': b'list' - } - }, - b'permissions': [ - b'pull' - ], - b'recommendedbatchsize': 50000 - }, - b'heads': { - b'args': { - b'publiconly': { - b'default': False, - b'required': False, - b'type': b'bool' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'known': { - b'args': { - b'nodes': { - b'default': [], - b'required': False, - b'type': b'list' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'listkeys': { - b'args': { - b'namespace': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'lookup': { - b'args': { - b'key': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'manifestdata': { - b'args': { - b'fields': { - b'default': set([]), - b'required': False, - b'type': b'set', - b'validvalues': set([ - b'parents', - b'revision' - ]) - }, - b'haveparents': { - b'default': False, - b'required': False, - b'type': b'bool' - }, - b'nodes': { - b'required': True, - b'type': b'list' - }, - b'tree': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'pull' - ], - b'recommendedbatchsize': 100000 - }, - b'pushkey': { - b'args': { - b'key': { - b'required': True, - b'type': b'bytes' - }, - b'namespace': { - b'required': True, - b'type': b'bytes' - }, - b'new': { - b'required': True, - b'type': b'bytes' - }, - b'old': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'push' - ] - }, - b'rawstorefiledata': { - b'args': { - b'files': { - b'required': True, - b'type': b'list' - }, - b'pathfilter': { - b'default': None, - b'required': False, - b'type': b'list' - } - }, - b'permissions': [ - b'pull' - ] - } - }, - b'framingmediatypes': [ - b'application/mercurial-exp-framing-0006' - ], - b'pathfilterprefixes': set([ - b'path:', - b'rootfilesin:' - ]), - b'rawrepoformats': [ - b'generaldelta', - b'revlogv1', - b'sparserevlog' - ] - } - ] - - $ test -f .hg/blackbox.log - [1] - -An error is not cached - - $ sendhttpv2peer << EOF - > command manifestdata - > nodes eval:[b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'] - > tree eval:b'' - > fields eval:[b'parents'] - > EOF - creating http peer for wire protocol version 2 - sending manifestdata command - abort: unknown node: \xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa (esc) - [255] - - $ cat .hg/blackbox.log - *> cacher constructed for manifestdata (glob) - *> cache miss for 2cba2a7d0d1575fea2fe68f597e97a7c2ac2f705 (glob) - *> cacher exiting due to error (glob) - - $ killdaemons.py - $ rm .hg/blackbox.log diff --git a/tests/test-wireproto-command-branchmap.t b/tests/test-wireproto-command-branchmap.t deleted file mode 100644 --- a/tests/test-wireproto-command-branchmap.t +++ /dev/null @@ -1,59 +0,0 @@ - $ . $TESTDIR/wireprotohelpers.sh - - $ hg init server - $ enablehttpv2 server - $ cd server - $ hg debugdrawdag << EOF - > C D - > |/ - > B - > | - > A - > EOF - - $ hg up B - 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg branch branch1 - marked working directory as branch branch1 - (branches are permanent and global, did you want a bookmark?) - $ echo b1 > foo - $ hg -q commit -A -m 'branch 1' - $ hg up B - 0 files updated, 0 files merged, 1 files removed, 0 files unresolved - $ hg branch branch2 - marked working directory as branch branch2 - $ echo b2 > foo - $ hg -q commit -A -m 'branch 2' - - $ hg log -T '{rev}:{node} {branch} {desc}\n' - 5:224161c7589aa48fa83a48feff5e95b56ae327fc branch2 branch 2 - 4:b5faacdfd2633768cb3152336cc0953381266688 branch1 branch 1 - 3:be0ef73c17ade3fc89dc41701eb9fc3a91b58282 default D - 2:26805aba1e600a82e93661149f2313866a221a7b default C - 1:112478962961147124edd43549aedd1a335e44bf default B - 0:426bada5c67598ca65036d57d9e4b64b0c1ce7a0 default A - - $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log - $ cat hg.pid > $DAEMON_PIDS - -No arguments returns something reasonable - - $ sendhttpv2peer << EOF - > command branchmap - > EOF - creating http peer for wire protocol version 2 - sending branchmap command - response: { - b'branch1': [ - b'\xb5\xfa\xac\xdf\xd2c7h\xcb1R3l\xc0\x953\x81&f\x88' - ], - b'branch2': [ - b'"Aa\xc7X\x9a\xa4\x8f\xa8:H\xfe\xff^\x95\xb5j\xe3\'\xfc' - ], - b'default': [ - b'&\x80Z\xba\x1e`\n\x82\xe96a\x14\x9f#\x13\x86j"\x1a{', - b'\xbe\x0e\xf7<\x17\xad\xe3\xfc\x89\xdcAp\x1e\xb9\xfc:\x91\xb5\x82\x82' - ] - } - - $ cat error.log diff --git a/tests/test-wireproto-command-capabilities.t b/tests/test-wireproto-command-capabilities.t deleted file mode 100644 --- a/tests/test-wireproto-command-capabilities.t +++ /dev/null @@ -1,762 +0,0 @@ -#require no-chg - - $ . $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 -zstd from the equation. - - $ cat >> server/.hg/hgrc << EOF - > [server] - > compressionengines = zlib - > EOF - - $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log - $ cat hg.pid > $DAEMON_PIDS - -A normal capabilities request is serviced for version 1 - - $ sendhttpraw << EOF - > httprequest GET ?cmd=capabilities - > user-agent: test - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> GET /?cmd=capabilities HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> user-agent: test\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 200 Script output follows\r\n - 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: *\r\n (glob) - 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 - -A proper request without the API server enabled returns the legacy response - - $ sendhttpraw << EOF - > httprequest GET ?cmd=capabilities - > user-agent: test - > x-hgupgrade-1: foo - > x-hgproto-1: cbor - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> GET /?cmd=capabilities HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> user-agent: test\r\n - s> x-hgproto-1: cbor\r\n - s> x-hgupgrade-1: foo\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 200 Script output follows\r\n - 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: *\r\n (glob) - 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 - -Restart with just API server enabled. This enables serving the new format. - - $ killdaemons.py - $ cat error.log - - $ cat >> server/.hg/hgrc << EOF - > [experimental] - > web.apiserver = true - > EOF - - $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log - $ cat hg.pid > $DAEMON_PIDS - -X-HgUpgrade- without CBOR advertisement uses legacy response - - $ sendhttpraw << EOF - > httprequest GET ?cmd=capabilities - > user-agent: test - > x-hgupgrade-1: foo bar - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> GET /?cmd=capabilities HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> user-agent: test\r\n - s> x-hgupgrade-1: foo bar\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 200 Script output follows\r\n - 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: *\r\n (glob) - 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 - -X-HgUpgrade- without known serialization in X-HgProto- uses legacy response - - $ sendhttpraw << EOF - > httprequest GET ?cmd=capabilities - > user-agent: test - > x-hgupgrade-1: foo bar - > x-hgproto-1: some value - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> GET /?cmd=capabilities HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> user-agent: test\r\n - s> x-hgproto-1: some value\r\n - s> x-hgupgrade-1: foo bar\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 200 Script output follows\r\n - 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: *\r\n (glob) - 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 - -X-HgUpgrade- + X-HgProto- headers trigger new response format - - $ sendhttpraw << EOF - > httprequest GET ?cmd=capabilities - > user-agent: test - > x-hgupgrade-1: foo bar - > x-hgproto-1: cbor - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> GET /?cmd=capabilities HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> user-agent: test\r\n - s> x-hgproto-1: cbor\r\n - s> x-hgupgrade-1: foo bar\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 200 OK\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: application/mercurial-cbor\r\n - s> Content-Length: *\r\n (glob) - s> \r\n - 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/', - b'apis': {}, - b'v1capabilities': b'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' - } - ] - -Restart server to enable HTTPv2 - - $ killdaemons.py - $ enablehttpv2 server - $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log - $ cat hg.pid > $DAEMON_PIDS - -Only requested API services are returned - - $ sendhttpraw << EOF - > httprequest GET ?cmd=capabilities - > user-agent: test - > x-hgupgrade-1: foo bar - > x-hgproto-1: cbor - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> GET /?cmd=capabilities HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> user-agent: test\r\n - s> x-hgproto-1: cbor\r\n - s> x-hgupgrade-1: foo bar\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 200 OK\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: application/mercurial-cbor\r\n - s> Content-Length: *\r\n (glob) - s> \r\n - 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/', - b'apis': {}, - b'v1capabilities': b'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' - } - ] - -Request for HTTPv2 service returns information about it - - $ sendhttpraw << EOF - > httprequest GET ?cmd=capabilities - > user-agent: test - > x-hgupgrade-1: exp-http-v2-0003 foo bar - > x-hgproto-1: cbor - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> GET /?cmd=capabilities HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> user-agent: test\r\n - s> x-hgproto-1: cbor\r\n - s> x-hgupgrade-1: exp-http-v2-0003 foo bar\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 200 OK\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - 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\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/', - b'apis': { - b'exp-http-v2-0003': { - b'commands': { - b'branchmap': { - b'args': {}, - b'permissions': [ - b'pull' - ] - }, - b'capabilities': { - b'args': {}, - b'permissions': [ - b'pull' - ] - }, - b'changesetdata': { - b'args': { - b'fields': { - b'default': set([]), - b'required': False, - b'type': b'set', - b'validvalues': set([ - b'bookmarks', - b'parents', - b'phase', - b'revision' - ]) - }, - b'revisions': { - b'required': True, - b'type': b'list' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'filedata': { - b'args': { - b'fields': { - b'default': set([]), - b'required': False, - b'type': b'set', - b'validvalues': set([ - b'linknode', - b'parents', - b'revision' - ]) - }, - b'haveparents': { - b'default': False, - b'required': False, - b'type': b'bool' - }, - b'nodes': { - b'required': True, - b'type': b'list' - }, - b'path': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'filesdata': { - b'args': { - b'fields': { - b'default': set([]), - b'required': False, - b'type': b'set', - b'validvalues': set([ - b'firstchangeset', - b'linknode', - b'parents', - b'revision' - ]) - }, - b'haveparents': { - b'default': False, - b'required': False, - b'type': b'bool' - }, - b'pathfilter': { - b'default': None, - b'required': False, - b'type': b'dict' - }, - b'revisions': { - b'required': True, - b'type': b'list' - } - }, - b'permissions': [ - b'pull' - ], - b'recommendedbatchsize': 50000 - }, - b'heads': { - b'args': { - b'publiconly': { - b'default': False, - b'required': False, - b'type': b'bool' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'known': { - b'args': { - b'nodes': { - b'default': [], - b'required': False, - b'type': b'list' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'listkeys': { - b'args': { - b'namespace': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'lookup': { - b'args': { - b'key': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'manifestdata': { - b'args': { - b'fields': { - b'default': set([]), - b'required': False, - b'type': b'set', - b'validvalues': set([ - b'parents', - b'revision' - ]) - }, - b'haveparents': { - b'default': False, - b'required': False, - b'type': b'bool' - }, - b'nodes': { - b'required': True, - b'type': b'list' - }, - b'tree': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'pull' - ], - b'recommendedbatchsize': 100000 - }, - b'pushkey': { - b'args': { - b'key': { - b'required': True, - b'type': b'bytes' - }, - b'namespace': { - b'required': True, - b'type': b'bytes' - }, - b'new': { - b'required': True, - b'type': b'bytes' - }, - b'old': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'push' - ] - }, - b'rawstorefiledata': { - b'args': { - b'files': { - b'required': True, - b'type': b'list' - }, - b'pathfilter': { - b'default': None, - b'required': False, - b'type': b'list' - } - }, - b'permissions': [ - b'pull' - ] - } - }, - b'framingmediatypes': [ - b'application/mercurial-exp-framing-0006' - ], - b'pathfilterprefixes': set([ - b'path:', - b'rootfilesin:' - ]), - b'rawrepoformats': [ - b'generaldelta', - b'revlogv1', - b'sparserevlog' - ] - } - }, - b'v1capabilities': b'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' - } - ] - -capabilities command returns expected info - - $ sendhttpv2peerhandshake << EOF - > command capabilities - > EOF - creating http peer for wire protocol version 2 - s> setsockopt(6, 1, 1) -> None (?) - s> GET /?cmd=capabilities HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> vary: X-HgProto-1,X-HgUpgrade-1\r\n - s> x-hgproto-1: cbor\r\n - s> x-hgupgrade-1: exp-http-v2-0003\r\n - s> accept: application/mercurial-0.1\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> user-agent: Mercurial debugwireproto\r\n - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 200 OK\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - 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\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 - s> Accept-Encoding: identity\r\n - s> accept: application/mercurial-exp-framing-0006\r\n - s> content-type: application/mercurial-exp-framing-0006\r\n - s> content-length: 63\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> user-agent: Mercurial debugwireproto\r\n - s> \r\n - s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity\x13\x00\x00\x01\x00\x01\x00\x11\xa1DnameLcapabilities - s> makefile('rb', None) - s> HTTP/1.1 200 OK\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: application/mercurial-exp-framing-0006\r\n - s> Transfer-Encoding: chunked\r\n - s> \r\n - s> 11\r\n - s> \t\x00\x00\x01\x00\x02\x01\x92 - s> Hidentity - s> \r\n - s> 13\r\n - s> \x0b\x00\x00\x01\x00\x02\x041 - s> \xa1FstatusBok - s> \r\n - s> 65e\r\n - s> V\x06\x00\x01\x00\x02\x041 - s> \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\x83LgeneraldeltaHrevlogv1Lsparserevlog - s> \r\n - s> 8\r\n - s> \x00\x00\x00\x01\x00\x02\x002 - s> \r\n - s> 0\r\n - s> \r\n - response: gen[ - { - b'commands': { - b'branchmap': { - b'args': {}, - b'permissions': [ - b'pull' - ] - }, - b'capabilities': { - b'args': {}, - b'permissions': [ - b'pull' - ] - }, - b'changesetdata': { - b'args': { - b'fields': { - b'default': set([]), - b'required': False, - b'type': b'set', - b'validvalues': set([ - b'bookmarks', - b'parents', - b'phase', - b'revision' - ]) - }, - b'revisions': { - b'required': True, - b'type': b'list' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'filedata': { - b'args': { - b'fields': { - b'default': set([]), - b'required': False, - b'type': b'set', - b'validvalues': set([ - b'linknode', - b'parents', - b'revision' - ]) - }, - b'haveparents': { - b'default': False, - b'required': False, - b'type': b'bool' - }, - b'nodes': { - b'required': True, - b'type': b'list' - }, - b'path': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'filesdata': { - b'args': { - b'fields': { - b'default': set([]), - b'required': False, - b'type': b'set', - b'validvalues': set([ - b'firstchangeset', - b'linknode', - b'parents', - b'revision' - ]) - }, - b'haveparents': { - b'default': False, - b'required': False, - b'type': b'bool' - }, - b'pathfilter': { - b'default': None, - b'required': False, - b'type': b'dict' - }, - b'revisions': { - b'required': True, - b'type': b'list' - } - }, - b'permissions': [ - b'pull' - ], - b'recommendedbatchsize': 50000 - }, - b'heads': { - b'args': { - b'publiconly': { - b'default': False, - b'required': False, - b'type': b'bool' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'known': { - b'args': { - b'nodes': { - b'default': [], - b'required': False, - b'type': b'list' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'listkeys': { - b'args': { - b'namespace': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'lookup': { - b'args': { - b'key': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'manifestdata': { - b'args': { - b'fields': { - b'default': set([]), - b'required': False, - b'type': b'set', - b'validvalues': set([ - b'parents', - b'revision' - ]) - }, - b'haveparents': { - b'default': False, - b'required': False, - b'type': b'bool' - }, - b'nodes': { - b'required': True, - b'type': b'list' - }, - b'tree': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'pull' - ], - b'recommendedbatchsize': 100000 - }, - b'pushkey': { - b'args': { - b'key': { - b'required': True, - b'type': b'bytes' - }, - b'namespace': { - b'required': True, - b'type': b'bytes' - }, - b'new': { - b'required': True, - b'type': b'bytes' - }, - b'old': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'push' - ] - }, - b'rawstorefiledata': { - b'args': { - b'files': { - b'required': True, - b'type': b'list' - }, - b'pathfilter': { - b'default': None, - b'required': False, - b'type': b'list' - } - }, - b'permissions': [ - b'pull' - ] - } - }, - b'framingmediatypes': [ - b'application/mercurial-exp-framing-0006' - ], - b'pathfilterprefixes': set([ - b'path:', - b'rootfilesin:' - ]), - b'rawrepoformats': [ - b'generaldelta', - b'revlogv1', - b'sparserevlog' - ] - } - ] - (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob) - - $ cat error.log diff --git a/tests/test-wireproto-command-changesetdata.t b/tests/test-wireproto-command-changesetdata.t deleted file mode 100644 --- a/tests/test-wireproto-command-changesetdata.t +++ /dev/null @@ -1,613 +0,0 @@ - $ . $TESTDIR/wireprotohelpers.sh - - $ hg init server - $ enablehttpv2 server - $ cd server - $ cat >> .hg/hgrc << EOF - > [phases] - > publish = false - > EOF - $ echo a0 > a - $ echo b0 > b - - $ hg -q commit -A -m 'commit 0' - - $ echo a1 > a - $ echo b1 > b - $ hg commit -m 'commit 1' - $ echo b2 > b - $ hg commit -m 'commit 2' - $ hg phase --public -r . - - $ hg -q up -r 0 - $ echo a2 > a - $ hg commit -m 'commit 3' - created new head - - $ hg log -G -T '{rev}:{node} {desc}\n' - @ 3:eae5f82c2e622368d27daecb76b7e393d0f24211 commit 3 - | - | o 2:0bb8ad894a15b15380b2a2a5b183e20f2a4b28dd commit 2 - | | - | o 1:7592917e1c3e82677cb0a4bc715ca25dd12d28c1 commit 1 - |/ - o 0:3390ef850073fbc2f0dfff2244342c8e9229013a commit 0 - - - $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log - $ cat hg.pid > $DAEMON_PIDS - -No arguments is an invalid request - - $ sendhttpv2peer << EOF - > command changesetdata - > EOF - creating http peer for wire protocol version 2 - sending changesetdata command - abort: missing required arguments: revisions - [255] - -Missing nodes for changesetexplicit results in error - - $ sendhttpv2peer << EOF - > command changesetdata - > revisions eval:[{b'type': b'changesetexplicit'}] - > EOF - creating http peer for wire protocol version 2 - sending changesetdata command - abort: nodes key not present in changesetexplicit revision specifier - [255] - -changesetexplicitdepth requires nodes and depth keys - - $ sendhttpv2peer << EOF - > command changesetdata - > revisions eval:[{b'type': b'changesetexplicitdepth'}] - > EOF - creating http peer for wire protocol version 2 - sending changesetdata command - abort: nodes key not present in changesetexplicitdepth revision specifier - [255] - - $ sendhttpv2peer << EOF - > command changesetdata - > revisions eval:[{b'type': b'changesetexplicitdepth', b'nodes': []}] - > EOF - creating http peer for wire protocol version 2 - sending changesetdata command - abort: depth key not present in changesetexplicitdepth revision specifier - [255] - - $ sendhttpv2peer << EOF - > command changesetdata - > revisions eval:[{b'type': b'changesetexplicitdepth', b'depth': 42}] - > EOF - creating http peer for wire protocol version 2 - sending changesetdata command - abort: nodes key not present in changesetexplicitdepth revision specifier - [255] - -changesetdagrange requires roots and heads keys - - $ sendhttpv2peer << EOF - > command changesetdata - > revisions eval:[{b'type': b'changesetdagrange'}] - > EOF - creating http peer for wire protocol version 2 - sending changesetdata command - abort: roots key not present in changesetdagrange revision specifier - [255] - - $ sendhttpv2peer << EOF - > command changesetdata - > revisions eval:[{b'type': b'changesetdagrange', b'roots': []}] - > EOF - creating http peer for wire protocol version 2 - sending changesetdata command - abort: heads key not present in changesetdagrange revision specifier - [255] - - $ sendhttpv2peer << EOF - > command changesetdata - > revisions eval:[{b'type': b'changesetdagrange', b'heads': [b'dummy']}] - > EOF - creating http peer for wire protocol version 2 - sending changesetdata command - abort: roots key not present in changesetdagrange revision specifier - [255] - -Empty changesetdagrange heads results in an error - - $ sendhttpv2peer << EOF - > command changesetdata - > revisions eval:[{b'type': b'changesetdagrange', b'heads': [], b'roots': []}] - > EOF - creating http peer for wire protocol version 2 - sending changesetdata command - abort: heads key in changesetdagrange cannot be empty - [255] - -Sending just dagrange heads sends all revisions - - $ sendhttpv2peer << EOF - > command changesetdata - > revisions eval:[{ - > b'type': b'changesetdagrange', - > b'roots': [], - > b'heads': [ - > b'\x0b\xb8\xad\x89\x4a\x15\xb1\x53\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f\x2a\x4b\x28\xdd', - > b'\xea\xe5\xf8\x2c\x2e\x62\x23\x68\xd2\x7d\xae\xcb\x76\xb7\xe3\x93\xd0\xf2\x42\x11', - > ]}] - > EOF - creating http peer for wire protocol version 2 - sending changesetdata command - response: gen[ - { - b'totalitems': 4 - }, - { - b'node': b'3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:' - }, - { - b'node': b'u\x92\x91~\x1c>\x82g|\xb0\xa4\xbcq\\\xa2]\xd1-(\xc1' - }, - { - b'node': b'\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xdd' - }, - { - b'node': b'\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11' - } - ] - -Sending root nodes limits what data is sent - - $ sendhttpv2peer << EOF - > command changesetdata - > revisions eval:[{ - > b'type': b'changesetdagrange', - > b'roots': [b'\x33\x90\xef\x85\x00\x73\xfb\xc2\xf0\xdf\xff\x22\x44\x34\x2c\x8e\x92\x29\x01\x3a'], - > b'heads': [ - > b'\x0b\xb8\xad\x89\x4a\x15\xb1\x53\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f\x2a\x4b\x28\xdd', - > ]}] - > EOF - creating http peer for wire protocol version 2 - sending changesetdata command - response: gen[ - { - b'totalitems': 2 - }, - { - b'node': b'u\x92\x91~\x1c>\x82g|\xb0\xa4\xbcq\\\xa2]\xd1-(\xc1' - }, - { - b'node': b'\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xdd' - } - ] - -Requesting data on a single node by node works - - $ sendhttpv2peer << EOF - > command changesetdata - > revisions eval:[{ - > b'type': b'changesetexplicit', - > b'nodes': [b'\x33\x90\xef\x85\x00\x73\xfb\xc2\xf0\xdf\xff\x22\x44\x34\x2c\x8e\x92\x29\x01\x3a']}] - > EOF - creating http peer for wire protocol version 2 - sending changesetdata command - response: gen[ - { - b'totalitems': 1 - }, - { - b'node': b'3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:' - } - ] - -Specifying a noderange and nodes takes union - - $ sendhttpv2peer << EOF - > command changesetdata - > revisions eval:[ - > { - > b'type': b'changesetexplicit', - > b'nodes': [b'\xea\xe5\xf8\x2c\x2e\x62\x23\x68\xd2\x7d\xae\xcb\x76\xb7\xe3\x93\xd0\xf2\x42\x11'], - > }, - > { - > b'type': b'changesetdagrange', - > b'roots': [b'\x75\x92\x91\x7e\x1c\x3e\x82\x67\x7c\xb0\xa4\xbc\x71\x5c\xa2\x5d\xd1\x2d\x28\xc1'], - > b'heads': [b'\x0b\xb8\xad\x89\x4a\x15\xb1\x53\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f\x2a\x4b\x28\xdd'], - > }] - > EOF - creating http peer for wire protocol version 2 - sending changesetdata command - response: gen[ - { - b'totalitems': 2 - }, - { - b'node': b'\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11' - }, - { - b'node': b'\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xdd' - } - ] - -nodesdepth of 1 limits to exactly requested nodes - - $ sendhttpv2peer << EOF - > command changesetdata - > revisions eval:[{ - > b'type': b'changesetexplicitdepth', - > b'nodes': [b'\xea\xe5\xf8\x2c\x2e\x62\x23\x68\xd2\x7d\xae\xcb\x76\xb7\xe3\x93\xd0\xf2\x42\x11'], - > b'depth': 1}] - > EOF - creating http peer for wire protocol version 2 - sending changesetdata command - response: gen[ - { - b'totalitems': 1 - }, - { - b'node': b'\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11' - } - ] - -nodesdepth of 2 limits to first ancestor - - $ sendhttpv2peer << EOF - > command changesetdata - > revisions eval:[{ - > b'type': b'changesetexplicitdepth', - > b'nodes': [b'\xea\xe5\xf8\x2c\x2e\x62\x23\x68\xd2\x7d\xae\xcb\x76\xb7\xe3\x93\xd0\xf2\x42\x11'], - > b'depth': 2}] - > EOF - creating http peer for wire protocol version 2 - sending changesetdata command - response: gen[ - { - b'totalitems': 2 - }, - { - b'node': b'3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:' - }, - { - b'node': b'\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11' - } - ] - -nodesdepth with multiple nodes - - $ sendhttpv2peer << EOF - > command changesetdata - > revisions eval:[{ - > b'type': b'changesetexplicitdepth', - > b'nodes': [b'\xea\xe5\xf8\x2c\x2e\x62\x23\x68\xd2\x7d\xae\xcb\x76\xb7\xe3\x93\xd0\xf2\x42\x11', b'\x0b\xb8\xad\x89\x4a\x15\xb1\x53\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f\x2a\x4b\x28\xdd'], - > b'depth': 2}] - > EOF - creating http peer for wire protocol version 2 - sending changesetdata command - response: gen[ - { - b'totalitems': 4 - }, - { - b'node': b'3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:' - }, - { - b'node': b'u\x92\x91~\x1c>\x82g|\xb0\xa4\xbcq\\\xa2]\xd1-(\xc1' - }, - { - b'node': b'\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xdd' - }, - { - b'node': b'\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11' - } - ] - -Parents data is transferred upon request - - $ sendhttpv2peer << EOF - > command changesetdata - > fields eval:[b'parents'] - > revisions eval:[{ - > b'type': b'changesetexplicit', - > b'nodes': [ - > b'\xea\xe5\xf8\x2c\x2e\x62\x23\x68\xd2\x7d\xae\xcb\x76\xb7\xe3\x93\xd0\xf2\x42\x11', - > ]}] - > EOF - creating http peer for wire protocol version 2 - sending changesetdata command - response: gen[ - { - b'totalitems': 1 - }, - { - b'node': b'\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11', - b'parents': [ - b'3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ] - } - ] - -Phase data is transferred upon request - - $ sendhttpv2peer << EOF - > command changesetdata - > fields eval:[b'phase'] - > revisions eval:[{ - > b'type': b'changesetexplicit', - > b'nodes': [ - > b'\x0b\xb8\xad\x89\x4a\x15\xb1\x53\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f\x2a\x4b\x28\xdd', - > ]}] - > EOF - creating http peer for wire protocol version 2 - sending changesetdata command - response: gen[ - { - b'totalitems': 1 - }, - { - b'node': b'\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xdd', - b'phase': b'public' - } - ] - -Revision data is transferred upon request - - $ sendhttpv2peer << EOF - > command changesetdata - > fields eval:[b'revision'] - > revisions eval:[{ - > b'type': b'changesetexplicit', - > b'nodes': [ - > b'\xea\xe5\xf8\x2c\x2e\x62\x23\x68\xd2\x7d\xae\xcb\x76\xb7\xe3\x93\xd0\xf2\x42\x11', - > ]}] - > EOF - creating http peer for wire protocol version 2 - sending changesetdata command - response: gen[ - { - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 61 - ] - ], - b'node': b'\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11' - }, - b'1b74476799ec8318045db759b1b4bcc9b839d0aa\ntest\n0 0\na\n\ncommit 3' - ] - -Bookmarks key isn't present if no bookmarks data - - $ sendhttpv2peer << EOF - > command changesetdata - > fields eval:[b'bookmarks'] - > revisions eval:[{ - > b'type': b'changesetdagrange', - > b'roots': [], - > b'heads': [ - > b'\x0b\xb8\xad\x89\x4a\x15\xb1\x53\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f\x2a\x4b\x28\xdd', - > b'\xea\xe5\xf8\x2c\x2e\x62\x23\x68\xd2\x7d\xae\xcb\x76\xb7\xe3\x93\xd0\xf2\x42\x11', - > ]}] - > EOF - creating http peer for wire protocol version 2 - sending changesetdata command - response: gen[ - { - b'totalitems': 4 - }, - { - b'node': b'3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:' - }, - { - b'node': b'u\x92\x91~\x1c>\x82g|\xb0\xa4\xbcq\\\xa2]\xd1-(\xc1' - }, - { - b'node': b'\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xdd' - }, - { - b'node': b'\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11' - } - ] - -Bookmarks are sent when requested - - $ hg -R ../server bookmark -r 0bb8ad894a15b15380b2a2a5b183e20f2a4b28dd book-1 - $ hg -R ../server bookmark -r eae5f82c2e622368d27daecb76b7e393d0f24211 book-2 - $ hg -R ../server bookmark -r eae5f82c2e622368d27daecb76b7e393d0f24211 book-3 - - $ sendhttpv2peer << EOF - > command changesetdata - > fields eval:[b'bookmarks'] - > revisions eval:[{ - > b'type': b'changesetdagrange', - > b'roots': [], - > b'heads': [ - > b'\x0b\xb8\xad\x89\x4a\x15\xb1\x53\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f\x2a\x4b\x28\xdd', - > b'\xea\xe5\xf8\x2c\x2e\x62\x23\x68\xd2\x7d\xae\xcb\x76\xb7\xe3\x93\xd0\xf2\x42\x11', - > ]}] - > EOF - creating http peer for wire protocol version 2 - sending changesetdata command - response: gen[ - { - b'totalitems': 4 - }, - { - b'node': b'3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:' - }, - { - b'node': b'u\x92\x91~\x1c>\x82g|\xb0\xa4\xbcq\\\xa2]\xd1-(\xc1' - }, - { - b'bookmarks': [ - b'book-1' - ], - b'node': b'\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xdd' - }, - { - b'bookmarks': [ - b'book-2', - b'book-3' - ], - b'node': b'\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11' - } - ] - -Bookmarks are sent when we make a no-new-revisions request - - $ sendhttpv2peer << EOF - > command changesetdata - > fields eval:[b'bookmarks', b'revision'] - > revisions eval:[{ - > b'type': b'changesetdagrange', - > b'roots': [b'\xea\xe5\xf8\x2c\x2e\x62\x23\x68\xd2\x7d\xae\xcb\x76\xb7\xe3\x93\xd0\xf2\x42\x11'], - > b'heads': [ - > b'\x0b\xb8\xad\x89\x4a\x15\xb1\x53\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f\x2a\x4b\x28\xdd', - > b'\xea\xe5\xf8\x2c\x2e\x62\x23\x68\xd2\x7d\xae\xcb\x76\xb7\xe3\x93\xd0\xf2\x42\x11', - > ]}] - > EOF - creating http peer for wire protocol version 2 - sending changesetdata command - response: gen[ - { - b'totalitems': 2 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 63 - ] - ], - b'node': b'u\x92\x91~\x1c>\x82g|\xb0\xa4\xbcq\\\xa2]\xd1-(\xc1' - }, - b'7f144aea0ba742713887b564d57e9d12f12ff382\ntest\n0 0\na\nb\n\ncommit 1', - { - b'bookmarks': [ - b'book-1' - ], - b'fieldsfollowing': [ - [ - b'revision', - 61 - ] - ], - b'node': b'\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xdd' - }, - b'37f0a2d1c28ffe4b879109a7d1bbf8f07b3c763b\ntest\n0 0\nb\n\ncommit 2', - { - b'bookmarks': [ - b'book-2', - b'book-3' - ], - b'node': b'\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11' - } - ] - -Multiple fields can be transferred - - $ sendhttpv2peer << EOF - > command changesetdata - > fields eval:[b'parents', b'revision'] - > revisions eval:[{ - > b'type': b'changesetexplicit', - > b'nodes': [ - > b'\xea\xe5\xf8\x2c\x2e\x62\x23\x68\xd2\x7d\xae\xcb\x76\xb7\xe3\x93\xd0\xf2\x42\x11', - > ]}] - > EOF - creating http peer for wire protocol version 2 - sending changesetdata command - response: gen[ - { - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 61 - ] - ], - b'node': b'\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11', - b'parents': [ - b'3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ] - }, - b'1b74476799ec8318045db759b1b4bcc9b839d0aa\ntest\n0 0\na\n\ncommit 3' - ] - -Base nodes have just their metadata (e.g. phase) transferred -TODO this doesn't work - - $ sendhttpv2peer << EOF - > command changesetdata - > fields eval:[b'phase', b'parents', b'revision'] - > revisions eval:[{ - > b'type': b'changesetdagrange', - > b'roots': [b'\x33\x90\xef\x85\x00\x73\xfb\xc2\xf0\xdf\xff\x22\x44\x34\x2c\x8e\x92\x29\x01\x3a'], - > b'heads': [ - > b'\x0b\xb8\xad\x89\x4a\x15\xb1\x53\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f\x2a\x4b\x28\xdd', - > b'\xea\xe5\xf8\x2c\x2e\x62\x23\x68\xd2\x7d\xae\xcb\x76\xb7\xe3\x93\xd0\xf2\x42\x11', - > ]}] - > EOF - creating http peer for wire protocol version 2 - sending changesetdata command - response: gen[ - { - b'totalitems': 3 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 63 - ] - ], - b'node': b'u\x92\x91~\x1c>\x82g|\xb0\xa4\xbcq\\\xa2]\xd1-(\xc1', - b'parents': [ - b'3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ], - b'phase': b'public' - }, - b'7f144aea0ba742713887b564d57e9d12f12ff382\ntest\n0 0\na\nb\n\ncommit 1', - { - b'fieldsfollowing': [ - [ - b'revision', - 61 - ] - ], - b'node': b'\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xdd', - b'parents': [ - b'u\x92\x91~\x1c>\x82g|\xb0\xa4\xbcq\\\xa2]\xd1-(\xc1', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ], - b'phase': b'public' - }, - b'37f0a2d1c28ffe4b879109a7d1bbf8f07b3c763b\ntest\n0 0\nb\n\ncommit 2', - { - b'fieldsfollowing': [ - [ - b'revision', - 61 - ] - ], - b'node': b'\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11', - b'parents': [ - b'3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ], - b'phase': b'draft' - }, - b'1b74476799ec8318045db759b1b4bcc9b839d0aa\ntest\n0 0\na\n\ncommit 3' - ] - - $ cat error.log diff --git a/tests/test-wireproto-command-filedata.t b/tests/test-wireproto-command-filedata.t deleted file mode 100644 --- a/tests/test-wireproto-command-filedata.t +++ /dev/null @@ -1,364 +0,0 @@ - $ . $TESTDIR/wireprotohelpers.sh - - $ hg init server - $ enablehttpv2 server - $ cd server - $ cat > a << EOF - > a0 - > 00000000000000000000000000000000000000 - > 11111111111111111111111111111111111111 - > EOF - $ echo b0 > b - $ mkdir -p dir0/child0 dir0/child1 dir1 - $ echo c0 > dir0/c - $ echo d0 > dir0/d - $ echo e0 > dir0/child0/e - $ echo f0 > dir0/child1/f - $ hg -q commit -A -m 'commit 0' - - $ echo a1 >> a - $ echo d1 > dir0/d - $ hg commit -m 'commit 1' - $ echo f1 > dir0/child1/f - $ hg commit -m 'commit 2' - - $ hg -q up -r 0 - $ echo a2 >> a - $ hg commit -m 'commit 3' - created new head - -Create multiple heads introducing the same changeset - - $ hg -q up -r 0 - $ echo foo > dupe-file - $ hg commit -Am 'dupe 1' - adding dupe-file - created new head - $ hg -q up -r 0 - $ echo foo > dupe-file - $ hg commit -Am 'dupe 2' - adding dupe-file - created new head - - $ hg log -G -T '{rev}:{node} {desc}\n' - @ 5:732c3dd7bee94242de656000e5f458e7ccfe2828 dupe 2 - | - | o 4:4334f10897d13c3e8beb4b636f7272b4ec2d0322 dupe 1 - |/ - | o 3:5ce944d7fece1252dae06c34422b573c191b9489 commit 3 - |/ - | o 2:b3c27db01410dae01e5485d425b1440078df540c commit 2 - | | - | o 1:3ef5e551f219ba505481d34d6b0316b017fa3f00 commit 1 - |/ - o 0:91b232a2253ce0638496f67bdfd7a4933fb51b25 commit 0 - - - $ hg --debug debugindex a - rev linkrev nodeid p1 p2 - 0 0 649d149df43d83882523b7fb1e6a3af6f1907b39 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 - 1 1 0a86321f1379d1a9ecd0579a22977af7a5acaf11 649d149df43d83882523b7fb1e6a3af6f1907b39 0000000000000000000000000000000000000000 - 2 3 7e5801b6d5f03a5a54f3c47b583f7567aad43e5b 649d149df43d83882523b7fb1e6a3af6f1907b39 0000000000000000000000000000000000000000 - - $ hg --debug debugindex dir0/child0/e - rev linkrev nodeid p1 p2 - 0 0 bbba6c06b30f443d34ff841bc985c4d0827c6be4 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 - - $ hg --debug debugindex dupe-file - rev linkrev nodeid p1 p2 - 0 4 2ed2a3912a0b24502043eae84ee4b279c18b90dd 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 - - $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log - $ cat hg.pid > $DAEMON_PIDS - -Missing arguments is an error - - $ sendhttpv2peer << EOF - > command filedata - > EOF - creating http peer for wire protocol version 2 - sending filedata command - abort: missing required arguments: nodes, path - [255] - - $ sendhttpv2peer << EOF - > command filedata - > nodes eval:[] - > EOF - creating http peer for wire protocol version 2 - sending filedata command - abort: missing required arguments: path - [255] - -Unknown node is an error - - $ sendhttpv2peer << EOF - > command filedata - > nodes eval:[b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'] - > path eval:b'a' - > EOF - creating http peer for wire protocol version 2 - sending filedata command - abort: unknown file node: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - [255] - -Fetching a single revision returns just metadata by default - - $ sendhttpv2peer << EOF - > command filedata - > nodes eval:[b'\x0a\x86\x32\x1f\x13\x79\xd1\xa9\xec\xd0\x57\x9a\x22\x97\x7a\xf7\xa5\xac\xaf\x11'] - > path eval:b'a' - > EOF - creating http peer for wire protocol version 2 - sending filedata command - response: gen[ - { - b'totalitems': 1 - }, - { - b'node': b'\n\x862\x1f\x13y\xd1\xa9\xec\xd0W\x9a"\x97z\xf7\xa5\xac\xaf\x11' - } - ] - -Requesting parents works - - $ sendhttpv2peer << EOF - > command filedata - > nodes eval:[b'\x0a\x86\x32\x1f\x13\x79\xd1\xa9\xec\xd0\x57\x9a\x22\x97\x7a\xf7\xa5\xac\xaf\x11'] - > path eval:b'a' - > fields eval:[b'parents'] - > EOF - creating http peer for wire protocol version 2 - sending filedata command - response: gen[ - { - b'totalitems': 1 - }, - { - b'node': b'\n\x862\x1f\x13y\xd1\xa9\xec\xd0W\x9a"\x97z\xf7\xa5\xac\xaf\x11', - b'parents': [ - b'd\x9d\x14\x9d\xf4=\x83\x88%#\xb7\xfb\x1ej:\xf6\xf1\x90{9', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ] - } - ] - -Requesting revision data works -(haveparents defaults to False, so fulltext is emitted) - - $ sendhttpv2peer << EOF - > command filedata - > nodes eval:[b'\x0a\x86\x32\x1f\x13\x79\xd1\xa9\xec\xd0\x57\x9a\x22\x97\x7a\xf7\xa5\xac\xaf\x11'] - > path eval:b'a' - > fields eval:[b'revision'] - > EOF - creating http peer for wire protocol version 2 - sending filedata command - response: gen[ - { - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 84 - ] - ], - b'node': b'\n\x862\x1f\x13y\xd1\xa9\xec\xd0W\x9a"\x97z\xf7\xa5\xac\xaf\x11' - }, - b'a0\n00000000000000000000000000000000000000\n11111111111111111111111111111111111111\na1\n' - ] - -haveparents=False should be same as above - - $ sendhttpv2peer << EOF - > command filedata - > nodes eval:[b'\x0a\x86\x32\x1f\x13\x79\xd1\xa9\xec\xd0\x57\x9a\x22\x97\x7a\xf7\xa5\xac\xaf\x11'] - > path eval:b'a' - > fields eval:[b'revision'] - > haveparents eval:False - > EOF - creating http peer for wire protocol version 2 - sending filedata command - response: gen[ - { - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 84 - ] - ], - b'node': b'\n\x862\x1f\x13y\xd1\xa9\xec\xd0W\x9a"\x97z\xf7\xa5\xac\xaf\x11' - }, - b'a0\n00000000000000000000000000000000000000\n11111111111111111111111111111111111111\na1\n' - ] - -haveparents=True should emit a delta - - $ sendhttpv2peer << EOF - > command filedata - > nodes eval:[b'\x0a\x86\x32\x1f\x13\x79\xd1\xa9\xec\xd0\x57\x9a\x22\x97\x7a\xf7\xa5\xac\xaf\x11'] - > path eval:b'a' - > fields eval:[b'revision'] - > haveparents eval:True - > EOF - creating http peer for wire protocol version 2 - sending filedata command - response: gen[ - { - b'totalitems': 1 - }, - { - b'deltabasenode': b'd\x9d\x14\x9d\xf4=\x83\x88%#\xb7\xfb\x1ej:\xf6\xf1\x90{9', - b'fieldsfollowing': [ - [ - b'delta', - 15 - ] - ], - b'node': b'\n\x862\x1f\x13y\xd1\xa9\xec\xd0W\x9a"\x97z\xf7\xa5\xac\xaf\x11' - }, - b'\x00\x00\x00Q\x00\x00\x00Q\x00\x00\x00\x03a1\n' - ] - -Requesting multiple revisions works -(first revision is a fulltext since haveparents=False by default) - - $ sendhttpv2peer << EOF - > command filedata - > nodes eval:[b'\x64\x9d\x14\x9d\xf4\x3d\x83\x88\x25\x23\xb7\xfb\x1e\x6a\x3a\xf6\xf1\x90\x7b\x39', b'\x0a\x86\x32\x1f\x13\x79\xd1\xa9\xec\xd0\x57\x9a\x22\x97\x7a\xf7\xa5\xac\xaf\x11'] - > path eval:b'a' - > fields eval:[b'revision'] - > EOF - creating http peer for wire protocol version 2 - sending filedata command - response: gen[ - { - b'totalitems': 2 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 81 - ] - ], - b'node': b'd\x9d\x14\x9d\xf4=\x83\x88%#\xb7\xfb\x1ej:\xf6\xf1\x90{9' - }, - b'a0\n00000000000000000000000000000000000000\n11111111111111111111111111111111111111\n', - { - b'deltabasenode': b'd\x9d\x14\x9d\xf4=\x83\x88%#\xb7\xfb\x1ej:\xf6\xf1\x90{9', - b'fieldsfollowing': [ - [ - b'delta', - 15 - ] - ], - b'node': b'\n\x862\x1f\x13y\xd1\xa9\xec\xd0W\x9a"\x97z\xf7\xa5\xac\xaf\x11' - }, - b'\x00\x00\x00Q\x00\x00\x00Q\x00\x00\x00\x03a1\n' - ] - -Revisions are sorted by DAG order, parents first - - $ sendhttpv2peer << EOF - > command filedata - > nodes eval:[b'\x0a\x86\x32\x1f\x13\x79\xd1\xa9\xec\xd0\x57\x9a\x22\x97\x7a\xf7\xa5\xac\xaf\x11', b'\x64\x9d\x14\x9d\xf4\x3d\x83\x88\x25\x23\xb7\xfb\x1e\x6a\x3a\xf6\xf1\x90\x7b\x39'] - > path eval:b'a' - > fields eval:[b'revision'] - > EOF - creating http peer for wire protocol version 2 - sending filedata command - response: gen[ - { - b'totalitems': 2 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 81 - ] - ], - b'node': b'd\x9d\x14\x9d\xf4=\x83\x88%#\xb7\xfb\x1ej:\xf6\xf1\x90{9' - }, - b'a0\n00000000000000000000000000000000000000\n11111111111111111111111111111111111111\n', - { - b'deltabasenode': b'd\x9d\x14\x9d\xf4=\x83\x88%#\xb7\xfb\x1ej:\xf6\xf1\x90{9', - b'fieldsfollowing': [ - [ - b'delta', - 15 - ] - ], - b'node': b'\n\x862\x1f\x13y\xd1\xa9\xec\xd0W\x9a"\x97z\xf7\xa5\xac\xaf\x11' - }, - b'\x00\x00\x00Q\x00\x00\x00Q\x00\x00\x00\x03a1\n' - ] - -Requesting parents and revision data works - - $ sendhttpv2peer << EOF - > command filedata - > nodes eval:[b'\x7e\x58\x01\xb6\xd5\xf0\x3a\x5a\x54\xf3\xc4\x7b\x58\x3f\x75\x67\xaa\xd4\x3e\x5b'] - > path eval:b'a' - > fields eval:[b'parents', b'revision'] - > EOF - creating http peer for wire protocol version 2 - sending filedata command - response: gen[ - { - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 84 - ] - ], - b'node': b'~X\x01\xb6\xd5\xf0:ZT\xf3\xc4{X?ug\xaa\xd4>[', - b'parents': [ - b'd\x9d\x14\x9d\xf4=\x83\x88%#\xb7\xfb\x1ej:\xf6\xf1\x90{9', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ] - }, - b'a0\n00000000000000000000000000000000000000\n11111111111111111111111111111111111111\na2\n' - ] - -Linknode for duplicate revision is the initial revision - - $ sendhttpv2peer << EOF - > command filedata - > nodes eval:[b'\x2e\xd2\xa3\x91\x2a\x0b\x24\x50\x20\x43\xea\xe8\x4e\xe4\xb2\x79\xc1\x8b\x90\xdd'] - > path eval:b'dupe-file' - > fields eval:[b'linknode', b'parents', b'revision'] - > EOF - creating http peer for wire protocol version 2 - sending filedata command - response: gen[ - { - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 4 - ] - ], - b'linknode': b'C4\xf1\x08\x97\xd1<>\x8b\xebKcorr\xb4\xec-\x03"', - b'node': b'.\xd2\xa3\x91*\x0b$P C\xea\xe8N\xe4\xb2y\xc1\x8b\x90\xdd', - b'parents': [ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ] - }, - b'foo\n' - ] - - $ cat error.log diff --git a/tests/test-wireproto-command-filesdata.t b/tests/test-wireproto-command-filesdata.t deleted file mode 100644 --- a/tests/test-wireproto-command-filesdata.t +++ /dev/null @@ -1,1298 +0,0 @@ - $ . $TESTDIR/wireprotohelpers.sh - - $ hg init server - $ enablehttpv2 server - $ cd server - $ cat > a << EOF - > a0 - > 00000000000000000000000000000000000000 - > 11111111111111111111111111111111111111 - > EOF - $ cat > b << EOF - > b0 - > aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - > bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb - > EOF - $ mkdir -p dir0/child0 dir0/child1 dir1 - $ echo c0 > dir0/c - $ echo d0 > dir0/d - $ echo e0 > dir0/child0/e - $ echo f0 > dir0/child1/f - $ hg -q commit -A -m 'commit 0' - - $ echo a1 >> a - $ echo d1 > dir0/d - $ echo g0 > g - $ echo h0 > h - $ hg -q commit -A -m 'commit 1' - $ echo f1 > dir0/child1/f - $ echo i0 > dir0/i - $ hg -q commit -A -m 'commit 2' - - $ hg -q up -r 0 - $ echo a2 >> a - $ hg commit -m 'commit 3' - created new head - -Create multiple heads introducing the same file nodefile node - - $ hg -q up -r 0 - $ echo foo > dupe-file - $ hg commit -Am 'dupe 1' - adding dupe-file - created new head - $ hg -q up -r 0 - $ echo foo > dupe-file - $ hg commit -Am 'dupe 2' - adding dupe-file - created new head - - $ hg log -G -T '{rev}:{node} {desc}\n' - @ 5:47fc30580911232cb264675b402819deddf6c6f0 dupe 2 - | - | o 4:b16cce2967c1749ef4f4e3086a806cfbad8a3af7 dupe 1 - |/ - | o 3:476fbf122cd82f6726f0191ff146f67140946abc commit 3 - |/ - | o 2:b91c03cbba3519ab149b6cd0a0afbdb5cf1b5c8a commit 2 - | | - | o 1:5b0b1a23577e205ea240e39c9704e28d7697cbd8 commit 1 - |/ - o 0:6e875ff18c227659ad6143bb3580c65700734884 commit 0 - - - $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log - $ cat hg.pid > $DAEMON_PIDS - -Missing arguments is an error - - $ sendhttpv2peer << EOF - > command filesdata - > EOF - creating http peer for wire protocol version 2 - sending filesdata command - abort: missing required arguments: revisions - [255] - -Bad pattern to pathfilter is rejected - - $ sendhttpv2peer << EOF - > command filesdata - > revisions eval:[{ - > b'type': b'changesetexplicit', - > b'nodes': [ - > b'\x5b\x0b\x1a\x23\x57\x7e\x20\x5e\xa2\x40\xe3\x9c\x97\x04\xe2\x8d\x76\x97\xcb\xd8', - > ]}] - > pathfilter eval:{b'include': [b'bad:foo']} - > EOF - creating http peer for wire protocol version 2 - sending filesdata command - abort: include pattern must begin with `path:` or `rootfilesin:`; got bad:foo - [255] - - $ sendhttpv2peer << EOF - > command filesdata - > revisions eval:[{ - > b'type': b'changesetexplicit', - > b'nodes': [ - > b'\x5b\x0b\x1a\x23\x57\x7e\x20\x5e\xa2\x40\xe3\x9c\x97\x04\xe2\x8d\x76\x97\xcb\xd8', - > ]}] - > pathfilter eval:{b'exclude': [b'glob:foo']} - > EOF - creating http peer for wire protocol version 2 - sending filesdata command - abort: exclude pattern must begin with `path:` or `rootfilesin:`; got glob:foo - [255] - -Fetching a single changeset without parents fetches all files - - $ sendhttpv2peer << EOF - > command filesdata - > revisions eval:[{ - > b'type': b'changesetexplicit', - > b'nodes': [ - > b'\x5b\x0b\x1a\x23\x57\x7e\x20\x5e\xa2\x40\xe3\x9c\x97\x04\xe2\x8d\x76\x97\xcb\xd8', - > ]}] - > EOF - creating http peer for wire protocol version 2 - sending filesdata command - response: gen[ - { - b'totalitems': 8, - b'totalpaths': 8 - }, - { - b'path': b'a', - b'totalitems': 1 - }, - { - b'node': b'\n\x862\x1f\x13y\xd1\xa9\xec\xd0W\x9a"\x97z\xf7\xa5\xac\xaf\x11' - }, - { - b'path': b'b', - b'totalitems': 1 - }, - { - b'node': b'\x88\xbac\xb8\xd8\xc6 :\xc6z\xc9\x98\xac\xd9\x17K\xf7\x05!\xb2' - }, - { - b'path': b'dir0/c', - b'totalitems': 1 - }, - { - b'node': b'\x91DE4j\x0c\xa0b\x9b\xd4|\xeb]\xfe\x07\xe4\xd4\xcf%\x01' - }, - { - b'path': b'dir0/child0/e', - b'totalitems': 1 - }, - { - b'node': b'\xbb\xbal\x06\xb3\x0fD=4\xff\x84\x1b\xc9\x85\xc4\xd0\x82|k\xe4' - }, - { - b'path': b'dir0/child1/f', - b'totalitems': 1 - }, - { - b'node': b'\x12\xfc}\xcdw;Z\n\x92\x9c\xe1\x95"\x80\x83\xc6\xdd\xc9\xce\xc4' - }, - { - b'path': b'dir0/d', - b'totalitems': 1 - }, - { - b'node': b'\x93\x88)\xad\x01R}2\xba\x06_\x81#6\xfe\xc7\x9d\xdd9G' - }, - { - b'path': b'g', - b'totalitems': 1 - }, - { - b'node': b'\xde\xca\xba5DFjI\x95r\xe9\x0f\xac\xe6\xfa\x0c!k\xba\x8c' - }, - { - b'path': b'h', - b'totalitems': 1 - }, - { - b'node': b'\x03A\xfc\x84\x1b\xb5\xb4\xba\x93\xb2mM\xdaa\xf7y6]\xb3K' - } - ] - -Fetching a single changeset saying parents data is available fetches just new files - - $ sendhttpv2peer << EOF - > command filesdata - > revisions eval:[{ - > b'type': b'changesetexplicit', - > b'nodes': [ - > b'\x5b\x0b\x1a\x23\x57\x7e\x20\x5e\xa2\x40\xe3\x9c\x97\x04\xe2\x8d\x76\x97\xcb\xd8', - > ]}] - > haveparents eval:True - > EOF - creating http peer for wire protocol version 2 - sending filesdata command - response: gen[ - { - b'totalitems': 4, - b'totalpaths': 4 - }, - { - b'path': b'a', - b'totalitems': 1 - }, - { - b'node': b'\n\x862\x1f\x13y\xd1\xa9\xec\xd0W\x9a"\x97z\xf7\xa5\xac\xaf\x11' - }, - { - b'path': b'dir0/d', - b'totalitems': 1 - }, - { - b'node': b'\x93\x88)\xad\x01R}2\xba\x06_\x81#6\xfe\xc7\x9d\xdd9G' - }, - { - b'path': b'g', - b'totalitems': 1 - }, - { - b'node': b'\xde\xca\xba5DFjI\x95r\xe9\x0f\xac\xe6\xfa\x0c!k\xba\x8c' - }, - { - b'path': b'h', - b'totalitems': 1 - }, - { - b'node': b'\x03A\xfc\x84\x1b\xb5\xb4\xba\x93\xb2mM\xdaa\xf7y6]\xb3K' - } - ] - -A path filter for a sub-directory is honored - - $ sendhttpv2peer << EOF - > command filesdata - > revisions eval:[{ - > b'type': b'changesetexplicit', - > b'nodes': [ - > b'\x5b\x0b\x1a\x23\x57\x7e\x20\x5e\xa2\x40\xe3\x9c\x97\x04\xe2\x8d\x76\x97\xcb\xd8', - > ]}] - > haveparents eval:True - > pathfilter eval:{b'include': [b'path:dir0']} - > EOF - creating http peer for wire protocol version 2 - sending filesdata command - response: gen[ - { - b'totalitems': 1, - b'totalpaths': 1 - }, - { - b'path': b'dir0/d', - b'totalitems': 1 - }, - { - b'node': b'\x93\x88)\xad\x01R}2\xba\x06_\x81#6\xfe\xc7\x9d\xdd9G' - } - ] - - $ sendhttpv2peer << EOF - > command filesdata - > revisions eval:[{ - > b'type': b'changesetexplicit', - > b'nodes': [ - > b'\x5b\x0b\x1a\x23\x57\x7e\x20\x5e\xa2\x40\xe3\x9c\x97\x04\xe2\x8d\x76\x97\xcb\xd8', - > ]}] - > haveparents eval:True - > pathfilter eval:{b'exclude': [b'path:a', b'path:g']} - > EOF - creating http peer for wire protocol version 2 - sending filesdata command - response: gen[ - { - b'totalitems': 2, - b'totalpaths': 2 - }, - { - b'path': b'dir0/d', - b'totalitems': 1 - }, - { - b'node': b'\x93\x88)\xad\x01R}2\xba\x06_\x81#6\xfe\xc7\x9d\xdd9G' - }, - { - b'path': b'h', - b'totalitems': 1 - }, - { - b'node': b'\x03A\xfc\x84\x1b\xb5\xb4\xba\x93\xb2mM\xdaa\xf7y6]\xb3K' - } - ] - -Requesting multiple changeset nodes without haveparents sends all data for both - - $ sendhttpv2peer << EOF - > command filesdata - > revisions eval:[{ - > b'type': b'changesetexplicit', - > b'nodes': [ - > b'\x5b\x0b\x1a\x23\x57\x7e\x20\x5e\xa2\x40\xe3\x9c\x97\x04\xe2\x8d\x76\x97\xcb\xd8', - > b'\xb9\x1c\x03\xcb\xba\x35\x19\xab\x14\x9b\x6c\xd0\xa0\xaf\xbd\xb5\xcf\x1b\x5c\x8a', - > ]}] - > EOF - creating http peer for wire protocol version 2 - sending filesdata command - response: gen[ - { - b'totalitems': 10, - b'totalpaths': 9 - }, - { - b'path': b'a', - b'totalitems': 1 - }, - { - b'node': b'\n\x862\x1f\x13y\xd1\xa9\xec\xd0W\x9a"\x97z\xf7\xa5\xac\xaf\x11' - }, - { - b'path': b'b', - b'totalitems': 1 - }, - { - b'node': b'\x88\xbac\xb8\xd8\xc6 :\xc6z\xc9\x98\xac\xd9\x17K\xf7\x05!\xb2' - }, - { - b'path': b'dir0/c', - b'totalitems': 1 - }, - { - b'node': b'\x91DE4j\x0c\xa0b\x9b\xd4|\xeb]\xfe\x07\xe4\xd4\xcf%\x01' - }, - { - b'path': b'dir0/child0/e', - b'totalitems': 1 - }, - { - b'node': b'\xbb\xbal\x06\xb3\x0fD=4\xff\x84\x1b\xc9\x85\xc4\xd0\x82|k\xe4' - }, - { - b'path': b'dir0/child1/f', - b'totalitems': 2 - }, - { - b'node': b'\x12\xfc}\xcdw;Z\n\x92\x9c\xe1\x95"\x80\x83\xc6\xdd\xc9\xce\xc4' - }, - { - b'node': b'(\xc7v\xae\x08\xd0\xd5^\xb4\x06H\xb4\x01\xb9\x0f\xf5DH4\x8e' - }, - { - b'path': b'dir0/d', - b'totalitems': 1 - }, - { - b'node': b'\x93\x88)\xad\x01R}2\xba\x06_\x81#6\xfe\xc7\x9d\xdd9G' - }, - { - b'path': b'dir0/i', - b'totalitems': 1 - }, - { - b'node': b'\xd7t\xb5\x80Jq\xfd1\xe1\xae\x05\xea\x8e2\xdd\x9b\xa3\xd8S\xd7' - }, - { - b'path': b'g', - b'totalitems': 1 - }, - { - b'node': b'\xde\xca\xba5DFjI\x95r\xe9\x0f\xac\xe6\xfa\x0c!k\xba\x8c' - }, - { - b'path': b'h', - b'totalitems': 1 - }, - { - b'node': b'\x03A\xfc\x84\x1b\xb5\xb4\xba\x93\xb2mM\xdaa\xf7y6]\xb3K' - } - ] - -Requesting multiple changeset nodes with haveparents sends incremental data for both - - $ sendhttpv2peer << EOF - > command filesdata - > revisions eval:[{ - > b'type': b'changesetexplicit', - > b'nodes': [ - > b'\x5b\x0b\x1a\x23\x57\x7e\x20\x5e\xa2\x40\xe3\x9c\x97\x04\xe2\x8d\x76\x97\xcb\xd8', - > b'\xb9\x1c\x03\xcb\xba\x35\x19\xab\x14\x9b\x6c\xd0\xa0\xaf\xbd\xb5\xcf\x1b\x5c\x8a', - > ]}] - > haveparents eval:True - > EOF - creating http peer for wire protocol version 2 - sending filesdata command - response: gen[ - { - b'totalitems': 6, - b'totalpaths': 6 - }, - { - b'path': b'a', - b'totalitems': 1 - }, - { - b'node': b'\n\x862\x1f\x13y\xd1\xa9\xec\xd0W\x9a"\x97z\xf7\xa5\xac\xaf\x11' - }, - { - b'path': b'dir0/child1/f', - b'totalitems': 1 - }, - { - b'node': b'(\xc7v\xae\x08\xd0\xd5^\xb4\x06H\xb4\x01\xb9\x0f\xf5DH4\x8e' - }, - { - b'path': b'dir0/d', - b'totalitems': 1 - }, - { - b'node': b'\x93\x88)\xad\x01R}2\xba\x06_\x81#6\xfe\xc7\x9d\xdd9G' - }, - { - b'path': b'dir0/i', - b'totalitems': 1 - }, - { - b'node': b'\xd7t\xb5\x80Jq\xfd1\xe1\xae\x05\xea\x8e2\xdd\x9b\xa3\xd8S\xd7' - }, - { - b'path': b'g', - b'totalitems': 1 - }, - { - b'node': b'\xde\xca\xba5DFjI\x95r\xe9\x0f\xac\xe6\xfa\x0c!k\xba\x8c' - }, - { - b'path': b'h', - b'totalitems': 1 - }, - { - b'node': b'\x03A\xfc\x84\x1b\xb5\xb4\xba\x93\xb2mM\xdaa\xf7y6]\xb3K' - } - ] - -Requesting parents works - - $ sendhttpv2peer << EOF - > command filesdata - > revisions eval:[{ - > b'type': b'changesetexplicit', - > b'nodes': [ - > b'\x5b\x0b\x1a\x23\x57\x7e\x20\x5e\xa2\x40\xe3\x9c\x97\x04\xe2\x8d\x76\x97\xcb\xd8', - > ]}] - > fields eval:[b'parents'] - > EOF - creating http peer for wire protocol version 2 - sending filesdata command - response: gen[ - { - b'totalitems': 8, - b'totalpaths': 8 - }, - { - b'path': b'a', - b'totalitems': 1 - }, - { - b'node': b'\n\x862\x1f\x13y\xd1\xa9\xec\xd0W\x9a"\x97z\xf7\xa5\xac\xaf\x11', - b'parents': [ - b'd\x9d\x14\x9d\xf4=\x83\x88%#\xb7\xfb\x1ej:\xf6\xf1\x90{9', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ] - }, - { - b'path': b'b', - b'totalitems': 1 - }, - { - b'node': b'\x88\xbac\xb8\xd8\xc6 :\xc6z\xc9\x98\xac\xd9\x17K\xf7\x05!\xb2', - b'parents': [ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ] - }, - { - b'path': b'dir0/c', - b'totalitems': 1 - }, - { - b'node': b'\x91DE4j\x0c\xa0b\x9b\xd4|\xeb]\xfe\x07\xe4\xd4\xcf%\x01', - b'parents': [ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ] - }, - { - b'path': b'dir0/child0/e', - b'totalitems': 1 - }, - { - b'node': b'\xbb\xbal\x06\xb3\x0fD=4\xff\x84\x1b\xc9\x85\xc4\xd0\x82|k\xe4', - b'parents': [ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ] - }, - { - b'path': b'dir0/child1/f', - b'totalitems': 1 - }, - { - b'node': b'\x12\xfc}\xcdw;Z\n\x92\x9c\xe1\x95"\x80\x83\xc6\xdd\xc9\xce\xc4', - b'parents': [ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ] - }, - { - b'path': b'dir0/d', - b'totalitems': 1 - }, - { - b'node': b'\x93\x88)\xad\x01R}2\xba\x06_\x81#6\xfe\xc7\x9d\xdd9G', - b'parents': [ - b'S\x82\x06\xdc\x97\x1eR\x15@\xd6\x84:\xbf\xe6\xd1`2\xf6\xd4&', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ] - }, - { - b'path': b'g', - b'totalitems': 1 - }, - { - b'node': b'\xde\xca\xba5DFjI\x95r\xe9\x0f\xac\xe6\xfa\x0c!k\xba\x8c', - b'parents': [ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ] - }, - { - b'path': b'h', - b'totalitems': 1 - }, - { - b'node': b'\x03A\xfc\x84\x1b\xb5\xb4\xba\x93\xb2mM\xdaa\xf7y6]\xb3K', - b'parents': [ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ] - } - ] - -Requesting revision data works -(haveparents defaults to False, so fulltext is emitted) - - $ sendhttpv2peer << EOF - > command filesdata - > revisions eval:[{ - > b'type': b'changesetexplicit', - > b'nodes': [ - > b'\x5b\x0b\x1a\x23\x57\x7e\x20\x5e\xa2\x40\xe3\x9c\x97\x04\xe2\x8d\x76\x97\xcb\xd8', - > ]}] - > fields eval:[b'revision'] - > EOF - creating http peer for wire protocol version 2 - sending filesdata command - response: gen[ - { - b'totalitems': 8, - b'totalpaths': 8 - }, - { - b'path': b'a', - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 84 - ] - ], - b'node': b'\n\x862\x1f\x13y\xd1\xa9\xec\xd0W\x9a"\x97z\xf7\xa5\xac\xaf\x11' - }, - b'a0\n00000000000000000000000000000000000000\n11111111111111111111111111111111111111\na1\n', - { - b'path': b'b', - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 81 - ] - ], - b'node': b'\x88\xbac\xb8\xd8\xc6 :\xc6z\xc9\x98\xac\xd9\x17K\xf7\x05!\xb2' - }, - b'b0\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\nbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n', - { - b'path': b'dir0/c', - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 3 - ] - ], - b'node': b'\x91DE4j\x0c\xa0b\x9b\xd4|\xeb]\xfe\x07\xe4\xd4\xcf%\x01' - }, - b'c0\n', - { - b'path': b'dir0/child0/e', - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 3 - ] - ], - b'node': b'\xbb\xbal\x06\xb3\x0fD=4\xff\x84\x1b\xc9\x85\xc4\xd0\x82|k\xe4' - }, - b'e0\n', - { - b'path': b'dir0/child1/f', - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 3 - ] - ], - b'node': b'\x12\xfc}\xcdw;Z\n\x92\x9c\xe1\x95"\x80\x83\xc6\xdd\xc9\xce\xc4' - }, - b'f0\n', - { - b'path': b'dir0/d', - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 3 - ] - ], - b'node': b'\x93\x88)\xad\x01R}2\xba\x06_\x81#6\xfe\xc7\x9d\xdd9G' - }, - b'd1\n', - { - b'path': b'g', - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 3 - ] - ], - b'node': b'\xde\xca\xba5DFjI\x95r\xe9\x0f\xac\xe6\xfa\x0c!k\xba\x8c' - }, - b'g0\n', - { - b'path': b'h', - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 3 - ] - ], - b'node': b'\x03A\xfc\x84\x1b\xb5\xb4\xba\x93\xb2mM\xdaa\xf7y6]\xb3K' - }, - b'h0\n' - ] - -haveparents=False should be same as above - - $ sendhttpv2peer << EOF - > command filesdata - > revisions eval:[{ - > b'type': b'changesetexplicit', - > b'nodes': [ - > b'\x5b\x0b\x1a\x23\x57\x7e\x20\x5e\xa2\x40\xe3\x9c\x97\x04\xe2\x8d\x76\x97\xcb\xd8', - > ]}] - > fields eval:[b'revision'] - > haveparents eval:False - > EOF - creating http peer for wire protocol version 2 - sending filesdata command - response: gen[ - { - b'totalitems': 8, - b'totalpaths': 8 - }, - { - b'path': b'a', - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 84 - ] - ], - b'node': b'\n\x862\x1f\x13y\xd1\xa9\xec\xd0W\x9a"\x97z\xf7\xa5\xac\xaf\x11' - }, - b'a0\n00000000000000000000000000000000000000\n11111111111111111111111111111111111111\na1\n', - { - b'path': b'b', - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 81 - ] - ], - b'node': b'\x88\xbac\xb8\xd8\xc6 :\xc6z\xc9\x98\xac\xd9\x17K\xf7\x05!\xb2' - }, - b'b0\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\nbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n', - { - b'path': b'dir0/c', - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 3 - ] - ], - b'node': b'\x91DE4j\x0c\xa0b\x9b\xd4|\xeb]\xfe\x07\xe4\xd4\xcf%\x01' - }, - b'c0\n', - { - b'path': b'dir0/child0/e', - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 3 - ] - ], - b'node': b'\xbb\xbal\x06\xb3\x0fD=4\xff\x84\x1b\xc9\x85\xc4\xd0\x82|k\xe4' - }, - b'e0\n', - { - b'path': b'dir0/child1/f', - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 3 - ] - ], - b'node': b'\x12\xfc}\xcdw;Z\n\x92\x9c\xe1\x95"\x80\x83\xc6\xdd\xc9\xce\xc4' - }, - b'f0\n', - { - b'path': b'dir0/d', - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 3 - ] - ], - b'node': b'\x93\x88)\xad\x01R}2\xba\x06_\x81#6\xfe\xc7\x9d\xdd9G' - }, - b'd1\n', - { - b'path': b'g', - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 3 - ] - ], - b'node': b'\xde\xca\xba5DFjI\x95r\xe9\x0f\xac\xe6\xfa\x0c!k\xba\x8c' - }, - b'g0\n', - { - b'path': b'h', - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 3 - ] - ], - b'node': b'\x03A\xfc\x84\x1b\xb5\xb4\xba\x93\xb2mM\xdaa\xf7y6]\xb3K' - }, - b'h0\n' - ] - -haveparents=True should emit a delta - - $ sendhttpv2peer << EOF - > command filesdata - > revisions eval:[{ - > b'type': b'changesetexplicit', - > b'nodes': [ - > b'\x5b\x0b\x1a\x23\x57\x7e\x20\x5e\xa2\x40\xe3\x9c\x97\x04\xe2\x8d\x76\x97\xcb\xd8', - > ]}] - > fields eval:[b'revision'] - > haveparents eval:True - > EOF - creating http peer for wire protocol version 2 - sending filesdata command - response: gen[ - { - b'totalitems': 4, - b'totalpaths': 4 - }, - { - b'path': b'a', - b'totalitems': 1 - }, - { - b'deltabasenode': b'd\x9d\x14\x9d\xf4=\x83\x88%#\xb7\xfb\x1ej:\xf6\xf1\x90{9', - b'fieldsfollowing': [ - [ - b'delta', - 15 - ] - ], - b'node': b'\n\x862\x1f\x13y\xd1\xa9\xec\xd0W\x9a"\x97z\xf7\xa5\xac\xaf\x11' - }, - b'\x00\x00\x00Q\x00\x00\x00Q\x00\x00\x00\x03a1\n', - { - b'path': b'dir0/d', - b'totalitems': 1 - }, - { - b'deltabasenode': b'S\x82\x06\xdc\x97\x1eR\x15@\xd6\x84:\xbf\xe6\xd1`2\xf6\xd4&', - b'fieldsfollowing': [ - [ - b'delta', - 15 - ] - ], - b'node': b'\x93\x88)\xad\x01R}2\xba\x06_\x81#6\xfe\xc7\x9d\xdd9G' - }, - b'\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x03d1\n', - { - b'path': b'g', - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 3 - ] - ], - b'node': b'\xde\xca\xba5DFjI\x95r\xe9\x0f\xac\xe6\xfa\x0c!k\xba\x8c' - }, - b'g0\n', - { - b'path': b'h', - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 3 - ] - ], - b'node': b'\x03A\xfc\x84\x1b\xb5\xb4\xba\x93\xb2mM\xdaa\xf7y6]\xb3K' - }, - b'h0\n' - ] - -Requesting multiple revisions works -(first revision is a fulltext since haveparents=False by default) - - $ sendhttpv2peer << EOF - > command filesdata - > revisions eval:[{ - > b'type': b'changesetexplicit', - > b'nodes': [ - > b'\x6e\x87\x5f\xf1\x8c\x22\x76\x59\xad\x61\x43\xbb\x35\x80\xc6\x57\x00\x73\x48\x84', - > b'\x5b\x0b\x1a\x23\x57\x7e\x20\x5e\xa2\x40\xe3\x9c\x97\x04\xe2\x8d\x76\x97\xcb\xd8', - > b'\xb9\x1c\x03\xcb\xba\x35\x19\xab\x14\x9b\x6c\xd0\xa0\xaf\xbd\xb5\xcf\x1b\x5c\x8a', - > ]}] - > fields eval:[b'revision'] - > EOF - creating http peer for wire protocol version 2 - sending filesdata command - response: gen[ - { - b'totalitems': 12, - b'totalpaths': 9 - }, - { - b'path': b'a', - b'totalitems': 2 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 81 - ] - ], - b'node': b'd\x9d\x14\x9d\xf4=\x83\x88%#\xb7\xfb\x1ej:\xf6\xf1\x90{9' - }, - b'a0\n00000000000000000000000000000000000000\n11111111111111111111111111111111111111\n', - { - b'deltabasenode': b'd\x9d\x14\x9d\xf4=\x83\x88%#\xb7\xfb\x1ej:\xf6\xf1\x90{9', - b'fieldsfollowing': [ - [ - b'delta', - 15 - ] - ], - b'node': b'\n\x862\x1f\x13y\xd1\xa9\xec\xd0W\x9a"\x97z\xf7\xa5\xac\xaf\x11' - }, - b'\x00\x00\x00Q\x00\x00\x00Q\x00\x00\x00\x03a1\n', - { - b'path': b'b', - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 81 - ] - ], - b'node': b'\x88\xbac\xb8\xd8\xc6 :\xc6z\xc9\x98\xac\xd9\x17K\xf7\x05!\xb2' - }, - b'b0\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\nbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n', - { - b'path': b'dir0/c', - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 3 - ] - ], - b'node': b'\x91DE4j\x0c\xa0b\x9b\xd4|\xeb]\xfe\x07\xe4\xd4\xcf%\x01' - }, - b'c0\n', - { - b'path': b'dir0/child0/e', - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 3 - ] - ], - b'node': b'\xbb\xbal\x06\xb3\x0fD=4\xff\x84\x1b\xc9\x85\xc4\xd0\x82|k\xe4' - }, - b'e0\n', - { - b'path': b'dir0/child1/f', - b'totalitems': 2 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 3 - ] - ], - b'node': b'\x12\xfc}\xcdw;Z\n\x92\x9c\xe1\x95"\x80\x83\xc6\xdd\xc9\xce\xc4' - }, - b'f0\n', - { - b'deltabasenode': b'\x12\xfc}\xcdw;Z\n\x92\x9c\xe1\x95"\x80\x83\xc6\xdd\xc9\xce\xc4', - b'fieldsfollowing': [ - [ - b'delta', - 15 - ] - ], - b'node': b'(\xc7v\xae\x08\xd0\xd5^\xb4\x06H\xb4\x01\xb9\x0f\xf5DH4\x8e' - }, - b'\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x03f1\n', - { - b'path': b'dir0/d', - b'totalitems': 2 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 3 - ] - ], - b'node': b'S\x82\x06\xdc\x97\x1eR\x15@\xd6\x84:\xbf\xe6\xd1`2\xf6\xd4&' - }, - b'd0\n', - { - b'deltabasenode': b'S\x82\x06\xdc\x97\x1eR\x15@\xd6\x84:\xbf\xe6\xd1`2\xf6\xd4&', - b'fieldsfollowing': [ - [ - b'delta', - 15 - ] - ], - b'node': b'\x93\x88)\xad\x01R}2\xba\x06_\x81#6\xfe\xc7\x9d\xdd9G' - }, - b'\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x03d1\n', - { - b'path': b'dir0/i', - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 3 - ] - ], - b'node': b'\xd7t\xb5\x80Jq\xfd1\xe1\xae\x05\xea\x8e2\xdd\x9b\xa3\xd8S\xd7' - }, - b'i0\n', - { - b'path': b'g', - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 3 - ] - ], - b'node': b'\xde\xca\xba5DFjI\x95r\xe9\x0f\xac\xe6\xfa\x0c!k\xba\x8c' - }, - b'g0\n', - { - b'path': b'h', - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 3 - ] - ], - b'node': b'\x03A\xfc\x84\x1b\xb5\xb4\xba\x93\xb2mM\xdaa\xf7y6]\xb3K' - }, - b'h0\n' - ] - -Requesting linknode field works - - $ sendhttpv2peer << EOF - > command filesdata - > revisions eval:[{ - > b'type': b'changesetexplicit', - > b'nodes': [ - > b'\x6e\x87\x5f\xf1\x8c\x22\x76\x59\xad\x61\x43\xbb\x35\x80\xc6\x57\x00\x73\x48\x84', - > b'\x5b\x0b\x1a\x23\x57\x7e\x20\x5e\xa2\x40\xe3\x9c\x97\x04\xe2\x8d\x76\x97\xcb\xd8', - > b'\xb9\x1c\x03\xcb\xba\x35\x19\xab\x14\x9b\x6c\xd0\xa0\xaf\xbd\xb5\xcf\x1b\x5c\x8a', - > ]}] - > fields eval:[b'linknode'] - > EOF - creating http peer for wire protocol version 2 - sending filesdata command - response: gen[ - { - b'totalitems': 12, - b'totalpaths': 9 - }, - { - b'path': b'a', - b'totalitems': 2 - }, - { - b'linknode': b'n\x87_\xf1\x8c"vY\xadaC\xbb5\x80\xc6W\x00sH\x84', - b'node': b'd\x9d\x14\x9d\xf4=\x83\x88%#\xb7\xfb\x1ej:\xf6\xf1\x90{9' - }, - { - b'linknode': b'[\x0b\x1a#W~ ^\xa2@\xe3\x9c\x97\x04\xe2\x8dv\x97\xcb\xd8', - b'node': b'\n\x862\x1f\x13y\xd1\xa9\xec\xd0W\x9a"\x97z\xf7\xa5\xac\xaf\x11' - }, - { - b'path': b'b', - b'totalitems': 1 - }, - { - b'linknode': b'n\x87_\xf1\x8c"vY\xadaC\xbb5\x80\xc6W\x00sH\x84', - b'node': b'\x88\xbac\xb8\xd8\xc6 :\xc6z\xc9\x98\xac\xd9\x17K\xf7\x05!\xb2' - }, - { - b'path': b'dir0/c', - b'totalitems': 1 - }, - { - b'linknode': b'n\x87_\xf1\x8c"vY\xadaC\xbb5\x80\xc6W\x00sH\x84', - b'node': b'\x91DE4j\x0c\xa0b\x9b\xd4|\xeb]\xfe\x07\xe4\xd4\xcf%\x01' - }, - { - b'path': b'dir0/child0/e', - b'totalitems': 1 - }, - { - b'linknode': b'n\x87_\xf1\x8c"vY\xadaC\xbb5\x80\xc6W\x00sH\x84', - b'node': b'\xbb\xbal\x06\xb3\x0fD=4\xff\x84\x1b\xc9\x85\xc4\xd0\x82|k\xe4' - }, - { - b'path': b'dir0/child1/f', - b'totalitems': 2 - }, - { - b'linknode': b'n\x87_\xf1\x8c"vY\xadaC\xbb5\x80\xc6W\x00sH\x84', - b'node': b'\x12\xfc}\xcdw;Z\n\x92\x9c\xe1\x95"\x80\x83\xc6\xdd\xc9\xce\xc4' - }, - { - b'linknode': b'\xb9\x1c\x03\xcb\xba5\x19\xab\x14\x9bl\xd0\xa0\xaf\xbd\xb5\xcf\x1b\\\x8a', - b'node': b'(\xc7v\xae\x08\xd0\xd5^\xb4\x06H\xb4\x01\xb9\x0f\xf5DH4\x8e' - }, - { - b'path': b'dir0/d', - b'totalitems': 2 - }, - { - b'linknode': b'n\x87_\xf1\x8c"vY\xadaC\xbb5\x80\xc6W\x00sH\x84', - b'node': b'S\x82\x06\xdc\x97\x1eR\x15@\xd6\x84:\xbf\xe6\xd1`2\xf6\xd4&' - }, - { - b'linknode': b'[\x0b\x1a#W~ ^\xa2@\xe3\x9c\x97\x04\xe2\x8dv\x97\xcb\xd8', - b'node': b'\x93\x88)\xad\x01R}2\xba\x06_\x81#6\xfe\xc7\x9d\xdd9G' - }, - { - b'path': b'dir0/i', - b'totalitems': 1 - }, - { - b'linknode': b'\xb9\x1c\x03\xcb\xba5\x19\xab\x14\x9bl\xd0\xa0\xaf\xbd\xb5\xcf\x1b\\\x8a', - b'node': b'\xd7t\xb5\x80Jq\xfd1\xe1\xae\x05\xea\x8e2\xdd\x9b\xa3\xd8S\xd7' - }, - { - b'path': b'g', - b'totalitems': 1 - }, - { - b'linknode': b'[\x0b\x1a#W~ ^\xa2@\xe3\x9c\x97\x04\xe2\x8dv\x97\xcb\xd8', - b'node': b'\xde\xca\xba5DFjI\x95r\xe9\x0f\xac\xe6\xfa\x0c!k\xba\x8c' - }, - { - b'path': b'h', - b'totalitems': 1 - }, - { - b'linknode': b'[\x0b\x1a#W~ ^\xa2@\xe3\x9c\x97\x04\xe2\x8dv\x97\xcb\xd8', - b'node': b'\x03A\xfc\x84\x1b\xb5\xb4\xba\x93\xb2mM\xdaa\xf7y6]\xb3K' - } - ] - -Test behavior where a file node is introduced in 2 DAG heads - -Request for changeset introducing filenode returns linknode as self - - $ sendhttpv2peer << EOF - > command filesdata - > revisions eval:[{ - > b'type': b'changesetexplicit', - > b'nodes': [ - > b'\xb1\x6c\xce\x29\x67\xc1\x74\x9e\xf4\xf4\xe3\x08\x6a\x80\x6c\xfb\xad\x8a\x3a\xf7', - > ]}] - > fields eval:[b'linknode'] - > pathfilter eval:{b'include': [b'path:dupe-file']} - > EOF - creating http peer for wire protocol version 2 - sending filesdata command - response: gen[ - { - b'totalitems': 1, - b'totalpaths': 1 - }, - { - b'path': b'dupe-file', - b'totalitems': 1 - }, - { - b'linknode': b'\xb1l\xce)g\xc1t\x9e\xf4\xf4\xe3\x08j\x80l\xfb\xad\x8a:\xf7', - b'node': b'.\xd2\xa3\x91*\x0b$P C\xea\xe8N\xe4\xb2y\xc1\x8b\x90\xdd' - } - ] - - $ sendhttpv2peer << EOF - > command filesdata - > revisions eval:[{ - > b'type': b'changesetexplicit', - > b'nodes': [ - > b'\xb1\x6c\xce\x29\x67\xc1\x74\x9e\xf4\xf4\xe3\x08\x6a\x80\x6c\xfb\xad\x8a\x3a\xf7', - > ]}] - > fields eval:[b'linknode'] - > haveparents eval:True - > pathfilter eval:{b'include': [b'path:dupe-file']} - > EOF - creating http peer for wire protocol version 2 - sending filesdata command - response: gen[ - { - b'totalitems': 1, - b'totalpaths': 1 - }, - { - b'path': b'dupe-file', - b'totalitems': 1 - }, - { - b'linknode': b'\xb1l\xce)g\xc1t\x9e\xf4\xf4\xe3\x08j\x80l\xfb\xad\x8a:\xf7', - b'node': b'.\xd2\xa3\x91*\x0b$P C\xea\xe8N\xe4\xb2y\xc1\x8b\x90\xdd' - } - ] - -Request for changeset where recorded linknode isn't in DAG ancestry will get -rewritten accordingly - - $ sendhttpv2peer << EOF - > command filesdata - > revisions eval:[{ - > b'type': b'changesetexplicit', - > b'nodes': [ - > b'\x47\xfc\x30\x58\x09\x11\x23\x2c\xb2\x64\x67\x5b\x40\x28\x19\xde\xdd\xf6\xc6\xf0', - > ]}] - > fields eval:[b'linknode'] - > pathfilter eval:{b'include': [b'path:dupe-file']} - > EOF - creating http peer for wire protocol version 2 - sending filesdata command - response: gen[ - { - b'totalitems': 1, - b'totalpaths': 1 - }, - { - b'path': b'dupe-file', - b'totalitems': 1 - }, - { - b'linknode': b'G\xfc0X\t\x11#,\xb2dg[@(\x19\xde\xdd\xf6\xc6\xf0', - b'node': b'.\xd2\xa3\x91*\x0b$P C\xea\xe8N\xe4\xb2y\xc1\x8b\x90\xdd' - } - ] - - $ sendhttpv2peer << EOF - > command filesdata - > revisions eval:[{ - > b'type': b'changesetexplicit', - > b'nodes': [ - > b'\x47\xfc\x30\x58\x09\x11\x23\x2c\xb2\x64\x67\x5b\x40\x28\x19\xde\xdd\xf6\xc6\xf0', - > ]}] - > fields eval:[b'linknode'] - > haveparents eval:True - > pathfilter eval:{b'include': [b'path:dupe-file']} - > EOF - creating http peer for wire protocol version 2 - sending filesdata command - response: gen[ - { - b'totalitems': 1, - b'totalpaths': 1 - }, - { - b'path': b'dupe-file', - b'totalitems': 1 - }, - { - b'linknode': b'G\xfc0X\t\x11#,\xb2dg[@(\x19\xde\xdd\xf6\xc6\xf0', - b'node': b'.\xd2\xa3\x91*\x0b$P C\xea\xe8N\xe4\xb2y\xc1\x8b\x90\xdd' - } - ] - - $ cat error.log diff --git a/tests/test-wireproto-command-heads.t b/tests/test-wireproto-command-heads.t deleted file mode 100644 --- a/tests/test-wireproto-command-heads.t +++ /dev/null @@ -1,56 +0,0 @@ - $ . $TESTDIR/wireprotohelpers.sh - - $ hg init server - $ enablehttpv2 server - $ cd server - $ hg debugdrawdag << EOF - > H I J - > | | | - > E F G - > | |/ - > C D - > |/ - > B - > | - > A - > EOF - - $ hg phase --force --secret J - $ hg phase --public E - - $ hg log -r 'E + H + I + G + J' -T '{rev}:{node} {desc} {phase}\n' - 4:78d2dca436b2f5b188ac267e29b81e07266d38fc E public - 7:ae492e36b0c8339ffaf328d00b85b4525de1165e H draft - 8:1d6f6b91d44aaba6d5e580bc30a9948530dbe00b I draft - 6:29446d2dc5419c5f97447a8bc062e4cc328bf241 G draft - 9:dec04b246d7cbb670c6689806c05ad17c835284e J secret - - $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log - $ cat hg.pid > $DAEMON_PIDS - -All non-secret heads returned by default - - $ sendhttpv2peer << EOF - > command heads - > EOF - creating http peer for wire protocol version 2 - sending heads command - response: [ - b'\x1dok\x91\xd4J\xab\xa6\xd5\xe5\x80\xbc0\xa9\x94\x850\xdb\xe0\x0b', - b'\xaeI.6\xb0\xc83\x9f\xfa\xf3(\xd0\x0b\x85\xb4R]\xe1\x16^', - b')Dm-\xc5A\x9c_\x97Dz\x8b\xc0b\xe4\xcc2\x8b\xf2A' - ] - -Requesting just the public heads works - - $ sendhttpv2peer << EOF - > command heads - > publiconly 1 - > EOF - creating http peer for wire protocol version 2 - sending heads command - response: [ - b'x\xd2\xdc\xa46\xb2\xf5\xb1\x88\xac&~)\xb8\x1e\x07&m8\xfc' - ] - - $ cat error.log diff --git a/tests/test-wireproto-command-known.t b/tests/test-wireproto-command-known.t deleted file mode 100644 --- a/tests/test-wireproto-command-known.t +++ /dev/null @@ -1,58 +0,0 @@ - $ . $TESTDIR/wireprotohelpers.sh - - $ hg init server - $ enablehttpv2 server - $ cd server - $ hg debugdrawdag << EOF - > C D - > |/ - > B - > | - > A - > EOF - - $ hg log -T '{rev}:{node} {desc}\n' - 3:be0ef73c17ade3fc89dc41701eb9fc3a91b58282 D - 2:26805aba1e600a82e93661149f2313866a221a7b C - 1:112478962961147124edd43549aedd1a335e44bf B - 0:426bada5c67598ca65036d57d9e4b64b0c1ce7a0 A - - $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log - $ cat hg.pid > $DAEMON_PIDS - -No arguments returns something reasonable - - $ sendhttpv2peer << EOF - > command known - > EOF - creating http peer for wire protocol version 2 - sending known command - response: [] - -Single known node works - - $ sendhttpv2peer << EOF - > command known - > nodes eval:[b'\x42\x6b\xad\xa5\xc6\x75\x98\xca\x65\x03\x6d\x57\xd9\xe4\xb6\x4b\x0c\x1c\xe7\xa0'] - > EOF - creating http peer for wire protocol version 2 - sending known command - response: [ - True - ] - -Multiple nodes works - - $ sendhttpv2peer << EOF - > command known - > nodes eval:[b'\x42\x6b\xad\xa5\xc6\x75\x98\xca\x65\x03\x6d\x57\xd9\xe4\xb6\x4b\x0c\x1c\xe7\xa0', b'00000000000000000000', b'\x11\x24\x78\x96\x29\x61\x14\x71\x24\xed\xd4\x35\x49\xae\xdd\x1a\x33\x5e\x44\xbf'] - > EOF - creating http peer for wire protocol version 2 - sending known command - response: [ - True, - False, - True - ] - - $ cat error.log diff --git a/tests/test-wireproto-command-listkeys.t b/tests/test-wireproto-command-listkeys.t deleted file mode 100644 --- a/tests/test-wireproto-command-listkeys.t +++ /dev/null @@ -1,65 +0,0 @@ - $ . $TESTDIR/wireprotohelpers.sh - - $ hg init server - $ enablehttpv2 server - $ cd server - $ hg debugdrawdag << EOF - > C D - > |/ - > B - > | - > A - > EOF - - $ hg phase --public -r C - $ hg book -r C @ - - $ hg log -T '{rev}:{node} {desc}\n' - 3:be0ef73c17ade3fc89dc41701eb9fc3a91b58282 D - 2:26805aba1e600a82e93661149f2313866a221a7b C - 1:112478962961147124edd43549aedd1a335e44bf B - 0:426bada5c67598ca65036d57d9e4b64b0c1ce7a0 A - - $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log - $ cat hg.pid > $DAEMON_PIDS - -Request for namespaces works - - $ sendhttpv2peer << EOF - > command listkeys - > namespace namespaces - > EOF - creating http peer for wire protocol version 2 - sending listkeys command - response: { - b'bookmarks': b'', - b'namespaces': b'', - b'phases': b'' - } - -Request for phases works - - $ sendhttpv2peer << EOF - > command listkeys - > namespace phases - > EOF - creating http peer for wire protocol version 2 - sending listkeys command - response: { - b'be0ef73c17ade3fc89dc41701eb9fc3a91b58282': b'1', - b'publishing': b'True' - } - -Request for bookmarks works - - $ sendhttpv2peer << EOF - > command listkeys - > namespace bookmarks - > EOF - creating http peer for wire protocol version 2 - sending listkeys command - response: { - b'@': b'26805aba1e600a82e93661149f2313866a221a7b' - } - - $ cat error.log diff --git a/tests/test-wireproto-command-lookup.t b/tests/test-wireproto-command-lookup.t deleted file mode 100644 --- a/tests/test-wireproto-command-lookup.t +++ /dev/null @@ -1,33 +0,0 @@ - $ . $TESTDIR/wireprotohelpers.sh - - $ hg init server - $ enablehttpv2 server - $ cd server - $ cat >> .hg/hgrc << EOF - > [web] - > push_ssl = false - > allow-push = * - > EOF - $ hg debugdrawdag << EOF - > C D - > |/ - > B - > | - > A - > EOF - $ root_node=$(hg log -r A -T '{node}') - - $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log - $ cat hg.pid > $DAEMON_PIDS - -lookup for known node works - - $ sendhttpv2peer << EOF - > command lookup - > key $root_node - > EOF - creating http peer for wire protocol version 2 - sending lookup command - response: b'Bk\xad\xa5\xc6u\x98\xcae\x03mW\xd9\xe4\xb6K\x0c\x1c\xe7\xa0' - - $ cat error.log diff --git a/tests/test-wireproto-command-manifestdata.t b/tests/test-wireproto-command-manifestdata.t deleted file mode 100644 --- a/tests/test-wireproto-command-manifestdata.t +++ /dev/null @@ -1,358 +0,0 @@ - $ . $TESTDIR/wireprotohelpers.sh - - $ hg init server - $ enablehttpv2 server - $ cd server - $ echo a0 > a - $ echo b0 > b - $ mkdir -p dir0/child0 dir0/child1 dir1 - $ echo c0 > dir0/c - $ echo d0 > dir0/d - $ echo e0 > dir0/child0/e - $ echo f0 > dir0/child1/f - $ hg -q commit -A -m 'commit 0' - - $ echo a1 > a - $ echo d1 > dir0/d - $ hg commit -m 'commit 1' - $ echo f0 > dir0/child1/f - $ hg commit -m 'commit 2' - nothing changed - [1] - - $ hg -q up -r 0 - $ echo a2 > a - $ hg commit -m 'commit 3' - created new head - - $ hg log -G -T '{rev}:{node} {desc}\n' - @ 2:c8757a2ffe552850d1e0dfe60d295ebf64c196d9 commit 3 - | - | o 1:650165e803375748a94df471e5b58d85763e0b29 commit 1 - |/ - o 0:6d85ca1270b377d320098556ba5bfad34a9ee12d commit 0 - - - $ hg --debug debugindex -m - rev linkrev nodeid p1 p2 - 0 0 1b175b595f022cfab5b809cc0ed551bd0b3ff5e4 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 - 1 1 91e0bdbfb0dde0023fa063edc1445f207a22eac7 1b175b595f022cfab5b809cc0ed551bd0b3ff5e4 0000000000000000000000000000000000000000 - 2 2 46a6721b5edaf0ea04b79a5cb3218854a4d2aba0 1b175b595f022cfab5b809cc0ed551bd0b3ff5e4 0000000000000000000000000000000000000000 - - $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log - $ cat hg.pid > $DAEMON_PIDS - -Missing arguments is an error - - $ sendhttpv2peer << EOF - > command manifestdata - > EOF - creating http peer for wire protocol version 2 - sending manifestdata command - abort: missing required arguments: nodes, tree - [255] - - $ sendhttpv2peer << EOF - > command manifestdata - > nodes eval:[] - > EOF - creating http peer for wire protocol version 2 - sending manifestdata command - abort: missing required arguments: tree - [255] - -Unknown node is an error - - $ sendhttpv2peer << EOF - > command manifestdata - > nodes eval:[b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'] - > tree eval:b'' - > EOF - creating http peer for wire protocol version 2 - sending manifestdata command - abort: unknown node: \xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa (esc) - [255] - -Fetching a single revision returns just metadata by default - - $ sendhttpv2peer << EOF - > command manifestdata - > nodes eval:[b'\x46\xa6\x72\x1b\x5e\xda\xf0\xea\x04\xb7\x9a\x5c\xb3\x21\x88\x54\xa4\xd2\xab\xa0'] - > tree eval:b'' - > EOF - creating http peer for wire protocol version 2 - sending manifestdata command - response: gen[ - { - b'totalitems': 1 - }, - { - b'node': b'F\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0' - } - ] - -Requesting parents works - - $ sendhttpv2peer << EOF - > command manifestdata - > nodes eval:[b'\x46\xa6\x72\x1b\x5e\xda\xf0\xea\x04\xb7\x9a\x5c\xb3\x21\x88\x54\xa4\xd2\xab\xa0'] - > tree eval:b'' - > fields eval:[b'parents'] - > EOF - creating http peer for wire protocol version 2 - sending manifestdata command - response: gen[ - { - b'totalitems': 1 - }, - { - b'node': b'F\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0', - b'parents': [ - b'\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ] - } - ] - -Requesting revision data works -(haveparents defaults to false, so fulltext is emitted) - - $ sendhttpv2peer << EOF - > command manifestdata - > nodes eval:[b'\x46\xa6\x72\x1b\x5e\xda\xf0\xea\x04\xb7\x9a\x5c\xb3\x21\x88\x54\xa4\xd2\xab\xa0'] - > tree eval:b'' - > fields eval:[b'revision'] - > EOF - creating http peer for wire protocol version 2 - sending manifestdata command - response: gen[ - { - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 292 - ] - ], - b'node': b'F\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0' - }, - b'a\x000879345e39377229634b420c639454156726c6b6\nb\x00819e258d31a5e1606629f365bb902a1b21ee4216\ndir0/c\x00914445346a0ca0629bd47ceb5dfe07e4d4cf2501\ndir0/child0/e\x00bbba6c06b30f443d34ff841bc985c4d0827c6be4\ndir0/child1/f\x0012fc7dcd773b5a0a929ce195228083c6ddc9cec4\ndir0/d\x00538206dc971e521540d6843abfe6d16032f6d426\n' - ] - -haveparents=False yields same output - - $ sendhttpv2peer << EOF - > command manifestdata - > nodes eval:[b'\x46\xa6\x72\x1b\x5e\xda\xf0\xea\x04\xb7\x9a\x5c\xb3\x21\x88\x54\xa4\xd2\xab\xa0'] - > tree eval:b'' - > fields eval:[b'revision'] - > haveparents eval:False - > EOF - creating http peer for wire protocol version 2 - sending manifestdata command - response: gen[ - { - b'totalitems': 1 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 292 - ] - ], - b'node': b'F\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0' - }, - b'a\x000879345e39377229634b420c639454156726c6b6\nb\x00819e258d31a5e1606629f365bb902a1b21ee4216\ndir0/c\x00914445346a0ca0629bd47ceb5dfe07e4d4cf2501\ndir0/child0/e\x00bbba6c06b30f443d34ff841bc985c4d0827c6be4\ndir0/child1/f\x0012fc7dcd773b5a0a929ce195228083c6ddc9cec4\ndir0/d\x00538206dc971e521540d6843abfe6d16032f6d426\n' - ] - -haveparents=True will emit delta - - $ sendhttpv2peer << EOF - > command manifestdata - > nodes eval:[b'\x46\xa6\x72\x1b\x5e\xda\xf0\xea\x04\xb7\x9a\x5c\xb3\x21\x88\x54\xa4\xd2\xab\xa0'] - > tree eval:b'' - > fields eval:[b'revision'] - > haveparents eval:True - > EOF - creating http peer for wire protocol version 2 - sending manifestdata command - response: gen[ - { - b'totalitems': 1 - }, - { - b'deltabasenode': b'\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4', - b'fieldsfollowing': [ - [ - b'delta', - 55 - ] - ], - b'node': b'F\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0' - }, - b'\x00\x00\x00\x00\x00\x00\x00+\x00\x00\x00+a\x000879345e39377229634b420c639454156726c6b6\n' - ] - -Requesting multiple revisions works -(haveparents defaults to false, so fulltext is emitted unless a parent -has been emitted) - - $ sendhttpv2peer << EOF - > command manifestdata - > nodes eval:[b'\x1b\x17\x5b\x59\x5f\x02\x2c\xfa\xb5\xb8\x09\xcc\x0e\xd5\x51\xbd\x0b\x3f\xf5\xe4', b'\x46\xa6\x72\x1b\x5e\xda\xf0\xea\x04\xb7\x9a\x5c\xb3\x21\x88\x54\xa4\xd2\xab\xa0'] - > tree eval:b'' - > fields eval:[b'revision'] - > EOF - creating http peer for wire protocol version 2 - sending manifestdata command - response: gen[ - { - b'totalitems': 2 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 292 - ] - ], - b'node': b'\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4' - }, - b'a\x002b4eb07319bfa077a40a2f04913659aef0da42da\nb\x00819e258d31a5e1606629f365bb902a1b21ee4216\ndir0/c\x00914445346a0ca0629bd47ceb5dfe07e4d4cf2501\ndir0/child0/e\x00bbba6c06b30f443d34ff841bc985c4d0827c6be4\ndir0/child1/f\x0012fc7dcd773b5a0a929ce195228083c6ddc9cec4\ndir0/d\x00538206dc971e521540d6843abfe6d16032f6d426\n', - { - b'deltabasenode': b'\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4', - b'fieldsfollowing': [ - [ - b'delta', - 55 - ] - ], - b'node': b'F\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0' - }, - b'\x00\x00\x00\x00\x00\x00\x00+\x00\x00\x00+a\x000879345e39377229634b420c639454156726c6b6\n' - ] - -With haveparents=True, first revision is a delta instead of fulltext - - $ sendhttpv2peer << EOF - > command manifestdata - > nodes eval:[b'\x1b\x17\x5b\x59\x5f\x02\x2c\xfa\xb5\xb8\x09\xcc\x0e\xd5\x51\xbd\x0b\x3f\xf5\xe4', b'\x46\xa6\x72\x1b\x5e\xda\xf0\xea\x04\xb7\x9a\x5c\xb3\x21\x88\x54\xa4\xd2\xab\xa0'] - > tree eval:b'' - > fields eval:[b'revision'] - > haveparents eval:True - > EOF - creating http peer for wire protocol version 2 - sending manifestdata command - response: gen[ - { - b'totalitems': 2 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 292 - ] - ], - b'node': b'\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4' - }, - b'a\x002b4eb07319bfa077a40a2f04913659aef0da42da\nb\x00819e258d31a5e1606629f365bb902a1b21ee4216\ndir0/c\x00914445346a0ca0629bd47ceb5dfe07e4d4cf2501\ndir0/child0/e\x00bbba6c06b30f443d34ff841bc985c4d0827c6be4\ndir0/child1/f\x0012fc7dcd773b5a0a929ce195228083c6ddc9cec4\ndir0/d\x00538206dc971e521540d6843abfe6d16032f6d426\n', - { - b'deltabasenode': b'\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4', - b'fieldsfollowing': [ - [ - b'delta', - 55 - ] - ], - b'node': b'F\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0' - }, - b'\x00\x00\x00\x00\x00\x00\x00+\x00\x00\x00+a\x000879345e39377229634b420c639454156726c6b6\n' - ] - -Revisions are sorted by DAG order, parents first - - $ sendhttpv2peer << EOF - > command manifestdata - > nodes eval:[b'\x46\xa6\x72\x1b\x5e\xda\xf0\xea\x04\xb7\x9a\x5c\xb3\x21\x88\x54\xa4\xd2\xab\xa0', b'\x1b\x17\x5b\x59\x5f\x02\x2c\xfa\xb5\xb8\x09\xcc\x0e\xd5\x51\xbd\x0b\x3f\xf5\xe4'] - > tree eval:b'' - > fields eval:[b'revision'] - > EOF - creating http peer for wire protocol version 2 - sending manifestdata command - response: gen[ - { - b'totalitems': 2 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 292 - ] - ], - b'node': b'\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4' - }, - b'a\x002b4eb07319bfa077a40a2f04913659aef0da42da\nb\x00819e258d31a5e1606629f365bb902a1b21ee4216\ndir0/c\x00914445346a0ca0629bd47ceb5dfe07e4d4cf2501\ndir0/child0/e\x00bbba6c06b30f443d34ff841bc985c4d0827c6be4\ndir0/child1/f\x0012fc7dcd773b5a0a929ce195228083c6ddc9cec4\ndir0/d\x00538206dc971e521540d6843abfe6d16032f6d426\n', - { - b'deltabasenode': b'\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4', - b'fieldsfollowing': [ - [ - b'delta', - 55 - ] - ], - b'node': b'F\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0' - }, - b'\x00\x00\x00\x00\x00\x00\x00+\x00\x00\x00+a\x000879345e39377229634b420c639454156726c6b6\n' - ] - -Requesting parents and revision data works - - $ sendhttpv2peer << EOF - > command manifestdata - > nodes eval:[b'\x1b\x17\x5b\x59\x5f\x02\x2c\xfa\xb5\xb8\x09\xcc\x0e\xd5\x51\xbd\x0b\x3f\xf5\xe4', b'\x46\xa6\x72\x1b\x5e\xda\xf0\xea\x04\xb7\x9a\x5c\xb3\x21\x88\x54\xa4\xd2\xab\xa0'] - > tree eval:b'' - > fields eval:[b'parents', b'revision'] - > EOF - creating http peer for wire protocol version 2 - sending manifestdata command - response: gen[ - { - b'totalitems': 2 - }, - { - b'fieldsfollowing': [ - [ - b'revision', - 292 - ] - ], - b'node': b'\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4', - b'parents': [ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ] - }, - b'a\x002b4eb07319bfa077a40a2f04913659aef0da42da\nb\x00819e258d31a5e1606629f365bb902a1b21ee4216\ndir0/c\x00914445346a0ca0629bd47ceb5dfe07e4d4cf2501\ndir0/child0/e\x00bbba6c06b30f443d34ff841bc985c4d0827c6be4\ndir0/child1/f\x0012fc7dcd773b5a0a929ce195228083c6ddc9cec4\ndir0/d\x00538206dc971e521540d6843abfe6d16032f6d426\n', - { - b'deltabasenode': b'\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4', - b'fieldsfollowing': [ - [ - b'delta', - 55 - ] - ], - b'node': b'F\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0', - b'parents': [ - b'\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ] - }, - b'\x00\x00\x00\x00\x00\x00\x00+\x00\x00\x00+a\x000879345e39377229634b420c639454156726c6b6\n' - ] - - $ cat error.log diff --git a/tests/test-wireproto-command-pushkey.t b/tests/test-wireproto-command-pushkey.t deleted file mode 100644 --- a/tests/test-wireproto-command-pushkey.t +++ /dev/null @@ -1,45 +0,0 @@ - $ . $TESTDIR/wireprotohelpers.sh - - $ hg init server - $ enablehttpv2 server - $ cd server - $ cat >> .hg/hgrc << EOF - > [web] - > push_ssl = false - > allow-push = * - > EOF - $ hg debugdrawdag << EOF - > C D - > |/ - > B - > | - > A - > EOF - - $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log - $ cat hg.pid > $DAEMON_PIDS - -pushkey for a bookmark works - - $ sendhttpv2peer << EOF - > command pushkey - > namespace bookmarks - > key @ - > old - > new 426bada5c67598ca65036d57d9e4b64b0c1ce7a0 - > EOF - creating http peer for wire protocol version 2 - sending pushkey command - response: True - - $ sendhttpv2peer << EOF - > command listkeys - > namespace bookmarks - > EOF - creating http peer for wire protocol version 2 - sending listkeys command - response: { - b'@': b'426bada5c67598ca65036d57d9e4b64b0c1ce7a0' - } - - $ cat error.log diff --git a/tests/test-wireproto-command-rawstorefiledata.t b/tests/test-wireproto-command-rawstorefiledata.t deleted file mode 100644 --- a/tests/test-wireproto-command-rawstorefiledata.t +++ /dev/null @@ -1,132 +0,0 @@ - $ . $TESTDIR/wireprotohelpers.sh - $ hg init server - $ enablehttpv2 server - $ cd server - $ echo a0 > a - $ echo b0 > b - $ hg -q commit -A -m 'commit 0' - $ echo a1 > a - $ hg commit -m 'commit 1' - $ mkdir dir0 - $ mkdir dir1 - $ echo c0 > dir0/c - $ echo d0 > dir0/d - $ echo e0 > dir1/e - $ echo f0 > dir1/f - $ hg commit -A -m 'commit 2' - adding dir0/c - adding dir0/d - adding dir1/e - adding dir1/f - $ echo f1 > dir1/f - $ hg commit -m 'commit 3' - - $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log - $ cat hg.pid > $DAEMON_PIDS - -Missing requirement argument results in error - - $ sendhttpv2peer << EOF - > command rawstorefiledata - > EOF - creating http peer for wire protocol version 2 - sending rawstorefiledata command - abort: missing required arguments: files - [255] - -Unknown files value results in error - - $ sendhttpv2peer << EOF - > command rawstorefiledata - > files eval:[b'unknown'] - > EOF - creating http peer for wire protocol version 2 - sending rawstorefiledata command - abort: unknown file type: unknown - [255] - -Requesting just changelog works - - $ sendhttpv2peer << EOF - > command rawstorefiledata - > files eval:[b'changelog'] - > EOF - creating http peer for wire protocol version 2 - sending rawstorefiledata command - response: gen[ - { - b'filecount': 1, - b'totalsize': 527 (no-zstd !) - b'totalsize': 530 (zstd !) - }, - { - b'location': b'store', - b'path': b'00changelog.i', - 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\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'' - ] - -Requesting just manifestlog works (as impractical as that operation may be). - - $ sendhttpv2peer << EOF - > command rawstorefiledata - > files eval:[b'manifestlog'] - > EOF - creating http peer for wire protocol version 2 - sending rawstorefiledata command - response: gen[ - { - b'filecount': 1, - b'totalsize': 584 (no-zstd !) - b'totalsize': 588 (zstd !) - }, - { - b'location': b'store', - b'path': b'00manifest.i', - 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', (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'' - ] - -Requesting both changelog and manifestlog works. - - $ sendhttpv2peer << EOF - > command rawstorefiledata - > files eval:[b'changelog', b'manifestlog'] - > EOF - creating http peer for wire protocol version 2 - sending rawstorefiledata command - response: gen[ - { - b'filecount': 2, - b'totalsize': 1111 (no-zstd !) - b'totalsize': 1118 (zstd !) - }, - { - b'location': b'store', - b'path': b'00manifest.i', - 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', (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 (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\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'' - ] - - $ cat error.log diff --git a/tests/test-wireproto-content-redirects.t b/tests/test-wireproto-content-redirects.t deleted file mode 100644 --- a/tests/test-wireproto-content-redirects.t +++ /dev/null @@ -1,1475 +0,0 @@ - $ . $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] - > track = simplecache - > EOF - - $ hg init server - $ enablehttpv2 server - $ cd server - $ cat >> .hg/hgrc << EOF - > [server] - > compressionengines = zlib - > [extensions] - > simplecache = $TESTDIR/wireprotosimplecache.py - > [simplecache] - > cacheapi = true - > EOF - - $ echo a0 > a - $ echo b0 > b - $ hg -q commit -A -m 'commit 0' - $ echo a1 > a - $ hg commit -m 'commit 1' - - $ hg --debug debugindex -m - rev linkrev nodeid p1 p2 - 0 0 992f4779029a3df8d0666d00bb924f69634e2641 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 - 1 1 a988fb43583e871d1ed5750ee074c6d840bbbfc8 992f4779029a3df8d0666d00bb924f69634e2641 0000000000000000000000000000000000000000 - - $ hg --config simplecache.redirectsfile=redirects.py serve -p $HGPORT -d --pid-file hg.pid -E error.log - $ cat hg.pid > $DAEMON_PIDS - - $ cat > redirects.py << EOF - > [ - > { - > b'name': b'target-a', - > b'protocol': b'http', - > b'snirequired': False, - > b'tlsversions': [b'1.2', b'1.3'], - > b'uris': [b'http://example.com/'], - > }, - > ] - > EOF - -Redirect targets advertised when configured - - $ sendhttpv2peerhandshake << EOF - > command capabilities - > EOF - creating http peer for wire protocol version 2 - s> setsockopt(6, 1, 1) -> None (?) - s> GET /?cmd=capabilities HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> vary: X-HgProto-1,X-HgUpgrade-1\r\n - s> x-hgproto-1: cbor\r\n - s> x-hgupgrade-1: exp-http-v2-0003\r\n - s> accept: application/mercurial-0.1\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> user-agent: Mercurial debugwireproto\r\n - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 200 OK\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: application/mercurial-cbor\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\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 - s> setsockopt(6, 1, 1) -> None (?) - s> POST /api/exp-http-v2-0003/ro/capabilities HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> accept: application/mercurial-exp-framing-0006\r\n - s> content-type: application/mercurial-exp-framing-0006\r\n - s> content-length: 111\r\n (tls1.2 !) - s> content-length: 102\r\n (no-tls1.2 !) - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> user-agent: Mercurial debugwireproto\r\n - s> \r\n - s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81HidentityC\x00\x00\x01\x00\x01\x00\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81Htarget-a (tls1.2 !) - s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity:\x00\x00\x01\x00\x01\x00\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x80 (no-tls1.2 !) - s> makefile('rb', None) - s> HTTP/1.1 200 OK\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: application/mercurial-exp-framing-0006\r\n - s> Transfer-Encoding: chunked\r\n - s> \r\n - s> 11\r\n - s> \t\x00\x00\x01\x00\x02\x01\x92 - s> Hidentity - s> \r\n - s> 13\r\n - s> \x0b\x00\x00\x01\x00\x02\x041 - s> \xa1FstatusBok - s> \r\n - s> 6de\r\n - s> \xd6\x06\x00\x01\x00\x02\x041 - s> \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/ - s> \r\n - s> 8\r\n - s> \x00\x00\x00\x01\x00\x02\x002 - s> \r\n - s> 0\r\n - s> \r\n - response: gen[ - { - b'commands': { - b'branchmap': { - b'args': {}, - b'permissions': [ - b'pull' - ] - }, - b'capabilities': { - b'args': {}, - b'permissions': [ - b'pull' - ] - }, - b'changesetdata': { - b'args': { - b'fields': { - b'default': set([]), - b'required': False, - b'type': b'set', - b'validvalues': set([ - b'bookmarks', - b'parents', - b'phase', - b'revision' - ]) - }, - b'revisions': { - b'required': True, - b'type': b'list' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'filedata': { - b'args': { - b'fields': { - b'default': set([]), - b'required': False, - b'type': b'set', - b'validvalues': set([ - b'linknode', - b'parents', - b'revision' - ]) - }, - b'haveparents': { - b'default': False, - b'required': False, - b'type': b'bool' - }, - b'nodes': { - b'required': True, - b'type': b'list' - }, - b'path': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'filesdata': { - b'args': { - b'fields': { - b'default': set([]), - b'required': False, - b'type': b'set', - b'validvalues': set([ - b'firstchangeset', - b'linknode', - b'parents', - b'revision' - ]) - }, - b'haveparents': { - b'default': False, - b'required': False, - b'type': b'bool' - }, - b'pathfilter': { - b'default': None, - b'required': False, - b'type': b'dict' - }, - b'revisions': { - b'required': True, - b'type': b'list' - } - }, - b'permissions': [ - b'pull' - ], - b'recommendedbatchsize': 50000 - }, - b'heads': { - b'args': { - b'publiconly': { - b'default': False, - b'required': False, - b'type': b'bool' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'known': { - b'args': { - b'nodes': { - b'default': [], - b'required': False, - b'type': b'list' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'listkeys': { - b'args': { - b'namespace': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'lookup': { - b'args': { - b'key': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'manifestdata': { - b'args': { - b'fields': { - b'default': set([]), - b'required': False, - b'type': b'set', - b'validvalues': set([ - b'parents', - b'revision' - ]) - }, - b'haveparents': { - b'default': False, - b'required': False, - b'type': b'bool' - }, - b'nodes': { - b'required': True, - b'type': b'list' - }, - b'tree': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'pull' - ], - b'recommendedbatchsize': 100000 - }, - b'pushkey': { - b'args': { - b'key': { - b'required': True, - b'type': b'bytes' - }, - b'namespace': { - b'required': True, - b'type': b'bytes' - }, - b'new': { - b'required': True, - b'type': b'bytes' - }, - b'old': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'push' - ] - }, - b'rawstorefiledata': { - b'args': { - b'files': { - b'required': True, - b'type': b'list' - }, - b'pathfilter': { - b'default': None, - b'required': False, - b'type': b'list' - } - }, - b'permissions': [ - b'pull' - ] - } - }, - b'framingmediatypes': [ - b'application/mercurial-exp-framing-0006' - ], - b'pathfilterprefixes': set([ - b'path:', - b'rootfilesin:' - ]), - b'rawrepoformats': [ - b'generaldelta', - b'revlogv1', - b'sparserevlog' - ], - b'redirect': { - b'hashes': [ - b'sha256', - b'sha1' - ], - b'targets': [ - { - b'name': b'target-a', - b'protocol': b'http', - b'snirequired': False, - b'tlsversions': [ - b'1.2', - b'1.3' - ], - b'uris': [ - b'http://example.com/' - ] - } - ] - } - } - ] - (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob) - -Unknown protocol is filtered from compatible targets - - $ cat > redirects.py << EOF - > [ - > { - > b'name': b'target-a', - > b'protocol': b'http', - > b'uris': [b'http://example.com/'], - > }, - > { - > b'name': b'target-b', - > b'protocol': b'unknown', - > b'uris': [b'unknown://example.com/'], - > }, - > ] - > EOF - - $ sendhttpv2peerhandshake << EOF - > command capabilities - > EOF - creating http peer for wire protocol version 2 - s> setsockopt(6, 1, 1) -> None (?) - s> GET /?cmd=capabilities HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> vary: X-HgProto-1,X-HgUpgrade-1\r\n - s> x-hgproto-1: cbor\r\n - s> x-hgupgrade-1: exp-http-v2-0003\r\n - s> accept: application/mercurial-0.1\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> user-agent: Mercurial debugwireproto\r\n - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 200 OK\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: application/mercurial-cbor\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\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 - s> setsockopt(6, 1, 1) -> None (?) - s> POST /api/exp-http-v2-0003/ro/capabilities HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> accept: application/mercurial-exp-framing-0006\r\n - s> content-type: application/mercurial-exp-framing-0006\r\n - s> content-length: 111\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> user-agent: Mercurial debugwireproto\r\n - s> \r\n - s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81HidentityC\x00\x00\x01\x00\x01\x00\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81Htarget-a - s> makefile('rb', None) - s> HTTP/1.1 200 OK\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: application/mercurial-exp-framing-0006\r\n - s> Transfer-Encoding: chunked\r\n - s> \r\n - s> 11\r\n - s> \t\x00\x00\x01\x00\x02\x01\x92 - s> Hidentity - s> \r\n - s> 13\r\n - s> \x0b\x00\x00\x01\x00\x02\x041 - s> \xa1FstatusBok - s> \r\n - s> 6f9\r\n - s> \xf1\x06\x00\x01\x00\x02\x041 - s> \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/ - s> \r\n - s> 8\r\n - s> \x00\x00\x00\x01\x00\x02\x002 - s> \r\n - s> 0\r\n - s> \r\n - response: gen[ - { - b'commands': { - b'branchmap': { - b'args': {}, - b'permissions': [ - b'pull' - ] - }, - b'capabilities': { - b'args': {}, - b'permissions': [ - b'pull' - ] - }, - b'changesetdata': { - b'args': { - b'fields': { - b'default': set([]), - b'required': False, - b'type': b'set', - b'validvalues': set([ - b'bookmarks', - b'parents', - b'phase', - b'revision' - ]) - }, - b'revisions': { - b'required': True, - b'type': b'list' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'filedata': { - b'args': { - b'fields': { - b'default': set([]), - b'required': False, - b'type': b'set', - b'validvalues': set([ - b'linknode', - b'parents', - b'revision' - ]) - }, - b'haveparents': { - b'default': False, - b'required': False, - b'type': b'bool' - }, - b'nodes': { - b'required': True, - b'type': b'list' - }, - b'path': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'filesdata': { - b'args': { - b'fields': { - b'default': set([]), - b'required': False, - b'type': b'set', - b'validvalues': set([ - b'firstchangeset', - b'linknode', - b'parents', - b'revision' - ]) - }, - b'haveparents': { - b'default': False, - b'required': False, - b'type': b'bool' - }, - b'pathfilter': { - b'default': None, - b'required': False, - b'type': b'dict' - }, - b'revisions': { - b'required': True, - b'type': b'list' - } - }, - b'permissions': [ - b'pull' - ], - b'recommendedbatchsize': 50000 - }, - b'heads': { - b'args': { - b'publiconly': { - b'default': False, - b'required': False, - b'type': b'bool' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'known': { - b'args': { - b'nodes': { - b'default': [], - b'required': False, - b'type': b'list' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'listkeys': { - b'args': { - b'namespace': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'lookup': { - b'args': { - b'key': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'manifestdata': { - b'args': { - b'fields': { - b'default': set([]), - b'required': False, - b'type': b'set', - b'validvalues': set([ - b'parents', - b'revision' - ]) - }, - b'haveparents': { - b'default': False, - b'required': False, - b'type': b'bool' - }, - b'nodes': { - b'required': True, - b'type': b'list' - }, - b'tree': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'pull' - ], - b'recommendedbatchsize': 100000 - }, - b'pushkey': { - b'args': { - b'key': { - b'required': True, - b'type': b'bytes' - }, - b'namespace': { - b'required': True, - b'type': b'bytes' - }, - b'new': { - b'required': True, - b'type': b'bytes' - }, - b'old': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'push' - ] - }, - b'rawstorefiledata': { - b'args': { - b'files': { - b'required': True, - b'type': b'list' - }, - b'pathfilter': { - b'default': None, - b'required': False, - b'type': b'list' - } - }, - b'permissions': [ - b'pull' - ] - } - }, - b'framingmediatypes': [ - b'application/mercurial-exp-framing-0006' - ], - b'pathfilterprefixes': set([ - b'path:', - b'rootfilesin:' - ]), - b'rawrepoformats': [ - b'generaldelta', - b'revlogv1', - b'sparserevlog' - ], - b'redirect': { - b'hashes': [ - b'sha256', - b'sha1' - ], - b'targets': [ - { - b'name': b'target-a', - b'protocol': b'http', - b'uris': [ - b'http://example.com/' - ] - }, - { - b'name': b'target-b', - b'protocol': b'unknown', - b'uris': [ - b'unknown://example.com/' - ] - } - ] - } - } - ] - (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob) - -Missing SNI support filters targets that require SNI - - $ cat > nosni.py << EOF - > from mercurial import sslutil - > sslutil.hassni = False - > EOF - $ cat >> $HGRCPATH << EOF - > [extensions] - > nosni=`pwd`/nosni.py - > EOF - - $ cat > redirects.py << EOF - > [ - > { - > b'name': b'target-bad-tls', - > b'protocol': b'https', - > b'uris': [b'https://example.com/'], - > b'snirequired': True, - > }, - > ] - > EOF - - $ sendhttpv2peerhandshake << EOF - > command capabilities - > EOF - creating http peer for wire protocol version 2 - s> setsockopt(6, 1, 1) -> None (?) - s> GET /?cmd=capabilities HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> vary: X-HgProto-1,X-HgUpgrade-1\r\n - s> x-hgproto-1: cbor\r\n - s> x-hgupgrade-1: exp-http-v2-0003\r\n - s> accept: application/mercurial-0.1\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> user-agent: Mercurial debugwireproto\r\n - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 200 OK\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: application/mercurial-cbor\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\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 (?) - s> POST /api/exp-http-v2-0003/ro/capabilities HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> accept: application/mercurial-exp-framing-0006\r\n - s> content-type: application/mercurial-exp-framing-0006\r\n - s> content-length: 102\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> user-agent: Mercurial debugwireproto\r\n - s> \r\n - s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity:\x00\x00\x01\x00\x01\x00\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x80 - s> makefile('rb', None) - s> HTTP/1.1 200 OK\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: application/mercurial-exp-framing-0006\r\n - s> Transfer-Encoding: chunked\r\n - s> \r\n - s> 11\r\n - s> \t\x00\x00\x01\x00\x02\x01\x92 - s> Hidentity - s> \r\n - s> 13\r\n - s> \x0b\x00\x00\x01\x00\x02\x041 - s> \xa1FstatusBok - s> \r\n - s> 6d1\r\n - s> \xc9\x06\x00\x01\x00\x02\x041 - s> \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/ - s> \r\n - s> 8\r\n - s> \x00\x00\x00\x01\x00\x02\x002 - s> \r\n - s> 0\r\n - s> \r\n - response: gen[ - { - b'commands': { - b'branchmap': { - b'args': {}, - b'permissions': [ - b'pull' - ] - }, - b'capabilities': { - b'args': {}, - b'permissions': [ - b'pull' - ] - }, - b'changesetdata': { - b'args': { - b'fields': { - b'default': set([]), - b'required': False, - b'type': b'set', - b'validvalues': set([ - b'bookmarks', - b'parents', - b'phase', - b'revision' - ]) - }, - b'revisions': { - b'required': True, - b'type': b'list' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'filedata': { - b'args': { - b'fields': { - b'default': set([]), - b'required': False, - b'type': b'set', - b'validvalues': set([ - b'linknode', - b'parents', - b'revision' - ]) - }, - b'haveparents': { - b'default': False, - b'required': False, - b'type': b'bool' - }, - b'nodes': { - b'required': True, - b'type': b'list' - }, - b'path': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'filesdata': { - b'args': { - b'fields': { - b'default': set([]), - b'required': False, - b'type': b'set', - b'validvalues': set([ - b'firstchangeset', - b'linknode', - b'parents', - b'revision' - ]) - }, - b'haveparents': { - b'default': False, - b'required': False, - b'type': b'bool' - }, - b'pathfilter': { - b'default': None, - b'required': False, - b'type': b'dict' - }, - b'revisions': { - b'required': True, - b'type': b'list' - } - }, - b'permissions': [ - b'pull' - ], - b'recommendedbatchsize': 50000 - }, - b'heads': { - b'args': { - b'publiconly': { - b'default': False, - b'required': False, - b'type': b'bool' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'known': { - b'args': { - b'nodes': { - b'default': [], - b'required': False, - b'type': b'list' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'listkeys': { - b'args': { - b'namespace': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'lookup': { - b'args': { - b'key': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'manifestdata': { - b'args': { - b'fields': { - b'default': set([]), - b'required': False, - b'type': b'set', - b'validvalues': set([ - b'parents', - b'revision' - ]) - }, - b'haveparents': { - b'default': False, - b'required': False, - b'type': b'bool' - }, - b'nodes': { - b'required': True, - b'type': b'list' - }, - b'tree': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'pull' - ], - b'recommendedbatchsize': 100000 - }, - b'pushkey': { - b'args': { - b'key': { - b'required': True, - b'type': b'bytes' - }, - b'namespace': { - b'required': True, - b'type': b'bytes' - }, - b'new': { - b'required': True, - b'type': b'bytes' - }, - b'old': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'push' - ] - }, - b'rawstorefiledata': { - b'args': { - b'files': { - b'required': True, - b'type': b'list' - }, - b'pathfilter': { - b'default': None, - b'required': False, - b'type': b'list' - } - }, - b'permissions': [ - b'pull' - ] - } - }, - b'framingmediatypes': [ - b'application/mercurial-exp-framing-0006' - ], - b'pathfilterprefixes': set([ - b'path:', - b'rootfilesin:' - ]), - b'rawrepoformats': [ - b'generaldelta', - b'revlogv1', - b'sparserevlog' - ], - b'redirect': { - b'hashes': [ - b'sha256', - b'sha1' - ], - b'targets': [ - { - b'name': b'target-bad-tls', - b'protocol': b'https', - b'snirequired': True, - b'uris': [ - b'https://example.com/' - ] - } - ] - } - } - ] - (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob) - - $ cat >> $HGRCPATH << EOF - > [extensions] - > nosni=! - > EOF - -Unknown tls value is filtered from compatible targets - - $ cat > redirects.py << EOF - > [ - > { - > b'name': b'target-bad-tls', - > b'protocol': b'https', - > b'uris': [b'https://example.com/'], - > b'tlsversions': [b'42', b'39'], - > }, - > ] - > EOF - - $ sendhttpv2peerhandshake << EOF - > command capabilities - > EOF - creating http peer for wire protocol version 2 - s> setsockopt(6, 1, 1) -> None (?) - s> GET /?cmd=capabilities HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> vary: X-HgProto-1,X-HgUpgrade-1\r\n - s> x-hgproto-1: cbor\r\n - s> x-hgupgrade-1: exp-http-v2-0003\r\n - s> accept: application/mercurial-0.1\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> user-agent: Mercurial debugwireproto\r\n - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 200 OK\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: application/mercurial-cbor\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\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 (?) - s> POST /api/exp-http-v2-0003/ro/capabilities HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> accept: application/mercurial-exp-framing-0006\r\n - s> content-type: application/mercurial-exp-framing-0006\r\n - s> content-length: 102\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> user-agent: Mercurial debugwireproto\r\n - s> \r\n - s> \x1c\x00\x00\x01\x00\x01\x01\x82\xa1Pcontentencodings\x81Hidentity:\x00\x00\x01\x00\x01\x00\x11\xa2DnameLcapabilitiesHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x80 - s> makefile('rb', None) - s> HTTP/1.1 200 OK\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: application/mercurial-exp-framing-0006\r\n - s> Transfer-Encoding: chunked\r\n - s> \r\n - s> 11\r\n - s> \t\x00\x00\x01\x00\x02\x01\x92 - s> Hidentity - s> \r\n - s> 13\r\n - s> \x0b\x00\x00\x01\x00\x02\x041 - s> \xa1FstatusBok - s> \r\n - s> 6d7\r\n - s> \xcf\x06\x00\x01\x00\x02\x041 - s> \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/ - s> \r\n - s> 8\r\n - s> \x00\x00\x00\x01\x00\x02\x002 - s> \r\n - s> 0\r\n - s> \r\n - response: gen[ - { - b'commands': { - b'branchmap': { - b'args': {}, - b'permissions': [ - b'pull' - ] - }, - b'capabilities': { - b'args': {}, - b'permissions': [ - b'pull' - ] - }, - b'changesetdata': { - b'args': { - b'fields': { - b'default': set([]), - b'required': False, - b'type': b'set', - b'validvalues': set([ - b'bookmarks', - b'parents', - b'phase', - b'revision' - ]) - }, - b'revisions': { - b'required': True, - b'type': b'list' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'filedata': { - b'args': { - b'fields': { - b'default': set([]), - b'required': False, - b'type': b'set', - b'validvalues': set([ - b'linknode', - b'parents', - b'revision' - ]) - }, - b'haveparents': { - b'default': False, - b'required': False, - b'type': b'bool' - }, - b'nodes': { - b'required': True, - b'type': b'list' - }, - b'path': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'filesdata': { - b'args': { - b'fields': { - b'default': set([]), - b'required': False, - b'type': b'set', - b'validvalues': set([ - b'firstchangeset', - b'linknode', - b'parents', - b'revision' - ]) - }, - b'haveparents': { - b'default': False, - b'required': False, - b'type': b'bool' - }, - b'pathfilter': { - b'default': None, - b'required': False, - b'type': b'dict' - }, - b'revisions': { - b'required': True, - b'type': b'list' - } - }, - b'permissions': [ - b'pull' - ], - b'recommendedbatchsize': 50000 - }, - b'heads': { - b'args': { - b'publiconly': { - b'default': False, - b'required': False, - b'type': b'bool' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'known': { - b'args': { - b'nodes': { - b'default': [], - b'required': False, - b'type': b'list' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'listkeys': { - b'args': { - b'namespace': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'lookup': { - b'args': { - b'key': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'pull' - ] - }, - b'manifestdata': { - b'args': { - b'fields': { - b'default': set([]), - b'required': False, - b'type': b'set', - b'validvalues': set([ - b'parents', - b'revision' - ]) - }, - b'haveparents': { - b'default': False, - b'required': False, - b'type': b'bool' - }, - b'nodes': { - b'required': True, - b'type': b'list' - }, - b'tree': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'pull' - ], - b'recommendedbatchsize': 100000 - }, - b'pushkey': { - b'args': { - b'key': { - b'required': True, - b'type': b'bytes' - }, - b'namespace': { - b'required': True, - b'type': b'bytes' - }, - b'new': { - b'required': True, - b'type': b'bytes' - }, - b'old': { - b'required': True, - b'type': b'bytes' - } - }, - b'permissions': [ - b'push' - ] - }, - b'rawstorefiledata': { - b'args': { - b'files': { - b'required': True, - b'type': b'list' - }, - b'pathfilter': { - b'default': None, - b'required': False, - b'type': b'list' - } - }, - b'permissions': [ - b'pull' - ] - } - }, - b'framingmediatypes': [ - b'application/mercurial-exp-framing-0006' - ], - b'pathfilterprefixes': set([ - b'path:', - b'rootfilesin:' - ]), - b'rawrepoformats': [ - b'generaldelta', - b'revlogv1', - b'sparserevlog' - ], - b'redirect': { - b'hashes': [ - b'sha256', - b'sha1' - ], - b'targets': [ - { - b'name': b'target-bad-tls', - b'protocol': b'https', - b'tlsversions': [ - b'42', - b'39' - ], - b'uris': [ - b'https://example.com/' - ] - } - ] - } - } - ] - (sent 2 HTTP requests and * bytes; received * bytes in responses) (glob) - -Set up the server to issue content redirects to its built-in API server. - - $ cat > redirects.py << EOF - > [ - > { - > b'name': b'local', - > b'protocol': b'http', - > b'uris': [b'http://example.com/'], - > }, - > ] - > EOF - -Request to eventual cache URL should return 404 (validating the cache server works) - - $ sendhttpraw << EOF - > httprequest GET api/simplecache/missingkey - > user-agent: test - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> GET /api/simplecache/missingkey HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> user-agent: test\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 404 Not Found\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: text/plain\r\n - s> Content-Length: 22\r\n - s> \r\n - s> key not found in cache - -Send a cacheable request - - $ sendhttpv2peer << EOF - > command manifestdata - > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41'] - > tree eval:b'' - > fields eval:[b'parents'] - > EOF - creating http peer for wire protocol version 2 - sending manifestdata command - response: gen[ - { - b'totalitems': 1 - }, - { - b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A', - b'parents': [ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ] - } - ] - -Cached entry should be available on server - - $ sendhttpraw << EOF - > httprequest GET api/simplecache/47abb8efa5f01b8964d74917793ad2464db0fa2c - > user-agent: test - > EOF - using raw connection to peer - s> setsockopt(6, 1, 1) -> None (?) - s> GET /api/simplecache/47abb8efa5f01b8964d74917793ad2464db0fa2c HTTP/1.1\r\n - s> Accept-Encoding: identity\r\n - s> user-agent: test\r\n - s> host: $LOCALIP:$HGPORT\r\n (glob) - s> \r\n - s> makefile('rb', None) - s> HTTP/1.1 200 OK\r\n - s> Server: testing stub value\r\n - s> Date: $HTTP_DATE$\r\n - s> Content-Type: application/mercurial-cbor\r\n - s> Content-Length: 91\r\n - s> \r\n - s> \xa1Jtotalitems\x01\xa2DnodeT\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&AGparents\x82T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 - cbor> [ - { - b'totalitems': 1 - }, - { - b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A', - b'parents': [ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ] - } - ] - -2nd request should result in content redirect response - - $ sendhttpv2peer << EOF - > command manifestdata - > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41'] - > tree eval:b'' - > fields eval:[b'parents'] - > EOF - creating http peer for wire protocol version 2 - sending manifestdata command - response: gen[ - { - b'totalitems': 1 - }, - { - b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A', - b'parents': [ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - ] - } - ] - - $ cat error.log - $ killdaemons.py - - $ cat .hg/blackbox.log - *> cacher constructed for manifestdata (glob) - *> cache miss for 47abb8efa5f01b8964d74917793ad2464db0fa2c (glob) - *> storing cache entry for 47abb8efa5f01b8964d74917793ad2464db0fa2c (glob) - *> cacher constructed for manifestdata (glob) - *> cache hit for 47abb8efa5f01b8964d74917793ad2464db0fa2c (glob) - *> sending content redirect for 47abb8efa5f01b8964d74917793ad2464db0fa2c to http://*:$HGPORT/api/simplecache/47abb8efa5f01b8964d74917793ad2464db0fa2c (glob) diff --git a/tests/test-wireproto-exchangev2-shallow.t b/tests/test-wireproto-exchangev2-shallow.t deleted file mode 100644 --- a/tests/test-wireproto-exchangev2-shallow.t +++ /dev/null @@ -1,597 +0,0 @@ -#require sqlite - -Tests for wire protocol version 2 exchange. -Tests in this file should be folded into existing tests once protocol -v2 has enough features that it can be enabled via #testcase in existing -tests. - - $ . $TESTDIR/wireprotohelpers.sh - $ enablehttpv2client - $ cat >> $HGRCPATH << EOF - > [extensions] - > sqlitestore = - > pullext = $TESTDIR/pullext.py - > [storage] - > new-repo-backend=sqlite - > EOF - -Configure a server - - $ hg init server-basic - $ enablehttpv2 server-basic - $ cd server-basic - $ mkdir dir0 dir1 - $ echo a0 > a - $ echo b0 > b - $ hg -q commit -A -m 'commit 0' - $ echo c0 > dir0/c - $ echo d0 > dir0/d - $ hg -q commit -A -m 'commit 1' - $ echo e0 > dir1/e - $ echo f0 > dir1/f - $ hg -q commit -A -m 'commit 2' - $ echo c1 > dir0/c - $ echo e1 > dir1/e - $ hg commit -m 'commit 3' - $ echo c2 > dir0/c - $ echo e2 > dir1/e - $ echo f1 > dir1/f - $ hg commit -m 'commit 4' - $ echo a1 > a - $ echo b1 > b - $ hg commit -m 'commit 5' - - $ hg log -G -T '{node} {desc}' - @ 93a8bd067ed2840d9aa810ad598168383a3a2c3a commit 5 - | - o dc666cf9ecf3d94e6b830f30e5f1272e2a9164d9 commit 4 - | - o 97765fc3cd624fd1fa0176932c21ffd16adf432e commit 3 - | - o 47fe012ab237a8c7fc0c78f9f26d5866eef3f825 commit 2 - | - o b709380892b193c1091d3a817f706052e346821b commit 1 - | - o 3390ef850073fbc2f0dfff2244342c8e9229013a commit 0 - - $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log - $ cat hg.pid > $DAEMON_PIDS - - $ cd .. - -Shallow clone pulls down latest revision of every file - - $ hg --debug clone --depth 1 http://localhost:$HGPORT client-shallow-1 - using http://localhost:$HGPORT/ - sending capabilities command - query 1; heads - sending 2 commands - sending command heads: {} - sending command known: { - 'nodes': [] - } - 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=22; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=1; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos) - sending 1 commands - sending command changesetdata: { - 'fields': set([ - 'bookmarks', - 'parents', - 'phase', - 'revision' - ]), - 'revisions': [ - { - 'heads': [ - '\x93\xa8\xbd\x06~\xd2\x84\r\x9a\xa8\x10\xadY\x81h8::,:' - ], - 'roots': [], - 'type': 'changesetdagrange' - } - ] - } - 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=1170; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) (?) - add changeset 3390ef850073 - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) (?) - add changeset b709380892b1 - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) (?) - add changeset 47fe012ab237 - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) (?) - add changeset 97765fc3cd62 - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) (?) - add changeset dc666cf9ecf3 - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) (?) - add changeset 93a8bd067ed2 - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) (?) - checking for updated bookmarks - sending 1 commands - sending command manifestdata: { - 'fields': set([ - 'parents', - 'revision' - ]), - 'haveparents': True, - 'nodes': [ - '\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A', - '|2 \x1a\xa3\xa1R\xa9\xe6\xa9"+?\xa8\xd0\xe3\x0f\xc2V\xe8', - '\x8d\xd0W<\x7f\xaf\xe2\x04F\xcc\xea\xac\x05N\xea\xa4x\x91M\xdb', - '113\x85\xf2!\x8b\x08^\xb2Z\x821\x1e*\xdd\x0e\xeb\x8c3', - 'H]O\xc2`\xef\\\xb9\xc0p6\x88K\x00k\x11\x0ej\xdby', - '\xd9;\xc4\x0b\x0e*GMp\xee\xf7}^\x91/f\x7fSd\x83' - ], - 'tree': '' - } - 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=1515; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - sending 1 commands - sending command filesdata: { - 'fields': set([ - 'linknode', - 'parents', - 'revision' - ]), - 'haveparents': False, - 'revisions': [ - { - 'nodes': [ - '\x93\xa8\xbd\x06~\xd2\x84\r\x9a\xa8\x10\xadY\x81h8::,:' - ], - 'type': 'changesetexplicit' - } - ] - } - 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=1005; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - updating the branch cache - new changesets 3390ef850073:93a8bd067ed2 - updating to branch default - resolving manifests - branchmerge: False, force: False, partial: False - ancestor: 000000000000, local: 000000000000+, remote: 93a8bd067ed2 - a: remote created -> g - getting a - b: remote created -> g - getting b - dir0/c: remote created -> g - getting dir0/c - dir0/d: remote created -> g - getting dir0/d - dir1/e: remote created -> g - getting dir1/e - dir1/f: remote created -> g - getting dir1/f - 6 files updated, 0 files merged, 0 files removed, 0 files unresolved - 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 - id = 1 - path = a - revnum = 0 - node = \x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xcc (esc) - p1rev = -1 - p2rev = -1 - linkrev = 5 - flags = 2 - - id = 2 - path = b - revnum = 0 - node = \xb1zk\xd3g=\x9a\xb8\xce\xd5\x81\xa2 \xf6/=\xa5\xccEx (esc) - p1rev = -1 - p2rev = -1 - linkrev = 5 - flags = 2 - - id = 3 - path = dir0/c - revnum = 0 - node = I\x1d\xa1\xbb\x89\xeax\xc0\xc0\xa2s[\x16\xce}\x93\x1d\xc8\xe2\r (esc) - p1rev = -1 - p2rev = -1 - linkrev = 5 - flags = 2 - - id = 4 - path = dir0/d - revnum = 0 - node = S\x82\x06\xdc\x97\x1eR\x15@\xd6\x84:\xbf\xe6\xd1`2\xf6\xd4& (esc) - p1rev = -1 - p2rev = -1 - linkrev = 5 - flags = 0 - - id = 5 - path = dir1/e - revnum = 0 - node = ]\xf3\xac\xd8\xd0\xc7\xfaP\x98\xd0'\x9a\x044\xc3\x02\x9e+x\xe1 (esc) - p1rev = -1 - p2rev = -1 - linkrev = 5 - flags = 2 - - id = 6 - path = dir1/f - revnum = 0 - node = (\xc7v\xae\x08\xd0\xd5^\xb4\x06H\xb4\x01\xb9\x0f\xf5DH4\x8e (esc) - p1rev = -1 - p2rev = -1 - linkrev = 5 - flags = 2 - -Test a shallow clone with only some files - - $ hg --debug clone --depth 1 --include dir0/ http://localhost:$HGPORT client-shallow-narrow-1 - using http://localhost:$HGPORT/ - sending capabilities command - query 1; heads - sending 2 commands - sending command heads: {} - sending command known: { - 'nodes': [] - } - 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=22; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=1; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos) - sending 1 commands - sending command changesetdata: { - 'fields': set([ - 'bookmarks', - 'parents', - 'phase', - 'revision' - ]), - 'revisions': [ - { - 'heads': [ - '\x93\xa8\xbd\x06~\xd2\x84\r\x9a\xa8\x10\xadY\x81h8::,:' - ], - 'roots': [], - 'type': 'changesetdagrange' - } - ] - } - 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=1170; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) (?) - add changeset 3390ef850073 - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) (?) - add changeset b709380892b1 - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) (?) - add changeset 47fe012ab237 - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) (?) - add changeset 97765fc3cd62 - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) (?) - add changeset dc666cf9ecf3 - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) (?) - add changeset 93a8bd067ed2 - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) (?) - checking for updated bookmarks - sending 1 commands - sending command manifestdata: { - 'fields': set([ - 'parents', - 'revision' - ]), - 'haveparents': True, - 'nodes': [ - '\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A', - '|2 \x1a\xa3\xa1R\xa9\xe6\xa9"+?\xa8\xd0\xe3\x0f\xc2V\xe8', - '\x8d\xd0W<\x7f\xaf\xe2\x04F\xcc\xea\xac\x05N\xea\xa4x\x91M\xdb', - '113\x85\xf2!\x8b\x08^\xb2Z\x821\x1e*\xdd\x0e\xeb\x8c3', - 'H]O\xc2`\xef\\\xb9\xc0p6\x88K\x00k\x11\x0ej\xdby', - '\xd9;\xc4\x0b\x0e*GMp\xee\xf7}^\x91/f\x7fSd\x83' - ], - 'tree': '' - } - 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=1515; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - sending 1 commands - sending command filesdata: { - 'fields': set([ - 'linknode', - 'parents', - 'revision' - ]), - 'haveparents': False, - 'pathfilter': { - 'include': [ - 'path:dir0' - ] - }, - 'revisions': [ - { - 'nodes': [ - '\x93\xa8\xbd\x06~\xd2\x84\r\x9a\xa8\x10\xadY\x81h8::,:' - ], - 'type': 'changesetexplicit' - } - ] - } - 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=355; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - updating the branch cache - new changesets 3390ef850073:93a8bd067ed2 - updating to branch default - resolving manifests - branchmerge: False, force: False, partial: False - ancestor: 000000000000, local: 000000000000+, remote: 93a8bd067ed2 - dir0/c: remote created -> g - getting dir0/c - dir0/d: remote created -> g - getting dir0/d - 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - 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 - id = 1 - path = dir0/c - revnum = 0 - node = I\x1d\xa1\xbb\x89\xeax\xc0\xc0\xa2s[\x16\xce}\x93\x1d\xc8\xe2\r (esc) - p1rev = -1 - p2rev = -1 - linkrev = 5 - flags = 2 - - id = 2 - path = dir0/d - revnum = 0 - node = S\x82\x06\xdc\x97\x1eR\x15@\xd6\x84:\xbf\xe6\xd1`2\xf6\xd4& (esc) - p1rev = -1 - p2rev = -1 - linkrev = 5 - flags = 0 - -Cloning an old revision with depth=1 works - - $ hg --debug clone --depth 1 -r 97765fc3cd624fd1fa0176932c21ffd16adf432e http://localhost:$HGPORT client-shallow-2 - using http://localhost:$HGPORT/ - sending capabilities command - sending 1 commands - sending command lookup: { - 'key': '97765fc3cd624fd1fa0176932c21ffd16adf432e' - } - 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=21; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - query 1; heads - sending 2 commands - sending command heads: {} - sending command known: { - 'nodes': [] - } - 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=22; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=1; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos) - sending 1 commands - sending command changesetdata: { - 'fields': set([ - 'bookmarks', - 'parents', - 'phase', - 'revision' - ]), - 'revisions': [ - { - 'heads': [ - '\x97v_\xc3\xcdbO\xd1\xfa\x01v\x93,!\xff\xd1j\xdfC.' - ], - 'roots': [], - 'type': 'changesetdagrange' - } - ] - } - 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=783; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) (?) - add changeset 3390ef850073 - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) (?) - add changeset b709380892b1 - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) (?) - add changeset 47fe012ab237 - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) (?) - add changeset 97765fc3cd62 - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) (?) - checking for updated bookmarks - sending 1 commands - sending command manifestdata: { - 'fields': set([ - 'parents', - 'revision' - ]), - 'haveparents': True, - 'nodes': [ - '\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A', - '|2 \x1a\xa3\xa1R\xa9\xe6\xa9"+?\xa8\xd0\xe3\x0f\xc2V\xe8', - '\x8d\xd0W<\x7f\xaf\xe2\x04F\xcc\xea\xac\x05N\xea\xa4x\x91M\xdb', - '113\x85\xf2!\x8b\x08^\xb2Z\x821\x1e*\xdd\x0e\xeb\x8c3' - ], - 'tree': '' - } - 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=967; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - sending 1 commands - sending command filesdata: { - 'fields': set([ - 'linknode', - 'parents', - 'revision' - ]), - 'haveparents': False, - 'revisions': [ - { - 'nodes': [ - '\x97v_\xc3\xcdbO\xd1\xfa\x01v\x93,!\xff\xd1j\xdfC.' - ], - 'type': 'changesetexplicit' - } - ] - } - 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=1005; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - updating the branch cache - new changesets 3390ef850073:97765fc3cd62 - updating to branch default - resolving manifests - branchmerge: False, force: False, partial: False - ancestor: 000000000000, local: 000000000000+, remote: 97765fc3cd62 - a: remote created -> g - getting a - b: remote created -> g - getting b - dir0/c: remote created -> g - getting dir0/c - dir0/d: remote created -> g - getting dir0/d - dir1/e: remote created -> g - getting dir1/e - dir1/f: remote created -> g - getting dir1/f - 6 files updated, 0 files merged, 0 files removed, 0 files unresolved - updating the branch cache - (sent 6 HTTP requests and * bytes; received * bytes in responses) (glob) - -Incremental pull of shallow clone fetches new changesets - - $ hg --cwd client-shallow-2 --debug pull http://localhost:$HGPORT - pulling from http://localhost:$HGPORT/ - using http://localhost:$HGPORT/ - sending capabilities command - query 1; heads - sending 2 commands - sending command heads: {} - sending command known: { - 'nodes': [ - '\x97v_\xc3\xcdbO\xd1\xfa\x01v\x93,!\xff\xd1j\xdfC.' - ] - } - 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=22; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=2; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos) - searching for changes - all local changesets known remotely - sending 1 commands - sending command changesetdata: { - 'fields': set([ - 'bookmarks', - 'parents', - 'phase', - 'revision' - ]), - 'revisions': [ - { - 'heads': [ - '\x93\xa8\xbd\x06~\xd2\x84\r\x9a\xa8\x10\xadY\x81h8::,:' - ], - 'roots': [ - '\x97v_\xc3\xcdbO\xd1\xfa\x01v\x93,!\xff\xd1j\xdfC.' - ], - 'type': 'changesetdagrange' - } - ] - } - 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=400; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) (?) - add changeset dc666cf9ecf3 - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) (?) - add changeset 93a8bd067ed2 - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) (?) - checking for updated bookmarks - sending 1 commands - sending command manifestdata: { - 'fields': set([ - 'parents', - 'revision' - ]), - 'haveparents': True, - 'nodes': [ - 'H]O\xc2`\xef\\\xb9\xc0p6\x88K\x00k\x11\x0ej\xdby', - '\xd9;\xc4\x0b\x0e*GMp\xee\xf7}^\x91/f\x7fSd\x83' - ], - 'tree': '' - } - 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=561; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - sending 1 commands - sending command filesdata: { - 'fields': set([ - 'linknode', - 'parents', - 'revision' - ]), - 'haveparents': False, - 'revisions': [ - { - 'nodes': [ - '\xdcfl\xf9\xec\xf3\xd9Nk\x83\x0f0\xe5\xf1\'.*\x91d\xd9', - '\x93\xa8\xbd\x06~\xd2\x84\r\x9a\xa8\x10\xadY\x81h8::,:' - ], - 'type': 'changesetexplicit' - } - ] - } - 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=1373; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - updating the branch cache - new changesets dc666cf9ecf3:93a8bd067ed2 - (run 'hg update' to get a working copy) - (sent 5 HTTP requests and * bytes; received * bytes in responses) (glob) - - $ hg --cwd client-shallow-2 up tip - merging dir0/c - merging dir1/e - 3 files updated, 2 files merged, 0 files removed, 0 files unresolved diff --git a/tests/test-wireproto-exchangev2.t b/tests/test-wireproto-exchangev2.t deleted file mode 100644 --- a/tests/test-wireproto-exchangev2.t +++ /dev/null @@ -1,1392 +0,0 @@ -Tests for wire protocol version 2 exchange. -Tests in this file should be folded into existing tests once protocol -v2 has enough features that it can be enabled via #testcase in existing -tests. - - $ . $TESTDIR/wireprotohelpers.sh - $ enablehttpv2client - - $ hg init server-simple - $ enablehttpv2 server-simple - $ cd server-simple - $ cat >> .hg/hgrc << EOF - > [phases] - > publish = false - > EOF - $ echo a0 > a - $ echo b0 > b - $ hg -q commit -A -m 'commit 0' - - $ echo a1 > a - $ hg commit -m 'commit 1' - $ hg phase --public -r . - $ echo a2 > a - $ hg commit -m 'commit 2' - - $ hg -q up -r 0 - $ echo b1 > b - $ hg -q commit -m 'head 2 commit 1' - $ echo b2 > b - $ hg -q commit -m 'head 2 commit 2' - - $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log - $ cat hg.pid > $DAEMON_PIDS - - $ cd .. - -Test basic clone - -Output is flaky, save it in a file and check part independently - $ hg --debug clone -U http://localhost:$HGPORT client-simple > clone-output - - $ cat clone-output | grep -v "received frame" - using http://localhost:$HGPORT/ - sending capabilities command - query 1; heads - sending 2 commands - sending command heads: {} - sending command known: { - 'nodes': [] - } - sending 1 commands - sending command changesetdata: { - 'fields': set([ - 'bookmarks', - 'parents', - 'phase', - 'revision' - ]), - 'revisions': [ - { - 'heads': [ - '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1', - '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f' - ], - 'roots': [], - 'type': 'changesetdagrange' - } - ] - } - add changeset 3390ef850073 - add changeset 4432d83626e8 - add changeset cd2534766bec - add changeset e96ae20f4188 - add changeset caa2a465451d - checking for updated bookmarks - sending 1 commands - sending command manifestdata: { - 'fields': set([ - 'parents', - 'revision' - ]), - 'haveparents': True, - 'nodes': [ - '\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A', - '\xa9\x88\xfbCX>\x87\x1d\x1e\xd5u\x0e\xe0t\xc6\xd8@\xbb\xbf\xc8', - '\xec\x80NH\x8c \x88\xc25\t\x9a\x10 u\x13\xbe\xcd\xc3\xdd\xa5', - '\x04\\\x7f9\'\xda\x13\xe7Z\xf8\xf0\xe4\xf0HI\xe4a\xa9x\x0f', - '7\x9c\xb0\xc2\xe6d\\y\xdd\xc5\x9a\x1dG\'\xa9\xfb\x83\n\xeb&' - ], - 'tree': '' - } - sending 1 commands - sending command filesdata: { - 'fields': set([ - 'parents', - 'revision' - ]), - 'haveparents': True, - 'revisions': [ - { - 'nodes': [ - '3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:', - 'D2\xd86&\xe8\xa9\x86U\xf0b\xec\x1f*C\xb0\x7f\x7f\xbb\xb0', - '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f', - '\xe9j\xe2\x0fA\x88H{\x9a\xe4\xef9A\xc2|\x81\x141F\xe5', - '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1' - ], - 'type': 'changesetexplicit' - } - ] - } - updating the branch cache - new changesets 3390ef850073:caa2a465451d (3 drafts) - updating the branch cache - (sent 5 HTTP requests and * bytes; received * bytes in responses) (glob) - - $ 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=43; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=1; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=3; 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) - received frame(size=941; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - 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) - received frame(size=992; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - 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) - received frame(size=901; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - - $ rm clone-output - -All changesets should have been transferred - - $ hg -R client-simple debugindex -c - rev linkrev nodeid p1 p2 - 0 0 3390ef850073 000000000000 000000000000 - 1 1 4432d83626e8 3390ef850073 000000000000 - 2 2 cd2534766bec 4432d83626e8 000000000000 - 3 3 e96ae20f4188 3390ef850073 000000000000 - 4 4 caa2a465451d e96ae20f4188 000000000000 - - $ hg -R client-simple log -G -T '{rev} {node} {phase}\n' - o 4 caa2a465451dd1facda0f5b12312c355584188a1 draft - | - o 3 e96ae20f4188487b9ae4ef3941c27c81143146e5 draft - | - | o 2 cd2534766bece138c7c1afdc6825302f0f62d81f draft - | | - | o 1 4432d83626e8a98655f062ec1f2a43b07f7fbbb0 public - |/ - o 0 3390ef850073fbc2f0dfff2244342c8e9229013a public - - -All manifests should have been transferred - - $ hg -R client-simple debugindex -m - rev linkrev nodeid p1 p2 - 0 0 992f4779029a 000000000000 000000000000 - 1 1 a988fb43583e 992f4779029a 000000000000 - 2 2 ec804e488c20 a988fb43583e 000000000000 - 3 3 045c7f3927da 992f4779029a 000000000000 - 4 4 379cb0c2e664 045c7f3927da 000000000000 - -Cloning only a specific revision works - -Output is flaky, save it in a file and check part independently - $ hg --debug clone -U -r 4432d83626e8 http://localhost:$HGPORT client-singlehead > clone-output - - $ cat clone-output | grep -v "received frame" - using http://localhost:$HGPORT/ - sending capabilities command - sending 1 commands - sending command lookup: { - 'key': '4432d83626e8' - } - query 1; heads - sending 2 commands - sending command heads: {} - sending command known: { - 'nodes': [] - } - sending 1 commands - sending command changesetdata: { - 'fields': set([ - 'bookmarks', - 'parents', - 'phase', - 'revision' - ]), - 'revisions': [ - { - 'heads': [ - 'D2\xd86&\xe8\xa9\x86U\xf0b\xec\x1f*C\xb0\x7f\x7f\xbb\xb0' - ], - 'roots': [], - 'type': 'changesetdagrange' - } - ] - } - add changeset 3390ef850073 - add changeset 4432d83626e8 - checking for updated bookmarks - sending 1 commands - sending command manifestdata: { - 'fields': set([ - 'parents', - 'revision' - ]), - 'haveparents': True, - 'nodes': [ - '\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A', - '\xa9\x88\xfbCX>\x87\x1d\x1e\xd5u\x0e\xe0t\xc6\xd8@\xbb\xbf\xc8' - ], - 'tree': '' - } - sending 1 commands - sending command filesdata: { - 'fields': set([ - 'parents', - 'revision' - ]), - 'haveparents': True, - 'revisions': [ - { - 'nodes': [ - '3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:', - 'D2\xd86&\xe8\xa9\x86U\xf0b\xec\x1f*C\xb0\x7f\x7f\xbb\xb0' - ], - 'type': 'changesetexplicit' - } - ] - } - updating the branch cache - new changesets 3390ef850073:4432d83626e8 - updating the branch cache - (sent 6 HTTP requests and * bytes; received * bytes in responses) (glob) - - $ 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=21; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - 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) - received frame(size=43; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=1; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=3; 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) - received frame(size=381; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - 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) - received frame(size=404; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - 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) - received frame(size=439; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - - $ rm clone-output - - $ cd client-singlehead - - $ hg log -G -T '{rev} {node} {phase}\n' - o 1 4432d83626e8a98655f062ec1f2a43b07f7fbbb0 public - | - o 0 3390ef850073fbc2f0dfff2244342c8e9229013a public - - - $ hg debugindex -m - rev linkrev nodeid p1 p2 - 0 0 992f4779029a 000000000000 000000000000 - 1 1 a988fb43583e 992f4779029a 000000000000 - -Incremental pull works - -Output is flaky, save it in a file and check part independently - $ hg --debug pull > pull-output - - $ cat pull-output | grep -v "received frame" - pulling from http://localhost:$HGPORT/ - using http://localhost:$HGPORT/ - sending capabilities command - query 1; heads - sending 2 commands - sending command heads: {} - sending command known: { - 'nodes': [ - 'D2\xd86&\xe8\xa9\x86U\xf0b\xec\x1f*C\xb0\x7f\x7f\xbb\xb0' - ] - } - searching for changes - all local changesets known remotely - sending 1 commands - sending command changesetdata: { - 'fields': set([ - 'bookmarks', - 'parents', - 'phase', - 'revision' - ]), - 'revisions': [ - { - 'heads': [ - '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1', - '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f' - ], - 'roots': [ - 'D2\xd86&\xe8\xa9\x86U\xf0b\xec\x1f*C\xb0\x7f\x7f\xbb\xb0' - ], - 'type': 'changesetdagrange' - } - ] - } - add changeset cd2534766bec - add changeset e96ae20f4188 - add changeset caa2a465451d - checking for updated bookmarks - sending 1 commands - sending command manifestdata: { - 'fields': set([ - 'parents', - 'revision' - ]), - 'haveparents': True, - 'nodes': [ - '\xec\x80NH\x8c \x88\xc25\t\x9a\x10 u\x13\xbe\xcd\xc3\xdd\xa5', - '\x04\\\x7f9\'\xda\x13\xe7Z\xf8\xf0\xe4\xf0HI\xe4a\xa9x\x0f', - '7\x9c\xb0\xc2\xe6d\\y\xdd\xc5\x9a\x1dG\'\xa9\xfb\x83\n\xeb&' - ], - 'tree': '' - } - sending 1 commands - sending command filesdata: { - 'fields': set([ - 'parents', - 'revision' - ]), - 'haveparents': True, - 'revisions': [ - { - 'nodes': [ - '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f', - '\xe9j\xe2\x0fA\x88H{\x9a\xe4\xef9A\xc2|\x81\x141F\xe5', - '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1' - ], - 'type': 'changesetexplicit' - } - ] - } - updating the branch cache - new changesets cd2534766bec:caa2a465451d (3 drafts) - (run 'hg update' to get a working copy) - (sent 5 HTTP requests and * bytes; received * bytes in responses) (glob) - - $ cat pull-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=43; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=2; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=3; 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) - received frame(size=573; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - 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) - received frame(size=601; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - 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) - received frame(size=527; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - - $ rm pull-output - - $ hg log -G -T '{rev} {node} {phase}\n' - o 4 caa2a465451dd1facda0f5b12312c355584188a1 draft - | - o 3 e96ae20f4188487b9ae4ef3941c27c81143146e5 draft - | - | o 2 cd2534766bece138c7c1afdc6825302f0f62d81f draft - | | - | o 1 4432d83626e8a98655f062ec1f2a43b07f7fbbb0 public - |/ - o 0 3390ef850073fbc2f0dfff2244342c8e9229013a public - - - $ hg debugindex -m - rev linkrev nodeid p1 p2 - 0 0 992f4779029a 000000000000 000000000000 - 1 1 a988fb43583e 992f4779029a 000000000000 - 2 2 ec804e488c20 a988fb43583e 000000000000 - 3 3 045c7f3927da 992f4779029a 000000000000 - 4 4 379cb0c2e664 045c7f3927da 000000000000 - -Phase-only update works -TODO this doesn't work - - $ hg -R ../server-simple phase --public -r caa2a465451dd - $ hg --debug pull - pulling from http://localhost:$HGPORT/ - using http://localhost:$HGPORT/ - sending capabilities command - query 1; heads - sending 2 commands - sending command heads: {} - sending command known: { - 'nodes': [ - '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f', - '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1' - ] - } - 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=43; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=3; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos) - searching for changes - all remote heads known locally - sending 1 commands - sending command changesetdata: { - 'fields': set([ - 'bookmarks', - 'parents', - 'phase', - 'revision' - ]), - 'revisions': [ - { - 'heads': [ - '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1', - '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f' - ], - 'roots': [ - '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1', - '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f' - ], - 'type': 'changesetdagrange' - } - ] - } - 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=13; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - checking for updated bookmarks - (run 'hg update' to get a working copy) - (sent 3 HTTP requests and * bytes; received * bytes in responses) (glob) - - $ hg log -G -T '{rev} {node} {phase}\n' - o 4 caa2a465451dd1facda0f5b12312c355584188a1 draft - | - o 3 e96ae20f4188487b9ae4ef3941c27c81143146e5 draft - | - | o 2 cd2534766bece138c7c1afdc6825302f0f62d81f draft - | | - | o 1 4432d83626e8a98655f062ec1f2a43b07f7fbbb0 public - |/ - o 0 3390ef850073fbc2f0dfff2244342c8e9229013a public - - - $ cd .. - -Bookmarks are transferred on clone - - $ hg -R server-simple bookmark -r 3390ef850073fbc2f0dfff2244342c8e9229013a book-1 - $ hg -R server-simple bookmark -r cd2534766bece138c7c1afdc6825302f0f62d81f book-2 - -Output is flaky, save it in a file and check part independently - $ hg --debug clone -U http://localhost:$HGPORT/ client-bookmarks > clone-output - - $ cat clone-output | grep -v "received frame" - using http://localhost:$HGPORT/ - sending capabilities command - query 1; heads - sending 2 commands - sending command heads: {} - sending command known: { - 'nodes': [] - } - sending 1 commands - sending command changesetdata: { - 'fields': set([ - 'bookmarks', - 'parents', - 'phase', - 'revision' - ]), - 'revisions': [ - { - 'heads': [ - '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1', - '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f' - ], - 'roots': [], - 'type': 'changesetdagrange' - } - ] - } - add changeset 3390ef850073 - add changeset 4432d83626e8 - add changeset cd2534766bec - add changeset e96ae20f4188 - add changeset caa2a465451d - checking for updated bookmarks - adding remote bookmark book-1 - adding remote bookmark book-2 - sending 1 commands - sending command manifestdata: { - 'fields': set([ - 'parents', - 'revision' - ]), - 'haveparents': True, - 'nodes': [ - '\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A', - '\xa9\x88\xfbCX>\x87\x1d\x1e\xd5u\x0e\xe0t\xc6\xd8@\xbb\xbf\xc8', - '\xec\x80NH\x8c \x88\xc25\t\x9a\x10 u\x13\xbe\xcd\xc3\xdd\xa5', - '\x04\\\x7f9\'\xda\x13\xe7Z\xf8\xf0\xe4\xf0HI\xe4a\xa9x\x0f', - '7\x9c\xb0\xc2\xe6d\\y\xdd\xc5\x9a\x1dG\'\xa9\xfb\x83\n\xeb&' - ], - 'tree': '' - } - sending 1 commands - sending command filesdata: { - 'fields': set([ - 'parents', - 'revision' - ]), - 'haveparents': True, - 'revisions': [ - { - 'nodes': [ - '3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:', - 'D2\xd86&\xe8\xa9\x86U\xf0b\xec\x1f*C\xb0\x7f\x7f\xbb\xb0', - '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f', - '\xe9j\xe2\x0fA\x88H{\x9a\xe4\xef9A\xc2|\x81\x141F\xe5', - '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1' - ], - 'type': 'changesetexplicit' - } - ] - } - updating the branch cache - new changesets 3390ef850073:caa2a465451d (1 drafts) - updating the branch cache - (sent 5 HTTP requests and * bytes; received * bytes in responses) (glob) - - $ 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=43; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=1; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=3; 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) - received frame(size=979; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - 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) - received frame(size=992; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - 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) - received frame(size=901; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - - $ rm clone-output - - $ hg -R client-bookmarks bookmarks - book-1 0:3390ef850073 - book-2 2:cd2534766bec - -Server-side bookmark moves are reflected during `hg pull` - - $ hg -R server-simple bookmark -r cd2534766bece138c7c1afdc6825302f0f62d81f book-1 - moving bookmark 'book-1' forward from 3390ef850073 - -Output is flaky, save it in a file and check part independently - $ hg -R client-bookmarks --debug pull > pull-output - - $ cat pull-output | grep -v "received frame" - pulling from http://localhost:$HGPORT/ - using http://localhost:$HGPORT/ - sending capabilities command - query 1; heads - sending 2 commands - sending command heads: {} - sending command known: { - 'nodes': [ - '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f', - '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1' - ] - } - searching for changes - all remote heads known locally - sending 1 commands - sending command changesetdata: { - 'fields': set([ - 'bookmarks', - 'parents', - 'phase', - 'revision' - ]), - 'revisions': [ - { - 'heads': [ - '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1', - '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f' - ], - 'roots': [ - '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1', - '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f' - ], - 'type': 'changesetdagrange' - } - ] - } - checking for updated bookmarks - updating bookmark book-1 - (run 'hg update' to get a working copy) - (sent 3 HTTP requests and * bytes; received * bytes in responses) (glob) - - $ cat pull-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=43; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=3; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=3; 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) - received frame(size=65; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - - $ rm pull-output - - $ hg -R client-bookmarks bookmarks - book-1 2:cd2534766bec - book-2 2:cd2534766bec - - $ killdaemons.py - -Let's set up a slightly more complicated server - - $ hg init server-2 - $ enablehttpv2 server-2 - $ cd server-2 - $ mkdir dir0 dir1 - $ echo a0 > a - $ echo b0 > b - $ hg -q commit -A -m 'commit 0' - $ echo c0 > dir0/c - $ echo d0 > dir0/d - $ hg -q commit -A -m 'commit 1' - $ echo e0 > dir1/e - $ echo f0 > dir1/f - $ hg -q commit -A -m 'commit 2' - $ echo c1 > dir0/c - $ echo e1 > dir1/e - $ hg commit -m 'commit 3' - $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log - $ cat hg.pid > $DAEMON_PIDS - - $ cd .. - -Narrow clone only fetches some files - -Output is flaky, save it in a file and check part independently - $ hg --config extensions.pullext=$TESTDIR/pullext.py --debug clone -U --include dir0/ http://localhost:$HGPORT/ client-narrow-0 > clone-output - - $ cat clone-output | grep -v "received frame" - using http://localhost:$HGPORT/ - sending capabilities command - query 1; heads - sending 2 commands - sending command heads: {} - sending command known: { - 'nodes': [] - } - sending 1 commands - sending command changesetdata: { - 'fields': set([ - 'bookmarks', - 'parents', - 'phase', - 'revision' - ]), - 'revisions': [ - { - 'heads': [ - '\x97v_\xc3\xcdbO\xd1\xfa\x01v\x93,!\xff\xd1j\xdfC.' - ], - 'roots': [], - 'type': 'changesetdagrange' - } - ] - } - add changeset 3390ef850073 - add changeset b709380892b1 - add changeset 47fe012ab237 - add changeset 97765fc3cd62 - checking for updated bookmarks - sending 1 commands - sending command manifestdata: { - 'fields': set([ - 'parents', - 'revision' - ]), - 'haveparents': True, - 'nodes': [ - '\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A', - '|2 \x1a\xa3\xa1R\xa9\xe6\xa9"+?\xa8\xd0\xe3\x0f\xc2V\xe8', - '\x8d\xd0W<\x7f\xaf\xe2\x04F\xcc\xea\xac\x05N\xea\xa4x\x91M\xdb', - '113\x85\xf2!\x8b\x08^\xb2Z\x821\x1e*\xdd\x0e\xeb\x8c3' - ], - 'tree': '' - } - sending 1 commands - sending command filesdata: { - 'fields': set([ - 'parents', - 'revision' - ]), - 'haveparents': True, - 'pathfilter': { - 'include': [ - 'path:dir0' - ] - }, - 'revisions': [ - { - 'nodes': [ - '3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:', - '\xb7\t8\x08\x92\xb1\x93\xc1\t\x1d:\x81\x7fp`R\xe3F\x82\x1b', - 'G\xfe\x01*\xb27\xa8\xc7\xfc\x0cx\xf9\xf2mXf\xee\xf3\xf8%', - '\x97v_\xc3\xcdbO\xd1\xfa\x01v\x93,!\xff\xd1j\xdfC.' - ], - 'type': 'changesetexplicit' - } - ] - } - updating the branch cache - new changesets 3390ef850073:97765fc3cd62 - updating the branch cache - (sent 5 HTTP requests and * bytes; received * bytes in responses) (glob) - - $ 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=22; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=1; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=3; 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) - received frame(size=783; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - 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) - received frame(size=967; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - 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) - received frame(size=449; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - - $ rm clone-output - -#if reporevlogstore - $ find client-narrow-0/.hg/store -type f -name '*.i' | sort - client-narrow-0/.hg/store/00changelog.i - client-narrow-0/.hg/store/00manifest.i - client-narrow-0/.hg/store/data/dir0/c.i - client-narrow-0/.hg/store/data/dir0/d.i -#endif - ---exclude by itself works - -Output is flaky, save it in a file and check part independently - $ hg --config extensions.pullext=$TESTDIR/pullext.py --debug clone -U --exclude dir0/ http://localhost:$HGPORT/ client-narrow-1 > clone-output - - $ cat clone-output | grep -v "received frame" - using http://localhost:$HGPORT/ - sending capabilities command - query 1; heads - sending 2 commands - sending command heads: {} - sending command known: { - 'nodes': [] - } - sending 1 commands - sending command changesetdata: { - 'fields': set([ - 'bookmarks', - 'parents', - 'phase', - 'revision' - ]), - 'revisions': [ - { - 'heads': [ - '\x97v_\xc3\xcdbO\xd1\xfa\x01v\x93,!\xff\xd1j\xdfC.' - ], - 'roots': [], - 'type': 'changesetdagrange' - } - ] - } - add changeset 3390ef850073 - add changeset b709380892b1 - add changeset 47fe012ab237 - add changeset 97765fc3cd62 - checking for updated bookmarks - sending 1 commands - sending command manifestdata: { - 'fields': set([ - 'parents', - 'revision' - ]), - 'haveparents': True, - 'nodes': [ - '\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A', - '|2 \x1a\xa3\xa1R\xa9\xe6\xa9"+?\xa8\xd0\xe3\x0f\xc2V\xe8', - '\x8d\xd0W<\x7f\xaf\xe2\x04F\xcc\xea\xac\x05N\xea\xa4x\x91M\xdb', - '113\x85\xf2!\x8b\x08^\xb2Z\x821\x1e*\xdd\x0e\xeb\x8c3' - ], - 'tree': '' - } - sending 1 commands - sending command filesdata: { - 'fields': set([ - 'parents', - 'revision' - ]), - 'haveparents': True, - 'pathfilter': { - 'exclude': [ - 'path:dir0' - ], - 'include': [ - 'path:.' - ] - }, - 'revisions': [ - { - 'nodes': [ - '3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:', - '\xb7\t8\x08\x92\xb1\x93\xc1\t\x1d:\x81\x7fp`R\xe3F\x82\x1b', - 'G\xfe\x01*\xb27\xa8\xc7\xfc\x0cx\xf9\xf2mXf\xee\xf3\xf8%', - '\x97v_\xc3\xcdbO\xd1\xfa\x01v\x93,!\xff\xd1j\xdfC.' - ], - 'type': 'changesetexplicit' - } - ] - } - updating the branch cache - new changesets 3390ef850073:97765fc3cd62 - updating the branch cache - (sent 5 HTTP requests and * bytes; received * bytes in responses) (glob) - - $ 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=22; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=1; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=3; 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) - received frame(size=783; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - 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) - received frame(size=967; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - 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) - received frame(size=709; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - - $ rm clone-output - -#if reporevlogstore - $ find client-narrow-1/.hg/store -type f -name '*.i' | sort - client-narrow-1/.hg/store/00changelog.i - client-narrow-1/.hg/store/00manifest.i - client-narrow-1/.hg/store/data/a.i - client-narrow-1/.hg/store/data/b.i - client-narrow-1/.hg/store/data/dir1/e.i - client-narrow-1/.hg/store/data/dir1/f.i -#endif - -Mixing --include and --exclude works - -Output is flaky, save it in a file and check part independently - $ hg --config extensions.pullext=$TESTDIR/pullext.py --debug clone -U --include dir0/ --exclude dir0/c http://localhost:$HGPORT/ client-narrow-2 > clone-output - - $ cat clone-output | grep -v "received frame" - using http://localhost:$HGPORT/ - sending capabilities command - query 1; heads - sending 2 commands - sending command heads: {} - sending command known: { - 'nodes': [] - } - sending 1 commands - sending command changesetdata: { - 'fields': set([ - 'bookmarks', - 'parents', - 'phase', - 'revision' - ]), - 'revisions': [ - { - 'heads': [ - '\x97v_\xc3\xcdbO\xd1\xfa\x01v\x93,!\xff\xd1j\xdfC.' - ], - 'roots': [], - 'type': 'changesetdagrange' - } - ] - } - add changeset 3390ef850073 - add changeset b709380892b1 - add changeset 47fe012ab237 - add changeset 97765fc3cd62 - checking for updated bookmarks - sending 1 commands - sending command manifestdata: { - 'fields': set([ - 'parents', - 'revision' - ]), - 'haveparents': True, - 'nodes': [ - '\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A', - '|2 \x1a\xa3\xa1R\xa9\xe6\xa9"+?\xa8\xd0\xe3\x0f\xc2V\xe8', - '\x8d\xd0W<\x7f\xaf\xe2\x04F\xcc\xea\xac\x05N\xea\xa4x\x91M\xdb', - '113\x85\xf2!\x8b\x08^\xb2Z\x821\x1e*\xdd\x0e\xeb\x8c3' - ], - 'tree': '' - } - sending 1 commands - sending command filesdata: { - 'fields': set([ - 'parents', - 'revision' - ]), - 'haveparents': True, - 'pathfilter': { - 'exclude': [ - 'path:dir0/c' - ], - 'include': [ - 'path:dir0' - ] - }, - 'revisions': [ - { - 'nodes': [ - '3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:', - '\xb7\t8\x08\x92\xb1\x93\xc1\t\x1d:\x81\x7fp`R\xe3F\x82\x1b', - 'G\xfe\x01*\xb27\xa8\xc7\xfc\x0cx\xf9\xf2mXf\xee\xf3\xf8%', - '\x97v_\xc3\xcdbO\xd1\xfa\x01v\x93,!\xff\xd1j\xdfC.' - ], - 'type': 'changesetexplicit' - } - ] - } - updating the branch cache - new changesets 3390ef850073:97765fc3cd62 - updating the branch cache - (sent 5 HTTP requests and * bytes; received * bytes in responses) (glob) - - $ 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=22; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=1; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=3; 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) - received frame(size=783; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - 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) - received frame(size=967; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - 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) - received frame(size=160; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - - $ rm clone-output - -#if reporevlogstore - $ find client-narrow-2/.hg/store -type f -name '*.i' | sort - client-narrow-2/.hg/store/00changelog.i - client-narrow-2/.hg/store/00manifest.i - client-narrow-2/.hg/store/data/dir0/d.i -#endif - ---stream will use rawfiledata to transfer changelog and manifestlog, then -fall through to get files data - -Output is flaky, save it in a file and check part independently - $ hg --debug clone --stream -U http://localhost:$HGPORT client-stream-0 > clone-output - - $ cat clone-output | grep -v "received frame" - using http://localhost:$HGPORT/ - sending capabilities command - sending 1 commands - sending command rawstorefiledata: { - 'files': [ - 'changelog', - 'manifestlog' - ] - } - updating the branch cache - query 1; heads - sending 2 commands - sending command heads: {} - sending command known: { - 'nodes': [ - '\x97v_\xc3\xcdbO\xd1\xfa\x01v\x93,!\xff\xd1j\xdfC.' - ] - } - searching for changes - all remote heads known locally - sending 1 commands - sending command changesetdata: { - 'fields': set([ - 'bookmarks', - 'parents', - 'phase', - 'revision' - ]), - 'revisions': [ - { - 'heads': [ - '\x97v_\xc3\xcdbO\xd1\xfa\x01v\x93,!\xff\xd1j\xdfC.' - ], - 'roots': [ - '\x97v_\xc3\xcdbO\xd1\xfa\x01v\x93,!\xff\xd1j\xdfC.' - ], - 'type': 'changesetdagrange' - } - ] - } - checking for updated bookmarks - sending 1 commands - sending command filesdata: { - 'fields': set([ - 'parents', - 'revision' - ]), - 'haveparents': True, - 'revisions': [ - { - 'nodes': [ - '3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:', - '\xb7\t8\x08\x92\xb1\x93\xc1\t\x1d:\x81\x7fp`R\xe3F\x82\x1b', - 'G\xfe\x01*\xb27\xa8\xc7\xfc\x0cx\xf9\xf2mXf\xee\xf3\xf8%', - '\x97v_\xc3\xcdbO\xd1\xfa\x01v\x93,!\xff\xd1j\xdfC.' - ], - 'type': 'changesetexplicit' - } - ] - } - updating the branch cache - (sent 5 HTTP requests and * bytes; received * bytes in responses) (glob) - - $ 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) (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) - received frame(size=22; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=2; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=3; 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) - received frame(size=13; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - 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) - received frame(size=1133; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - - $ rm clone-output - ---stream + --include/--exclude will only obtain some files - -Output is flaky, save it in a file and check part independently - $ hg --debug --config extensions.pullext=$TESTDIR/pullext.py clone --stream --include dir0/ -U http://localhost:$HGPORT client-stream-2 > clone-output - - $ cat clone-output | grep -v "received frame" - using http://localhost:$HGPORT/ - sending capabilities command - sending 1 commands - sending command rawstorefiledata: { - 'files': [ - 'changelog', - 'manifestlog' - ] - } - updating the branch cache - query 1; heads - sending 2 commands - sending command heads: {} - sending command known: { - 'nodes': [ - '\x97v_\xc3\xcdbO\xd1\xfa\x01v\x93,!\xff\xd1j\xdfC.' - ] - } - searching for changes - all remote heads known locally - sending 1 commands - sending command changesetdata: { - 'fields': set([ - 'bookmarks', - 'parents', - 'phase', - 'revision' - ]), - 'revisions': [ - { - 'heads': [ - '\x97v_\xc3\xcdbO\xd1\xfa\x01v\x93,!\xff\xd1j\xdfC.' - ], - 'roots': [ - '\x97v_\xc3\xcdbO\xd1\xfa\x01v\x93,!\xff\xd1j\xdfC.' - ], - 'type': 'changesetdagrange' - } - ] - } - checking for updated bookmarks - sending 1 commands - sending command filesdata: { - 'fields': set([ - 'parents', - 'revision' - ]), - 'haveparents': True, - 'pathfilter': { - 'include': [ - 'path:dir0' - ] - }, - 'revisions': [ - { - 'nodes': [ - '3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:', - '\xb7\t8\x08\x92\xb1\x93\xc1\t\x1d:\x81\x7fp`R\xe3F\x82\x1b', - 'G\xfe\x01*\xb27\xa8\xc7\xfc\x0cx\xf9\xf2mXf\xee\xf3\xf8%', - '\x97v_\xc3\xcdbO\xd1\xfa\x01v\x93,!\xff\xd1j\xdfC.' - ], - 'type': 'changesetexplicit' - } - ] - } - updating the branch cache - (sent 5 HTTP requests and * bytes; received * bytes in responses) (glob) - - $ 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) (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) - received frame(size=22; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=2; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=3; 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) - received frame(size=13; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - 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) - received frame(size=449; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - - $ rm clone-output - -#if reporevlogstore - $ find client-stream-2/.hg/store -type f -name '*.i' | sort - client-stream-2/.hg/store/00changelog.i - client-stream-2/.hg/store/00manifest.i - client-stream-2/.hg/store/data/dir0/c.i - client-stream-2/.hg/store/data/dir0/d.i -#endif - -Shallow clone doesn't work with revlogs - -Output is flaky, save it in a file and check part independently - $ hg --debug --config extensions.pullext=$TESTDIR/pullext.py clone --depth 1 -U http://localhost:$HGPORT client-shallow-revlogs > clone-output - transaction abort! - rollback completed - abort: revlog storage does not support missing parents write mode - [255] - - $ cat clone-output | grep -v "received frame" - using http://localhost:$HGPORT/ - sending capabilities command - query 1; heads - sending 2 commands - sending command heads: {} - sending command known: { - 'nodes': [] - } - sending 1 commands - sending command changesetdata: { - 'fields': set([ - 'bookmarks', - 'parents', - 'phase', - 'revision' - ]), - 'revisions': [ - { - 'heads': [ - '\x97v_\xc3\xcdbO\xd1\xfa\x01v\x93,!\xff\xd1j\xdfC.' - ], - 'roots': [], - 'type': 'changesetdagrange' - } - ] - } - add changeset 3390ef850073 - add changeset b709380892b1 - add changeset 47fe012ab237 - add changeset 97765fc3cd62 - checking for updated bookmarks - sending 1 commands - sending command manifestdata: { - 'fields': set([ - 'parents', - 'revision' - ]), - 'haveparents': True, - 'nodes': [ - '\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A', - '|2 \x1a\xa3\xa1R\xa9\xe6\xa9"+?\xa8\xd0\xe3\x0f\xc2V\xe8', - '\x8d\xd0W<\x7f\xaf\xe2\x04F\xcc\xea\xac\x05N\xea\xa4x\x91M\xdb', - '113\x85\xf2!\x8b\x08^\xb2Z\x821\x1e*\xdd\x0e\xeb\x8c3' - ], - 'tree': '' - } - sending 1 commands - sending command filesdata: { - 'fields': set([ - 'linknode', - 'parents', - 'revision' - ]), - 'haveparents': False, - 'revisions': [ - { - 'nodes': [ - '\x97v_\xc3\xcdbO\xd1\xfa\x01v\x93,!\xff\xd1j\xdfC.' - ], - 'type': 'changesetexplicit' - } - ] - } - (sent 5 HTTP requests and * bytes; received * bytes in responses) (glob) - - $ 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=22; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - received frame(size=11; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=1; request=3; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=3; 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) - received frame(size=783; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - 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) - received frame(size=967; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - 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) - received frame(size=1005; request=1; stream=2; streamflags=encoded; type=command-response; flags=continuation) - received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) - - $ rm clone-output - - $ killdaemons.py - -Repo with 2 DAG branches introducing same filenode, to test linknode adjustment - - $ hg init server-linknode - $ enablehttpv2 server-linknode - $ cd server-linknode - $ touch foo - $ hg -q commit -Am initial - $ echo foo > dupe-file - $ hg commit -Am 'dupe 1' - adding dupe-file - $ hg -q up -r 0 - $ echo foo > dupe-file - $ hg commit -Am 'dupe 2' - adding dupe-file - created new head - $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log - $ cat hg.pid > $DAEMON_PIDS - $ cd .. - -Perform an incremental pull of both heads and ensure linkrev is written out properly - - $ hg clone -r 96ee1d7354c4 http://localhost:$HGPORT client-linknode-1 - new changesets 96ee1d7354c4 - updating to branch default - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ cd client-linknode-1 - $ touch extra - $ hg commit -Am extra - adding extra - $ cd .. - - $ hg clone -r 96ee1d7354c4 http://localhost:$HGPORT client-linknode-2 - new changesets 96ee1d7354c4 - updating to branch default - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ cd client-linknode-2 - $ touch extra - $ hg commit -Am extra - adding extra - $ cd .. - - $ hg -R client-linknode-1 pull -r 1681c33f9f80 - pulling from http://localhost:$HGPORT/ - searching for changes - new changesets 1681c33f9f80 - (run 'hg update' to get a working copy) - -#if reporevlogstore - $ hg -R client-linknode-1 debugrevlogindex dupe-file - rev linkrev nodeid p1 p2 - 0 2 2ed2a3912a0b 000000000000 000000000000 -#endif - - $ hg -R client-linknode-2 pull -r 639c8990d6a5 - pulling from http://localhost:$HGPORT/ - searching for changes - new changesets 639c8990d6a5 - (run 'hg update' to get a working copy) - -#if reporevlogstore - $ hg -R client-linknode-2 debugrevlogindex dupe-file - rev linkrev nodeid p1 p2 - 0 2 2ed2a3912a0b 000000000000 000000000000 -#endif diff --git a/tests/badserverext.py b/tests/testlib/badserverext.py rename from tests/badserverext.py rename to tests/testlib/badserverext.py --- a/tests/badserverext.py +++ b/tests/testlib/badserverext.py @@ -14,23 +14,39 @@ events occur. Various config options in the [badserver] section influence behavior: -closebeforeaccept +close-before-accept If true, close() the server socket when a new connection arrives before accept() is called. The server will then exit. -closeafteraccept +close-after-accept If true, the server will close() the client socket immediately after accept(). -closeafterrecvbytes +close-after-recv-bytes If defined, close the client socket after receiving this many bytes. + (The value is a list, multiple values can use used to close a series of requests + request) -closeaftersendbytes +close-after-recv-patterns + If defined, the `close-after-recv-bytes` values only start counting after the + `read` operation that encountered the defined patterns. + (The value is a list, multiple values can use used to close a series of requests + request) + +close-after-send-bytes If defined, close the client socket after sending this many bytes. + (The value is a list, multiple values can use used to close a series of requests + request) + +close-after-send-patterns + If defined, close the client socket after the configured regexp is seen. + (The value is a list, multiple values can use used to close a series of requests + request) """ from __future__ import absolute_import +import re import socket from mercurial import ( @@ -45,44 +61,212 @@ configitem = registrar.configitem(config configitem( b'badserver', - b'closeafteraccept', + b'close-after-accept', default=False, ) configitem( b'badserver', - b'closeafterrecvbytes', + b'close-after-recv-bytes', default=b'0', ) configitem( b'badserver', - b'closeaftersendbytes', + b'close-after-recv-patterns', + default=b'', +) +configitem( + b'badserver', + b'close-after-send-bytes', default=b'0', ) configitem( b'badserver', - b'closebeforeaccept', + b'close-after-send-patterns', + default=b'', +) +configitem( + b'badserver', + b'close-before-accept', default=False, ) + +class ConditionTracker(object): + def __init__( + self, + close_after_recv_bytes, + close_after_recv_patterns, + close_after_send_bytes, + close_after_send_patterns, + ): + self._all_close_after_recv_bytes = close_after_recv_bytes + self._all_close_after_recv_patterns = close_after_recv_patterns + self._all_close_after_send_bytes = close_after_send_bytes + self._all_close_after_send_patterns = close_after_send_patterns + + self.target_recv_bytes = None + self.remaining_recv_bytes = None + self.recv_patterns = None + self.recv_data = b'' + self.target_send_bytes = None + self.remaining_send_bytes = None + self.send_pattern = None + self.send_data = b'' + + def start_next_request(self): + """move to the next set of close condition""" + if self._all_close_after_recv_bytes: + self.target_recv_bytes = self._all_close_after_recv_bytes.pop(0) + self.remaining_recv_bytes = self.target_recv_bytes + else: + self.target_recv_bytes = None + self.remaining_recv_bytes = None + + self.recv_data = b'' + if self._all_close_after_recv_patterns: + self.recv_pattern = self._all_close_after_recv_patterns.pop(0) + else: + self.recv_pattern = None + + if self._all_close_after_send_bytes: + self.target_send_bytes = self._all_close_after_send_bytes.pop(0) + self.remaining_send_bytes = self.target_send_bytes + else: + self.target_send_bytes = None + self.remaining_send_bytes = None + + self.send_data = b'' + if self._all_close_after_send_patterns: + self.send_pattern = self._all_close_after_send_patterns.pop(0) + else: + self.send_pattern = None + + def might_close(self): + """True, if any processing will be needed""" + if self.remaining_recv_bytes is not None: + return True + if self.recv_pattern is not None: + return True + if self.remaining_send_bytes is not None: + return True + if self.send_pattern is not None: + return True + return False + + def forward_write(self, obj, method, data, *args, **kwargs): + """call an underlying write function until condition are met + + When the condition are met the socket is closed + """ + remaining = self.remaining_send_bytes + pattern = self.send_pattern + + orig = object.__getattribute__(obj, '_orig') + bmethod = method.encode('ascii') + func = getattr(orig, method) + + if pattern: + self.send_data += data + pieces = pattern.split(self.send_data, maxsplit=1) + if len(pieces) > 1: + dropped = len(pieces[-1]) + remaining = len(data) - dropped + + if remaining: + remaining = max(0, remaining) + + if not remaining: + newdata = data + else: + if remaining < len(data): + newdata = data[0:remaining] + else: + newdata = data + remaining -= len(newdata) + self.remaining_send_bytes = remaining + + result = func(newdata, *args, **kwargs) + + if remaining is None: + obj._writelog(b'%s(%d) -> %s' % (bmethod, len(data), data)) + else: + msg = b'%s(%d from %d) -> (%d) %s' + msg %= (bmethod, len(newdata), len(data), remaining, newdata) + obj._writelog(msg) + + if remaining is not None and remaining <= 0: + obj._writelog(b'write limit reached; closing socket') + object.__getattribute__(obj, '_cond_close')() + raise Exception('connection closed after sending N bytes') + + return result + + def forward_read(self, obj, method, size=-1): + """call an underlying read function until condition are met + + When the condition are met the socket is closed + """ + remaining = self.remaining_recv_bytes + pattern = self.recv_pattern + + orig = object.__getattribute__(obj, '_orig') + bmethod = method.encode('ascii') + func = getattr(orig, method) + + requested_size = size + actual_size = size + + if pattern is None and remaining: + if size < 0: + actual_size = remaining + else: + actual_size = min(remaining, requested_size) + + result = func(actual_size) + + if pattern is None and remaining: + remaining -= len(result) + self.remaining_recv_bytes = remaining + + if requested_size == 65537: + requested_repr = b'~' + else: + requested_repr = b'%d' % requested_size + if requested_size == actual_size: + msg = b'%s(%s) -> (%d) %s' + msg %= (bmethod, requested_repr, len(result), result) + else: + msg = b'%s(%d from %s) -> (%d) %s' + msg %= (bmethod, actual_size, requested_repr, len(result), result) + obj._writelog(msg) + + if pattern is not None: + self.recv_data += result + if pattern.search(self.recv_data): + # start counting bytes starting with the next read + self.recv_pattern = None + + if remaining is not None and remaining <= 0: + obj._writelog(b'read limit reached; closing socket') + obj._cond_close() + + # This is the easiest way to abort the current request. + raise Exception('connection closed after receiving N bytes') + + return result + + # We can't adjust __class__ on a socket instance. So we define a proxy type. class socketproxy(object): - __slots__ = ( - '_orig', - '_logfp', - '_closeafterrecvbytes', - '_closeaftersendbytes', - ) + __slots__ = ('_orig', '_logfp', '_cond') - def __init__( - self, obj, logfp, closeafterrecvbytes=0, closeaftersendbytes=0 - ): + def __init__(self, obj, logfp, condition_tracked): object.__setattr__(self, '_orig', obj) object.__setattr__(self, '_logfp', logfp) - object.__setattr__(self, '_closeafterrecvbytes', closeafterrecvbytes) - object.__setattr__(self, '_closeaftersendbytes', closeaftersendbytes) + object.__setattr__(self, '_cond', condition_tracked) def __getattribute__(self, name): - if name in ('makefile', 'sendall', '_writelog'): + if name in ('makefile', 'sendall', '_writelog', '_cond_close'): return object.__getattribute__(self, name) return getattr(object.__getattribute__(self, '_orig'), name) @@ -104,73 +288,36 @@ class socketproxy(object): f = object.__getattribute__(self, '_orig').makefile(mode, bufsize) logfp = object.__getattribute__(self, '_logfp') - closeafterrecvbytes = object.__getattribute__( - self, '_closeafterrecvbytes' - ) - closeaftersendbytes = object.__getattribute__( - self, '_closeaftersendbytes' - ) + cond = object.__getattribute__(self, '_cond') - return fileobjectproxy( - f, - logfp, - closeafterrecvbytes=closeafterrecvbytes, - closeaftersendbytes=closeaftersendbytes, - ) + return fileobjectproxy(f, logfp, cond) def sendall(self, data, flags=0): - remaining = object.__getattribute__(self, '_closeaftersendbytes') - - # No read limit. Call original function. - if not remaining: - result = object.__getattribute__(self, '_orig').sendall(data, flags) - self._writelog(b'sendall(%d) -> %s' % (len(data), data)) - return result - - if len(data) > remaining: - newdata = data[0:remaining] - else: - newdata = data - - remaining -= len(newdata) + cond = object.__getattribute__(self, '_cond') + return cond.forward_write(self, 'sendall', data, flags) - result = object.__getattribute__(self, '_orig').sendall(newdata, flags) - - self._writelog( - b'sendall(%d from %d) -> (%d) %s' - % (len(newdata), len(data), remaining, newdata) - ) - - object.__setattr__(self, '_closeaftersendbytes', remaining) - - if remaining <= 0: - self._writelog(b'write limit reached; closing socket') - object.__getattribute__(self, '_orig').shutdown(socket.SHUT_RDWR) - - raise Exception('connection closed after sending N bytes') - - return result + def _cond_close(self): + object.__getattribute__(self, '_orig').shutdown(socket.SHUT_RDWR) # We can't adjust __class__ on socket._fileobject, so define a proxy. class fileobjectproxy(object): - __slots__ = ( - '_orig', - '_logfp', - '_closeafterrecvbytes', - '_closeaftersendbytes', - ) + __slots__ = ('_orig', '_logfp', '_cond') - def __init__( - self, obj, logfp, closeafterrecvbytes=0, closeaftersendbytes=0 - ): + def __init__(self, obj, logfp, condition_tracked): object.__setattr__(self, '_orig', obj) object.__setattr__(self, '_logfp', logfp) - object.__setattr__(self, '_closeafterrecvbytes', closeafterrecvbytes) - object.__setattr__(self, '_closeaftersendbytes', closeaftersendbytes) + object.__setattr__(self, '_cond', condition_tracked) def __getattribute__(self, name): - if name in ('_close', 'read', 'readline', 'write', '_writelog'): + if name in ( + '_close', + 'read', + 'readline', + 'write', + '_writelog', + '_cond_close', + ): return object.__getattribute__(self, name) return getattr(object.__getattribute__(self, '_orig'), name) @@ -202,111 +349,36 @@ class fileobjectproxy(object): self._sock.shutdown(socket.SHUT_RDWR) def read(self, size=-1): - remaining = object.__getattribute__(self, '_closeafterrecvbytes') - - # No read limit. Call original function. - if not remaining: - result = object.__getattribute__(self, '_orig').read(size) - self._writelog( - b'read(%d) -> (%d) (%s) %s' % (size, len(result), result) - ) - return result - - origsize = size - - if size < 0: - size = remaining - else: - size = min(remaining, size) - - result = object.__getattribute__(self, '_orig').read(size) - remaining -= len(result) - - self._writelog( - b'read(%d from %d) -> (%d) %s' - % (size, origsize, len(result), result) - ) - - object.__setattr__(self, '_closeafterrecvbytes', remaining) - - if remaining <= 0: - self._writelog(b'read limit reached, closing socket') - self._close() - - # This is the easiest way to abort the current request. - raise Exception('connection closed after receiving N bytes') - - return result + cond = object.__getattribute__(self, '_cond') + return cond.forward_read(self, 'read', size) def readline(self, size=-1): - remaining = object.__getattribute__(self, '_closeafterrecvbytes') - - # No read limit. Call original function. - if not remaining: - result = object.__getattribute__(self, '_orig').readline(size) - self._writelog( - b'readline(%d) -> (%d) %s' % (size, len(result), result) - ) - return result - - origsize = size - - if size < 0: - size = remaining - else: - size = min(remaining, size) - - result = object.__getattribute__(self, '_orig').readline(size) - remaining -= len(result) - - self._writelog( - b'readline(%d from %d) -> (%d) %s' - % (size, origsize, len(result), result) - ) - - object.__setattr__(self, '_closeafterrecvbytes', remaining) - - if remaining <= 0: - self._writelog(b'read limit reached; closing socket') - self._close() - - # This is the easiest way to abort the current request. - raise Exception('connection closed after receiving N bytes') - - return result + cond = object.__getattribute__(self, '_cond') + return cond.forward_read(self, 'readline', size) def write(self, data): - remaining = object.__getattribute__(self, '_closeaftersendbytes') + cond = object.__getattribute__(self, '_cond') + return cond.forward_write(self, 'write', data) - # No byte limit on this operation. Call original function. - if not remaining: - self._writelog(b'write(%d) -> %s' % (len(data), data)) - result = object.__getattribute__(self, '_orig').write(data) - return result + def _cond_close(self): + self._close() - if len(data) > remaining: - newdata = data[0:remaining] - else: - newdata = data - - remaining -= len(newdata) - self._writelog( - b'write(%d from %d) -> (%d) %s' - % (len(newdata), len(data), remaining, newdata) - ) +def process_bytes_config(value): + parts = value.split(b',') + integers = [int(v) for v in parts if v] + return [v if v else None for v in integers] - result = object.__getattribute__(self, '_orig').write(newdata) - object.__setattr__(self, '_closeaftersendbytes', remaining) - - if remaining <= 0: - self._writelog(b'write limit reached; closing socket') - self._close() - - raise Exception('connection closed after sending N bytes') - - return result +def process_pattern_config(value): + patterns = [] + for p in value.split(b','): + if not p: + p = None + else: + p = re.compile(p, re.DOTALL | re.MULTILINE) + patterns.append(p) + return patterns def extsetup(ui): @@ -317,12 +389,28 @@ def extsetup(ui): self._ui = ui super(badserver, self).__init__(ui, *args, **kwargs) - recvbytes = self._ui.config(b'badserver', b'closeafterrecvbytes') - recvbytes = recvbytes.split(b',') - self.closeafterrecvbytes = [int(v) for v in recvbytes if v] - sendbytes = self._ui.config(b'badserver', b'closeaftersendbytes') - sendbytes = sendbytes.split(b',') - self.closeaftersendbytes = [int(v) for v in sendbytes if v] + all_recv_bytes = self._ui.config( + b'badserver', b'close-after-recv-bytes' + ) + all_recv_bytes = process_bytes_config(all_recv_bytes) + all_recv_pattern = self._ui.config( + b'badserver', b'close-after-recv-patterns' + ) + all_recv_pattern = process_pattern_config(all_recv_pattern) + all_send_bytes = self._ui.config( + b'badserver', b'close-after-send-bytes' + ) + all_send_bytes = process_bytes_config(all_send_bytes) + all_send_patterns = self._ui.config( + b'badserver', b'close-after-send-patterns' + ) + all_send_patterns = process_pattern_config(all_send_patterns) + self._cond = ConditionTracker( + all_recv_bytes, + all_recv_pattern, + all_send_bytes, + all_send_patterns, + ) # Need to inherit object so super() works. class badrequesthandler(self.RequestHandlerClass, object): @@ -341,7 +429,7 @@ def extsetup(ui): # Called to accept() a pending socket. def get_request(self): - if self._ui.configbool(b'badserver', b'closebeforeaccept'): + if self._ui.configbool(b'badserver', b'close-before-accept'): self.socket.close() # Tells the server to stop processing more requests. @@ -350,7 +438,7 @@ def extsetup(ui): # Simulate failure to stop processing this request. raise socket.error('close before accept') - if self._ui.configbool(b'badserver', b'closeafteraccept'): + if self._ui.configbool(b'badserver', b'close-after-accept'): request, client_address = super(badserver, self).get_request() request.close() raise socket.error('close after accept') @@ -362,21 +450,11 @@ def extsetup(ui): # is a hgweb.server._httprequesthandler. def process_request(self, socket, address): # Wrap socket in a proxy if we need to count bytes. - if self.closeafterrecvbytes: - closeafterrecvbytes = self.closeafterrecvbytes.pop(0) - else: - closeafterrecvbytes = 0 - if self.closeaftersendbytes: - closeaftersendbytes = self.closeaftersendbytes.pop(0) - else: - closeaftersendbytes = 0 + self._cond.start_next_request() - if closeafterrecvbytes or closeaftersendbytes: + if self._cond.might_close(): socket = socketproxy( - socket, - self.errorlog, - closeafterrecvbytes=closeafterrecvbytes, - closeaftersendbytes=closeaftersendbytes, + socket, self.errorlog, condition_tracked=self._cond ) return super(badserver, self).process_request(socket, address) diff --git a/tests/testlib/crash_transaction_late.py b/tests/testlib/crash_transaction_late.py --- a/tests/testlib/crash_transaction_late.py +++ b/tests/testlib/crash_transaction_late.py @@ -9,7 +9,6 @@ from __future__ import absolute_import from mercurial import ( error, - transaction, ) @@ -18,14 +17,15 @@ def abort(fp): def reposetup(ui, repo): - - transaction.postfinalizegenerators.add(b'late-abort') - class LateAbortRepo(repo.__class__): def transaction(self, *args, **kwargs): tr = super(LateAbortRepo, self).transaction(*args, **kwargs) tr.addfilegenerator( - b'late-abort', [b'late-abort'], abort, order=9999999 + b'late-abort', + [b'late-abort'], + abort, + order=9999999, + post_finalize=True, ) return tr diff --git a/tests/testlib/push-checkheads-util.sh b/tests/testlib/push-checkheads-util.sh --- a/tests/testlib/push-checkheads-util.sh +++ b/tests/testlib/push-checkheads-util.sh @@ -41,4 +41,10 @@ setuprepos() { mkcommit A0 cd .. hg clone server client + + if [ "$1" = "single-head" ]; then + echo >> "server/.hg/hgrc" "[experimental]" + echo >> "server/.hg/hgrc" "# enforce a single name per branch" + echo >> "server/.hg/hgrc" "single-head-per-branch = yes" + fi } diff --git a/tests/testlib/stream_clone_setup.sh b/tests/testlib/stream_clone_setup.sh new file mode 100644 --- /dev/null +++ b/tests/testlib/stream_clone_setup.sh @@ -0,0 +1,97 @@ +# setup some files and commit for a good stream clone testing. + +touch foo +hg -q commit -A -m initial + +python3 << EOF +for i in range(1024): + with open(str(i), 'wb') as fh: + fh.write(b"%d" % i) and None +EOF +hg -q commit -A -m 'add a lot of files' + +# (the status call is to check for issue5130) + +hg st + +# add files with "tricky" name: + +echo foo > 00changelog.i +echo foo > 00changelog.d +echo foo > 00changelog.n +echo foo > 00changelog-ab349180a0405010.nd +echo foo > 00manifest.i +echo foo > 00manifest.d +echo foo > foo.i +echo foo > foo.d +echo foo > foo.n +echo foo > undo.py +echo foo > undo.i +echo foo > undo.d +echo foo > undo.n +echo foo > undo.foo.i +echo foo > undo.foo.d +echo foo > undo.foo.n +echo foo > undo.babar +mkdir savanah +echo foo > savanah/foo.i +echo foo > savanah/foo.d +echo foo > savanah/foo.n +echo foo > savanah/undo.py +echo foo > savanah/undo.i +echo foo > savanah/undo.d +echo foo > savanah/undo.n +echo foo > savanah/undo.foo.i +echo foo > savanah/undo.foo.d +echo foo > savanah/undo.foo.n +echo foo > savanah/undo.babar +mkdir data +echo foo > data/foo.i +echo foo > data/foo.d +echo foo > data/foo.n +echo foo > data/undo.py +echo foo > data/undo.i +echo foo > data/undo.d +echo foo > data/undo.n +echo foo > data/undo.foo.i +echo foo > data/undo.foo.d +echo foo > data/undo.foo.n +echo foo > data/undo.babar +mkdir meta +echo foo > meta/foo.i +echo foo > meta/foo.d +echo foo > meta/foo.n +echo foo > meta/undo.py +echo foo > meta/undo.i +echo foo > meta/undo.d +echo foo > meta/undo.n +echo foo > meta/undo.foo.i +echo foo > meta/undo.foo.d +echo foo > meta/undo.foo.n +echo foo > meta/undo.babar +mkdir store +echo foo > store/foo.i +echo foo > store/foo.d +echo foo > store/foo.n +echo foo > store/undo.py +echo foo > store/undo.i +echo foo > store/undo.d +echo foo > store/undo.n +echo foo > store/undo.foo.i +echo foo > store/undo.foo.d +echo foo > store/undo.foo.n +echo foo > store/undo.babar + +# Name with special characters + +echo foo > store/CélesteVille_is_a_Capital_City + +# name causing issue6581 + +mkdir -p container/isam-build-centos7/ +touch container/isam-build-centos7/bazel-coverage-generator-sandboxfs-compatibility-0758e3e4f6057904d44399bd666faba9e7f40686.patch + +# Add all that + +hg add . +hg ci -m 'add files with "tricky" name' diff --git a/tests/wireprotohelpers.sh b/tests/wireprotohelpers.sh --- a/tests/wireprotohelpers.sh +++ b/tests/wireprotohelpers.sh @@ -1,44 +1,23 @@ -HTTPV2=exp-http-v2-0003 MEDIATYPE=application/mercurial-exp-framing-0006 sendhttpraw() { hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/ } -sendhttpv2peer() { - hg --config experimental.httppeer.v2-encoder-order=identity debugwireproto --nologhandshake --peer http2 http://$LOCALIP:$HGPORT/ -} - -sendhttpv2peerverbose() { - hg --config experimental.httppeer.v2-encoder-order=identity --verbose debugwireproto --nologhandshake --peer http2 http://$LOCALIP:$HGPORT/ -} - -sendhttpv2peerhandshake() { - hg --config experimental.httppeer.v2-encoder-order=identity --verbose debugwireproto --peer http2 http://$LOCALIP:$HGPORT/ -} - cat > dummycommands.py << EOF from mercurial import ( wireprototypes, wireprotov1server, - wireprotov2server, ) @wireprotov1server.wireprotocommand(b'customreadonly', permission=b'pull') def customreadonlyv1(repo, proto): return wireprototypes.bytesresponse(b'customreadonly bytes response') -@wireprotov2server.wireprotocommand(b'customreadonly', permission=b'pull') -def customreadonlyv2(repo, proto): - yield b'customreadonly bytes response' - @wireprotov1server.wireprotocommand(b'customreadwrite', permission=b'push') def customreadwrite(repo, proto): return wireprototypes.bytesresponse(b'customreadwrite bytes response') -@wireprotov2server.wireprotocommand(b'customreadwrite', permission=b'push') -def customreadwritev2(repo, proto): - yield b'customreadwrite bytes response' EOF cat >> $HGRCPATH << EOF @@ -53,20 +32,3 @@ dummycommands = $TESTTMP/dummycommands.p EOF } -enablehttpv2() { - cat >> $1/.hg/hgrc << EOF -[experimental] -web.apiserver = true -web.api.http-v2 = true -EOF -} - -enablehttpv2client() { - cat >> $HGRCPATH << EOF -[experimental] -httppeer.advertise-v2 = true -# So tests are in plain text. Also, zstd isn't available in all installs, -# which would make tests non-deterministic. -httppeer.v2-encoder-order = identity -EOF -} diff --git a/tests/wireprotosimplecache.py b/tests/wireprotosimplecache.py deleted file mode 100644 --- a/tests/wireprotosimplecache.py +++ /dev/null @@ -1,220 +0,0 @@ -# wireprotosimplecache.py - Extension providing in-memory wire protocol cache -# -# Copyright 2018 Gregory Szorc -# -# 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 import ( - extensions, - registrar, - util, - wireprotoserver, - wireprototypes, - wireprotov2server, -) -from mercurial.interfaces import ( - repository, - util as interfaceutil, -) -from mercurial.utils import stringutil - -CACHE = None - -configtable = {} -configitem = registrar.configitem(configtable) - -configitem(b'simplecache', b'cacheapi', default=False) -configitem(b'simplecache', b'cacheobjects', default=False) -configitem(b'simplecache', b'redirectsfile', default=None) - -# API handler that makes cached keys available. -def handlecacherequest(rctx, req, res, checkperm, urlparts): - if rctx.repo.ui.configbool(b'simplecache', b'cacheobjects'): - res.status = b'500 Internal Server Error' - res.setbodybytes(b'cacheobjects not supported for api server') - return - - if not urlparts: - res.status = b'200 OK' - res.headers[b'Content-Type'] = b'text/plain' - res.setbodybytes(b'simple cache server') - return - - key = b'/'.join(urlparts) - - if key not in CACHE: - res.status = b'404 Not Found' - res.headers[b'Content-Type'] = b'text/plain' - res.setbodybytes(b'key not found in cache') - return - - res.status = b'200 OK' - res.headers[b'Content-Type'] = b'application/mercurial-cbor' - res.setbodybytes(CACHE[key]) - - -def cachedescriptor(req, repo): - return {} - - -wireprotoserver.API_HANDLERS[b'simplecache'] = { - b'config': (b'simplecache', b'cacheapi'), - b'handler': handlecacherequest, - b'apidescriptor': cachedescriptor, -} - - -@interfaceutil.implementer(repository.iwireprotocolcommandcacher) -class memorycacher(object): - def __init__( - self, ui, command, encodefn, redirecttargets, redirecthashes, req - ): - self.ui = ui - self.encodefn = encodefn - self.redirecttargets = redirecttargets - self.redirecthashes = redirecthashes - self.req = req - self.key = None - self.cacheobjects = ui.configbool(b'simplecache', b'cacheobjects') - self.cacheapi = ui.configbool(b'simplecache', b'cacheapi') - self.buffered = [] - - ui.log(b'simplecache', b'cacher constructed for %s\n', command) - - def __enter__(self): - return self - - def __exit__(self, exctype, excvalue, exctb): - if exctype: - self.ui.log(b'simplecache', b'cacher exiting due to error\n') - - def adjustcachekeystate(self, state): - # Needed in order to make tests deterministic. Don't copy this - # pattern for production caches! - del state[b'repo'] - - def setcachekey(self, key): - self.key = key - return True - - def lookup(self): - if self.key not in CACHE: - self.ui.log(b'simplecache', b'cache miss for %s\n', self.key) - return None - - entry = CACHE[self.key] - self.ui.log(b'simplecache', b'cache hit for %s\n', self.key) - - redirectable = True - - if not self.cacheapi: - redirectable = False - elif not self.redirecttargets: - redirectable = False - else: - clienttargets = set(self.redirecttargets) - ourtargets = {t[b'name'] for t in loadredirecttargets(self.ui)} - - # We only ever redirect to a single target (for now). So we don't - # need to store which target matched. - if not clienttargets & ourtargets: - redirectable = False - - if redirectable: - paths = self.req.dispatchparts[:-3] - paths.append(b'simplecache') - paths.append(self.key) - - url = b'%s/%s' % (self.req.baseurl, b'/'.join(paths)) - - # url = b'http://example.com/%s' % self.key - self.ui.log( - b'simplecache', - b'sending content redirect for %s to ' b'%s\n', - self.key, - url, - ) - response = wireprototypes.alternatelocationresponse( - url=url, mediatype=b'application/mercurial-cbor' - ) - - return {b'objs': [response]} - - if self.cacheobjects: - return { - b'objs': entry, - } - else: - return { - b'objs': [wireprototypes.encodedresponse(entry)], - } - - def onobject(self, obj): - if self.cacheobjects: - self.buffered.append(obj) - else: - self.buffered.extend(self.encodefn(obj)) - - yield obj - - def onfinished(self): - self.ui.log(b'simplecache', b'storing cache entry for %s\n', self.key) - if self.cacheobjects: - CACHE[self.key] = self.buffered - else: - CACHE[self.key] = b''.join(self.buffered) - - return [] - - -def makeresponsecacher( - orig, - repo, - proto, - command, - args, - objencoderfn, - redirecttargets, - redirecthashes, -): - return memorycacher( - repo.ui, - command, - objencoderfn, - redirecttargets, - redirecthashes, - proto._req, - ) - - -def loadredirecttargets(ui): - path = ui.config(b'simplecache', b'redirectsfile') - if not path: - return [] - - with open(path, 'rb') as fh: - s = fh.read() - - return stringutil.evalpythonliteral(s) - - -def getadvertisedredirecttargets(orig, repo, proto): - return loadredirecttargets(repo.ui) - - -def extsetup(ui): - global CACHE - - CACHE = util.lrucachedict(10000) - - extensions.wrapfunction( - wireprotov2server, b'makeresponsecacher', makeresponsecacher - ) - extensions.wrapfunction( - wireprotov2server, - b'getadvertisedredirecttargets', - getadvertisedredirecttargets, - )