Show More
The requested changes are too big and content was truncated. Show full diff
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
@@ -158,7 +158,7 b' def build_all_windows_packages(' | |||
|
158 | 158 | |
|
159 | 159 | windows.synchronize_hg(SOURCE_ROOT, revision, instance) |
|
160 | 160 | |
|
161 | for py_version in ("2.7", "3.7", "3.8", "3.9"): | |
|
161 | for py_version in ("2.7", "3.7", "3.8", "3.9", "3.10"): | |
|
162 | 162 | for arch in ("x86", "x64"): |
|
163 | 163 | windows.purge_hg(winrm_client) |
|
164 | 164 | windows.build_wheel( |
@@ -377,7 +377,7 b' def get_parser():' | |||
|
377 | 377 | sp.add_argument( |
|
378 | 378 | '--python-version', |
|
379 | 379 | help='Python version to build for', |
|
380 | choices={'2.7', '3.7', '3.8', '3.9'}, | |
|
380 | choices={'2.7', '3.7', '3.8', '3.9', '3.10'}, | |
|
381 | 381 | nargs='*', |
|
382 | 382 | default=['3.8'], |
|
383 | 383 | ) |
@@ -501,7 +501,7 b' def get_parser():' | |||
|
501 | 501 | sp.add_argument( |
|
502 | 502 | '--python-version', |
|
503 | 503 | help='Python version to use', |
|
504 | choices={'2.7', '3.5', '3.6', '3.7', '3.8', '3.9'}, | |
|
504 | choices={'2.7', '3.5', '3.6', '3.7', '3.8', '3.9', '3.10'}, | |
|
505 | 505 | default='2.7', |
|
506 | 506 | ) |
|
507 | 507 | sp.add_argument( |
@@ -129,6 +129,8 b" WHEEL_FILENAME_PYTHON38_X86 = 'mercurial" | |||
|
129 | 129 | WHEEL_FILENAME_PYTHON38_X64 = 'mercurial-{version}-cp38-cp38-win_amd64.whl' |
|
130 | 130 | WHEEL_FILENAME_PYTHON39_X86 = 'mercurial-{version}-cp39-cp39-win32.whl' |
|
131 | 131 | WHEEL_FILENAME_PYTHON39_X64 = 'mercurial-{version}-cp39-cp39-win_amd64.whl' |
|
132 | WHEEL_FILENAME_PYTHON310_X86 = 'mercurial-{version}-cp310-cp310-win32.whl' | |
|
133 | WHEEL_FILENAME_PYTHON310_X64 = 'mercurial-{version}-cp310-cp310-win_amd64.whl' | |
|
132 | 134 | |
|
133 | 135 | EXE_FILENAME_PYTHON2_X86 = 'Mercurial-{version}-x86-python2.exe' |
|
134 | 136 | EXE_FILENAME_PYTHON2_X64 = 'Mercurial-{version}-x64-python2.exe' |
@@ -480,6 +482,8 b' def resolve_wheel_artifacts(dist_path: p' | |||
|
480 | 482 | dist_path / WHEEL_FILENAME_PYTHON38_X64.format(version=version), |
|
481 | 483 | dist_path / WHEEL_FILENAME_PYTHON39_X86.format(version=version), |
|
482 | 484 | dist_path / WHEEL_FILENAME_PYTHON39_X64.format(version=version), |
|
485 | dist_path / WHEEL_FILENAME_PYTHON310_X86.format(version=version), | |
|
486 | dist_path / WHEEL_FILENAME_PYTHON310_X64.format(version=version), | |
|
483 | 487 | ) |
|
484 | 488 | |
|
485 | 489 | |
@@ -493,6 +497,8 b' def resolve_all_artifacts(dist_path: pat' | |||
|
493 | 497 | dist_path / WHEEL_FILENAME_PYTHON38_X64.format(version=version), |
|
494 | 498 | dist_path / WHEEL_FILENAME_PYTHON39_X86.format(version=version), |
|
495 | 499 | dist_path / WHEEL_FILENAME_PYTHON39_X64.format(version=version), |
|
500 | dist_path / WHEEL_FILENAME_PYTHON310_X86.format(version=version), | |
|
501 | dist_path / WHEEL_FILENAME_PYTHON310_X64.format(version=version), | |
|
496 | 502 | dist_path / EXE_FILENAME_PYTHON2_X86.format(version=version), |
|
497 | 503 | dist_path / EXE_FILENAME_PYTHON2_X64.format(version=version), |
|
498 | 504 | dist_path / EXE_FILENAME_PYTHON3_X86.format(version=version), |
@@ -56,6 +56,11 b' rust-cargo-test-py3:' | |||
|
56 | 56 | |
|
57 | 57 | phabricator-refresh: |
|
58 | 58 | stage: phabricator |
|
59 | rules: | |
|
60 | - if: '"$PHABRICATOR_TOKEN" != "NO-PHAB"' | |
|
61 | when: on_success | |
|
62 | - if: '"$PHABRICATOR_TOKEN" == "NO-PHAB"' | |
|
63 | when: never | |
|
59 | 64 | variables: |
|
60 | 65 | DEFAULT_COMMENT: ":white_check_mark: refresh by Heptapod after a successful CI run (:octopus: :green_heart:)" |
|
61 | 66 | 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}" |
@@ -29,10 +29,15 b'' | |||
|
29 | 29 | $PYTHON38_x64_URL = "https://www.python.org/ftp/python/3.8.10/python-3.8.10-amd64.exe" |
|
30 | 30 | $PYTHON38_x64_SHA256 = "7628244cb53408b50639d2c1287c659f4e29d3dfdb9084b11aed5870c0c6a48a" |
|
31 | 31 | |
|
32 |
$PYTHON39_x86_URL = "https://www.python.org/ftp/python/3.9. |
|
|
33 | $PYTHON39_x86_SHA256 = "505129081a839b699a6ab9064b441ad922ef03767b5dd4241fd0c2166baf64de" | |
|
34 |
$PYTHON39_ |
|
|
35 | $PYTHON39_x64_SHA256 = "84d5243088ba00c11e51905c704dbe041040dfff044f4e1ce5476844ee2e6eac" | |
|
32 | $PYTHON39_x86_URL = "https://www.python.org/ftp/python/3.9.9/python-3.9.9.exe" | |
|
33 | $PYTHON39_x86_SHA256 = "6646a5683adf14d35e8c53aab946895bc0f0b825f7acac3a62cc85ee7d0dc71a" | |
|
34 | $PYTHON39_X64_URL = "https://www.python.org/ftp/python/3.9.9/python-3.9.9-amd64.exe" | |
|
35 | $PYTHON39_x64_SHA256 = "137d59e5c0b01a8f1bdcba08344402ae658c81c6bf03b6602bd8b4e951ad0714" | |
|
36 | ||
|
37 | $PYTHON310_x86_URL = "https://www.python.org/ftp/python/3.10.0/python-3.10.0.exe" | |
|
38 | $PYTHON310_x86_SHA256 = "ea896eeefb1db9e12fb89ec77a6e28c9fe52b4a162a34c85d9688be2ec2392e8" | |
|
39 | $PYTHON310_X64_URL = "https://www.python.org/ftp/python/3.10.0/python-3.10.0-amd64.exe" | |
|
40 | $PYTHON310_x64_SHA256 = "cb580eb7dc55f9198e650f016645023e8b2224cf7d033857d12880b46c5c94ef" | |
|
36 | 41 | |
|
37 | 42 | # PIP 19.2.3. |
|
38 | 43 | $PIP_URL = "https://github.com/pypa/get-pip/raw/309a56c5fd94bd1134053a541cb4657a4e47e09d/get-pip.py" |
@@ -132,6 +137,8 b' function Install-Dependencies($prefix) {' | |||
|
132 | 137 | Secure-Download $PYTHON38_x64_URL ${prefix}\assets\python38-x64.exe $PYTHON38_x64_SHA256 |
|
133 | 138 | Secure-Download $PYTHON39_x86_URL ${prefix}\assets\python39-x86.exe $PYTHON39_x86_SHA256 |
|
134 | 139 | Secure-Download $PYTHON39_x64_URL ${prefix}\assets\python39-x64.exe $PYTHON39_x64_SHA256 |
|
140 | Secure-Download $PYTHON310_x86_URL ${prefix}\assets\python310-x86.exe $PYTHON310_x86_SHA256 | |
|
141 | Secure-Download $PYTHON310_x64_URL ${prefix}\assets\python310-x64.exe $PYTHON310_x64_SHA256 | |
|
135 | 142 | Secure-Download $PIP_URL ${pip} $PIP_SHA256 |
|
136 | 143 | Secure-Download $VS_BUILD_TOOLS_URL ${prefix}\assets\vs_buildtools.exe $VS_BUILD_TOOLS_SHA256 |
|
137 | 144 | Secure-Download $INNO_SETUP_URL ${prefix}\assets\InnoSetup.exe $INNO_SETUP_SHA256 |
@@ -146,6 +153,8 b' function Install-Dependencies($prefix) {' | |||
|
146 | 153 | # Install-Python3 "Python 3.8 64-bit" ${prefix}\assets\python38-x64.exe ${prefix}\python38-x64 ${pip} |
|
147 | 154 | Install-Python3 "Python 3.9 32-bit" ${prefix}\assets\python39-x86.exe ${prefix}\python39-x86 ${pip} |
|
148 | 155 | Install-Python3 "Python 3.9 64-bit" ${prefix}\assets\python39-x64.exe ${prefix}\python39-x64 ${pip} |
|
156 | Install-Python3 "Python 3.10 32-bit" ${prefix}\assets\python310-x86.exe ${prefix}\python310-x86 ${pip} | |
|
157 | Install-Python3 "Python 3.10 64-bit" ${prefix}\assets\python310-x64.exe ${prefix}\python310-x64 ${pip} | |
|
149 | 158 | |
|
150 | 159 | Write-Output "installing Visual Studio 2017 Build Tools and SDKs" |
|
151 | 160 | 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" |
@@ -1,68 +1,84 b'' | |||
|
1 | 1 | # |
|
2 | # This file is autogenerated by pip-compile | |
|
2 | # This file is autogenerated by pip-compile with python 3.7 | |
|
3 | 3 | # To update, run: |
|
4 | 4 | # |
|
5 | 5 | # pip-compile --generate-hashes --output-file=contrib/packaging/requirements-windows-py3.txt contrib/packaging/requirements-windows.txt.in |
|
6 | 6 | # |
|
7 | 7 | atomicwrites==1.4.0 \ |
|
8 | 8 | --hash=sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197 \ |
|
9 |
--hash=sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a |
|
|
9 | --hash=sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a | |
|
10 | 10 | # via pytest |
|
11 | 11 | attrs==21.2.0 \ |
|
12 | 12 | --hash=sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1 \ |
|
13 |
--hash=sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb |
|
|
13 | --hash=sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb | |
|
14 | 14 | # via pytest |
|
15 | 15 | cached-property==1.5.2 \ |
|
16 | 16 | --hash=sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130 \ |
|
17 |
--hash=sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0 |
|
|
17 | --hash=sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0 | |
|
18 | 18 | # via pygit2 |
|
19 | 19 | certifi==2021.5.30 \ |
|
20 | 20 | --hash=sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee \ |
|
21 |
--hash=sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8 |
|
|
21 | --hash=sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8 | |
|
22 | 22 | # via dulwich |
|
23 |
cffi==1.1 |
|
|
24 | --hash=sha256:00a1ba5e2e95684448de9b89888ccd02c98d512064b4cb987d48f4b40aa0421e \ | |
|
25 | --hash=sha256:00e28066507bfc3fe865a31f325c8391a1ac2916219340f87dfad602c3e48e5d \ | |
|
26 | --hash=sha256:045d792900a75e8b1e1b0ab6787dd733a8190ffcf80e8c8ceb2fb10a29ff238a \ | |
|
27 | --hash=sha256:0638c3ae1a0edfb77c6765d487fee624d2b1ee1bdfeffc1f0b58c64d149e7eec \ | |
|
28 | --hash=sha256:105abaf8a6075dc96c1fe5ae7aae073f4696f2905fde6aeada4c9d2926752362 \ | |
|
29 | --hash=sha256:155136b51fd733fa94e1c2ea5211dcd4c8879869008fc811648f16541bf99668 \ | |
|
30 | --hash=sha256:1a465cbe98a7fd391d47dce4b8f7e5b921e6cd805ef421d04f5f66ba8f06086c \ | |
|
31 | --hash=sha256:1d2c4994f515e5b485fd6d3a73d05526aa0fcf248eb135996b088d25dfa1865b \ | |
|
32 | --hash=sha256:2c24d61263f511551f740d1a065eb0212db1dbbbbd241db758f5244281590c06 \ | |
|
33 | --hash=sha256:51a8b381b16ddd370178a65360ebe15fbc1c71cf6f584613a7ea08bfad946698 \ | |
|
34 | --hash=sha256:594234691ac0e9b770aee9fcdb8fa02c22e43e5c619456efd0d6c2bf276f3eb2 \ | |
|
35 | --hash=sha256:5cf4be6c304ad0b6602f5c4e90e2f59b47653ac1ed9c662ed379fe48a8f26b0c \ | |
|
36 | --hash=sha256:64081b3f8f6f3c3de6191ec89d7dc6c86a8a43911f7ecb422c60e90c70be41c7 \ | |
|
37 | --hash=sha256:6bc25fc545a6b3d57b5f8618e59fc13d3a3a68431e8ca5fd4c13241cd70d0009 \ | |
|
38 | --hash=sha256:798caa2a2384b1cbe8a2a139d80734c9db54f9cc155c99d7cc92441a23871c03 \ | |
|
39 | --hash=sha256:7c6b1dece89874d9541fc974917b631406233ea0440d0bdfbb8e03bf39a49b3b \ | |
|
40 | --hash=sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909 \ | |
|
41 | --hash=sha256:8d6603078baf4e11edc4168a514c5ce5b3ba6e3e9c374298cb88437957960a53 \ | |
|
42 | --hash=sha256:9cc46bc107224ff5b6d04369e7c595acb700c3613ad7bcf2e2012f62ece80c35 \ | |
|
43 | --hash=sha256:9f7a31251289b2ab6d4012f6e83e58bc3b96bd151f5b5262467f4bb6b34a7c26 \ | |
|
44 | --hash=sha256:9ffb888f19d54a4d4dfd4b3f29bc2c16aa4972f1c2ab9c4ab09b8ab8685b9c2b \ | |
|
45 | --hash=sha256:a7711edca4dcef1a75257b50a2fbfe92a65187c47dab5a0f1b9b332c5919a3fb \ | |
|
46 | --hash=sha256:af5c59122a011049aad5dd87424b8e65a80e4a6477419c0c1015f73fb5ea0293 \ | |
|
47 | --hash=sha256:b18e0a9ef57d2b41f5c68beefa32317d286c3d6ac0484efd10d6e07491bb95dd \ | |
|
48 | --hash=sha256:b4e248d1087abf9f4c10f3c398896c87ce82a9856494a7155823eb45a892395d \ | |
|
49 | --hash=sha256:ba4e9e0ae13fc41c6b23299545e5ef73055213e466bd107953e4a013a5ddd7e3 \ | |
|
50 | --hash=sha256:c6332685306b6417a91b1ff9fae889b3ba65c2292d64bd9245c093b1b284809d \ | |
|
51 | --hash=sha256:d9efd8b7a3ef378dd61a1e77367f1924375befc2eba06168b6ebfa903a5e59ca \ | |
|
52 | --hash=sha256:df5169c4396adc04f9b0a05f13c074df878b6052430e03f50e68adf3a57aa28d \ | |
|
53 | --hash=sha256:ebb253464a5d0482b191274f1c8bf00e33f7e0b9c66405fbffc61ed2c839c775 \ | |
|
54 | --hash=sha256:ec80dc47f54e6e9a78181ce05feb71a0353854cc26999db963695f950b5fb375 \ | |
|
55 | --hash=sha256:f032b34669220030f905152045dfa27741ce1a6db3324a5bc0b96b6c7420c87b \ | |
|
56 | --hash=sha256:f60567825f791c6f8a592f3c6e3bd93dd2934e3f9dac189308426bd76b00ef3b \ | |
|
57 | --hash=sha256:f803eaa94c2fcda012c047e62bc7a51b0bdabda1cad7a92a522694ea2d76e49f \ | |
|
23 | cffi==1.15.0 \ | |
|
24 | --hash=sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3 \ | |
|
25 | --hash=sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2 \ | |
|
26 | --hash=sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636 \ | |
|
27 | --hash=sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20 \ | |
|
28 | --hash=sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728 \ | |
|
29 | --hash=sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27 \ | |
|
30 | --hash=sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66 \ | |
|
31 | --hash=sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443 \ | |
|
32 | --hash=sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0 \ | |
|
33 | --hash=sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7 \ | |
|
34 | --hash=sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39 \ | |
|
35 | --hash=sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605 \ | |
|
36 | --hash=sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a \ | |
|
37 | --hash=sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37 \ | |
|
38 | --hash=sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029 \ | |
|
39 | --hash=sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139 \ | |
|
40 | --hash=sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc \ | |
|
41 | --hash=sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df \ | |
|
42 | --hash=sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14 \ | |
|
43 | --hash=sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880 \ | |
|
44 | --hash=sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2 \ | |
|
45 | --hash=sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a \ | |
|
46 | --hash=sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e \ | |
|
47 | --hash=sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474 \ | |
|
48 | --hash=sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024 \ | |
|
49 | --hash=sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8 \ | |
|
50 | --hash=sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0 \ | |
|
51 | --hash=sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e \ | |
|
52 | --hash=sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a \ | |
|
53 | --hash=sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e \ | |
|
54 | --hash=sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032 \ | |
|
55 | --hash=sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6 \ | |
|
56 | --hash=sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e \ | |
|
57 | --hash=sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b \ | |
|
58 | --hash=sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e \ | |
|
59 | --hash=sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954 \ | |
|
60 | --hash=sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962 \ | |
|
61 | --hash=sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c \ | |
|
62 | --hash=sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4 \ | |
|
63 | --hash=sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55 \ | |
|
64 | --hash=sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962 \ | |
|
65 | --hash=sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023 \ | |
|
66 | --hash=sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c \ | |
|
67 | --hash=sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6 \ | |
|
68 | --hash=sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8 \ | |
|
69 | --hash=sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382 \ | |
|
70 | --hash=sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7 \ | |
|
71 | --hash=sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc \ | |
|
72 | --hash=sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997 \ | |
|
73 | --hash=sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796 | |
|
58 | 74 | # via pygit2 |
|
59 | 75 | colorama==0.4.4 \ |
|
60 | 76 | --hash=sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b \ |
|
61 |
--hash=sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2 |
|
|
77 | --hash=sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2 | |
|
62 | 78 | # via pytest |
|
63 | 79 | docutils==0.16 \ |
|
64 | 80 | --hash=sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af \ |
|
65 |
--hash=sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc |
|
|
81 | --hash=sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc | |
|
66 | 82 | # via -r contrib/packaging/requirements-windows.txt.in |
|
67 | 83 | dulwich==0.20.6 ; python_version >= "3" \ |
|
68 | 84 | --hash=sha256:1ccd55e38fa9f169290f93e027ab4508202f5bdd6ef534facac4edd3f6903f0d \ |
@@ -77,26 +93,29 b' dulwich==0.20.6 ; python_version >= "3" ' | |||
|
77 | 93 | --hash=sha256:8f7a7f973be2beedfb10dd8d3eb6bdf9ec466c72ad555704897cbd6357fe5021 \ |
|
78 | 94 | --hash=sha256:bea6e6caffc6c73bfd1647714c5715ab96ac49deb8beb8b67511529afa25685a \ |
|
79 | 95 | --hash=sha256:e5871b86a079e9e290f52ab14559cea1b694a0b8ed2b9ebb898f6ced7f14a406 \ |
|
80 |
--hash=sha256:e593f514b8ac740b4ceeb047745b4719bfc9f334904245c6edcb3a9d002f577b |
|
|
96 | --hash=sha256:e593f514b8ac740b4ceeb047745b4719bfc9f334904245c6edcb3a9d002f577b | |
|
81 | 97 | # via -r contrib/packaging/requirements-windows.txt.in |
|
82 | 98 | fuzzywuzzy==0.18.0 \ |
|
83 |
--hash=sha256:45016e92264780e58972dca1b3d939ac864b78437422beecebb3095f8efd00e8 |
|
|
99 | --hash=sha256:45016e92264780e58972dca1b3d939ac864b78437422beecebb3095f8efd00e8 | |
|
84 | 100 | # via -r contrib/packaging/requirements-windows.txt.in |
|
85 | 101 | idna==3.2 \ |
|
86 | 102 | --hash=sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a \ |
|
87 |
--hash=sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3 |
|
|
103 | --hash=sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3 | |
|
88 | 104 | # via yarl |
|
89 | 105 | importlib-metadata==3.1.0 \ |
|
90 | 106 | --hash=sha256:590690d61efdd716ff82c39ca9a9d4209252adfe288a4b5721181050acbd4175 \ |
|
91 |
--hash=sha256:d9b8a46a0885337627a6430db287176970fff18ad421becec1d64cfc763c2099 |
|
|
92 | # via keyring, pluggy, pytest | |
|
107 | --hash=sha256:d9b8a46a0885337627a6430db287176970fff18ad421becec1d64cfc763c2099 | |
|
108 | # via | |
|
109 | # keyring | |
|
110 | # pluggy | |
|
111 | # pytest | |
|
93 | 112 | iniconfig==1.1.1 \ |
|
94 | 113 | --hash=sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3 \ |
|
95 |
--hash=sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32 |
|
|
114 | --hash=sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32 | |
|
96 | 115 | # via pytest |
|
97 | 116 | keyring==21.4.0 \ |
|
98 | 117 | --hash=sha256:4e34ea2fdec90c1c43d6610b5a5fafa1b9097db1802948e90caf5763974b8f8d \ |
|
99 |
--hash=sha256:9aeadd006a852b78f4b4ef7c7556c2774d2432bbef8ee538a3e9089ac8b11466 |
|
|
118 | --hash=sha256:9aeadd006a852b78f4b4ef7c7556c2774d2432bbef8ee538a3e9089ac8b11466 | |
|
100 | 119 | # via -r contrib/packaging/requirements-windows.txt.in |
|
101 | 120 | multidict==5.1.0 \ |
|
102 | 121 | --hash=sha256:018132dbd8688c7a69ad89c4a3f39ea2f9f33302ebe567a879da8f4ca73f0d0a \ |
@@ -135,62 +154,68 b' multidict==5.1.0 \\' | |||
|
135 | 154 | --hash=sha256:ecc771ab628ea281517e24fd2c52e8f31c41e66652d07599ad8818abaad38cda \ |
|
136 | 155 | --hash=sha256:f200755768dc19c6f4e2b672421e0ebb3dd54c38d5a4f262b872d8cfcc9e93b5 \ |
|
137 | 156 | --hash=sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281 \ |
|
138 |
--hash=sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80 |
|
|
157 | --hash=sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80 | |
|
139 | 158 | # via yarl |
|
140 | 159 | packaging==21.0 \ |
|
141 | 160 | --hash=sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7 \ |
|
142 |
--hash=sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14 |
|
|
161 | --hash=sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14 | |
|
143 | 162 | # via pytest |
|
144 | 163 | pluggy==0.13.1 \ |
|
145 | 164 | --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ |
|
146 |
--hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d |
|
|
165 | --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d | |
|
147 | 166 | # via pytest |
|
148 | 167 | py==1.10.0 \ |
|
149 | 168 | --hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 \ |
|
150 |
--hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a |
|
|
169 | --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a | |
|
151 | 170 | # via pytest |
|
152 |
pycparser==2.2 |
|
|
153 | --hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0 \ | |
|
154 | --hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705 \ | |
|
171 | pycparser==2.21 \ | |
|
172 | --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ | |
|
173 | --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 | |
|
155 | 174 | # via cffi |
|
156 |
pygit2==1. |
|
|
157 | --hash=sha256:0d298098e286eeda000e49ca7e1b41f87300e10dd8b9d06b32b008bd61f50b83 \ | |
|
158 | --hash=sha256:0ee135eb2cd8b07ce1374f3596cc5c3213472d6389bad6a4c5d87d8e267e93e9 \ | |
|
159 | --hash=sha256:32eb863d6651d4890ced318505ea8dc229bd9637deaf29c898de1ab574d727a0 \ | |
|
160 | --hash=sha256:37d6d7d6d7804c42a0fe23425c72e38093488525092fc5e51a05684e63503ce7 \ | |
|
161 | --hash=sha256:41204b6f3406d9f53147710f3cc485d77181ba67f57c34d36b7c86de1c14a18c \ | |
|
162 | --hash=sha256:818c91b582109d90580c5da74af783738838353f15eb12eeb734d80a974b05a3 \ | |
|
163 | --hash=sha256:8306a302487dac67df7af6a064bb37e8a8eb4138958f9560ff49ff162e185dab \ | |
|
164 | --hash=sha256:9c2f2d9ef59513007b66f6534b000792b614de3faf60313a0a68f6b8571aea85 \ | |
|
165 | --hash=sha256:9c8d5881eb709e2e2e13000b507a131bd5fb91a879581030088d0ddffbcd19af \ | |
|
166 | --hash=sha256:b422e417739def0a136a6355723dfe8a5ffc83db5098076f28a14f1d139779c1 \ | |
|
167 | --hash=sha256:cbeb38ab1df9b5d8896548a11e63aae8a064763ab5f1eabe4475e6b8a78ee1c8 \ | |
|
168 | --hash=sha256:cf00481ddf053e549a6edd0216bdc267b292d261eae02a67bb3737de920cbf88 \ | |
|
169 | --hash=sha256:d0d889144e9487d926fecea947c3f39ce5f477e521d7d467d2e66907e4cd657d \ | |
|
170 | --hash=sha256:ddb7a1f6d38063e8724abfa1cfdfb0f9b25014b8bca0546274b7a84b873a3888 \ | |
|
171 | --hash=sha256:e9037a7d810750fe23c9f5641ef14a0af2525ff03e14752cd4f73e1870ecfcb0 \ | |
|
172 | --hash=sha256:ec5c0365a9bdfcac1609d20868507b28685ec5ea7cc3a2c903c9b62ef2e0bbc0 \ | |
|
173 | --hash=sha256:fdd8ba30cda277290e000322f505132f590cf89bd7d31829b45a3cb57447ec32 \ | |
|
175 | pygit2==1.7.1 ; python_version >= "3" \ | |
|
176 | --hash=sha256:2c9e95efb86c0b32cc07c26be3d179e851ca4a7899c47fef63c4203963144f5e \ | |
|
177 | --hash=sha256:3ddacbf461652d3d4900382f821d9fbd5ae2dedecd7862b5245842419ad0ccba \ | |
|
178 | --hash=sha256:4cb0414df6089d0072ebe93ff2f34730737172dd5f0e72289567d06a6caf09c0 \ | |
|
179 | --hash=sha256:56e960dc74f4582bfa3ca17a1a9d542732fc93b5cf8f82574c235d06b2d61eae \ | |
|
180 | --hash=sha256:6b17ab922c2a2d99b30ab9222472b07732bf7261d9f9655a4ea23b4c700049d8 \ | |
|
181 | --hash=sha256:73a7b471f22cb59e8729016de1f447c472b3b2c1cc2b622194e5e3b48a7f5776 \ | |
|
182 | --hash=sha256:761a8850e33822796c1c24d411d5cc2460c04e1a74b04ae8560efd3596bbd6bd \ | |
|
183 | --hash=sha256:7c467e81158f5827b3bca6362e5cc9b92857eff9de65034d338c1f18524b09be \ | |
|
184 | --hash=sha256:7c56e10592e62610a19bd3e2a633aafe3488c57b906c7c2fde0299937f0f0b2f \ | |
|
185 | --hash=sha256:7cc2a8e29cc9598310a78cf58b70d9331277cf374802be8f97d97c4a9e5d8387 \ | |
|
186 | --hash=sha256:812670f7994f31778e873a9eced29d2bbfa91674e8be0ab1e974c8a4bda9cbab \ | |
|
187 | --hash=sha256:8cdb0b1d6c3d24b44f340fed143b16e64ba23fe2a449f1a5db87aaf9339a9dbe \ | |
|
188 | --hash=sha256:91b77a305d8d18b649396e66e832d654cd593a3d29b5728f753f254a04533812 \ | |
|
189 | --hash=sha256:a75bcde32238c77eb0cf7d9698a5aa899408d7ad999a5920a29a7c4b80fdeaa7 \ | |
|
190 | --hash=sha256:b060240cf3038e7a0706bbfc5436dd03b8d5ac797ac1d512b613f4d04b974c80 \ | |
|
191 | --hash=sha256:cdfa61c0428a8182e5a6a1161c017b824cd511574f080a40b10d6413774eb0ca \ | |
|
192 | --hash=sha256:d7faa29558436decc2e78110f38d6677eb366b683ba5cdc2803d47195711165d \ | |
|
193 | --hash=sha256:d831825ad9c3b3c28e6b3ef8a2401ad2d3fd4db5455427ff27175a7e254e2592 \ | |
|
194 | --hash=sha256:df4c477bdfac85d32a1e3180282cd829a0980aa69be9bd0f7cbd4db1778ca72b \ | |
|
195 | --hash=sha256:eced3529bafcaaac015d08dfaa743b3cbad37fcd5b13ae9d280b8b7f716ec5ce \ | |
|
196 | --hash=sha256:fec17e2da668e6bb192d777417aad9c7ca924a166d0a0b9a81a11e00362b1bc7 | |
|
174 | 197 | # via -r contrib/packaging/requirements-windows.txt.in |
|
175 | 198 | pygments==2.7.1 \ |
|
176 | 199 | --hash=sha256:307543fe65c0947b126e83dd5a61bd8acbd84abec11f43caebaf5534cbc17998 \ |
|
177 |
--hash=sha256:926c3f319eda178d1bd90851e4317e6d8cdb5e292a3386aac9bd75eca29cf9c7 |
|
|
200 | --hash=sha256:926c3f319eda178d1bd90851e4317e6d8cdb5e292a3386aac9bd75eca29cf9c7 | |
|
178 | 201 | # via -r contrib/packaging/requirements-windows.txt.in |
|
179 | 202 | pyparsing==2.4.7 \ |
|
180 | 203 | --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ |
|
181 |
--hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b |
|
|
204 | --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b | |
|
182 | 205 | # via packaging |
|
183 | pytest-vcr==1.0.2 \ | |
|
184 | --hash=sha256:23ee51b75abbcc43d926272773aae4f39f93aceb75ed56852d0bf618f92e1896 \ | |
|
185 | # via -r contrib/packaging/requirements-windows.txt.in | |
|
186 | 206 | pytest==6.2.4 \ |
|
187 | 207 | --hash=sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b \ |
|
188 |
--hash=sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890 |
|
|
208 | --hash=sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890 | |
|
189 | 209 | # via pytest-vcr |
|
210 | pytest-vcr==1.0.2 \ | |
|
211 | --hash=sha256:23ee51b75abbcc43d926272773aae4f39f93aceb75ed56852d0bf618f92e1896 | |
|
212 | # via -r contrib/packaging/requirements-windows.txt.in | |
|
190 | 213 | pywin32-ctypes==0.2.0 \ |
|
191 | 214 | --hash=sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942 \ |
|
192 |
--hash=sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98 |
|
|
193 | # via -r contrib/packaging/requirements-windows.txt.in, keyring | |
|
215 | --hash=sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98 | |
|
216 | # via | |
|
217 | # -r contrib/packaging/requirements-windows.txt.in | |
|
218 | # keyring | |
|
194 | 219 | pyyaml==5.4.1 \ |
|
195 | 220 | --hash=sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf \ |
|
196 | 221 | --hash=sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696 \ |
@@ -220,41 +245,43 b' pyyaml==5.4.1 \\' | |||
|
220 | 245 | --hash=sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc \ |
|
221 | 246 | --hash=sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247 \ |
|
222 | 247 | --hash=sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6 \ |
|
223 |
--hash=sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0 |
|
|
248 | --hash=sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0 | |
|
224 | 249 | # via vcrpy |
|
225 | 250 | six==1.16.0 \ |
|
226 | 251 | --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ |
|
227 |
--hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 |
|
|
252 | --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 | |
|
228 | 253 | # via vcrpy |
|
229 | 254 | toml==0.10.2 \ |
|
230 | 255 | --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \ |
|
231 |
--hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f |
|
|
256 | --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f | |
|
232 | 257 | # via pytest |
|
233 | 258 | typing-extensions==3.10.0.0 \ |
|
234 | 259 | --hash=sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497 \ |
|
235 | 260 | --hash=sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342 \ |
|
236 |
--hash=sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84 |
|
|
261 | --hash=sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84 | |
|
237 | 262 | # via yarl |
|
238 | 263 | urllib3==1.25.11 \ |
|
239 | 264 | --hash=sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2 \ |
|
240 |
--hash=sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e |
|
|
265 | --hash=sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e | |
|
241 | 266 | # via dulwich |
|
242 | 267 | vcrpy==4.1.1 \ |
|
243 | 268 | --hash=sha256:12c3fcdae7b88ecf11fc0d3e6d77586549d4575a2ceee18e82eee75c1f626162 \ |
|
244 |
--hash=sha256:57095bf22fc0a2d99ee9674cdafebed0f3ba763018582450706f7d3a74fff599 |
|
|
269 | --hash=sha256:57095bf22fc0a2d99ee9674cdafebed0f3ba763018582450706f7d3a74fff599 | |
|
245 | 270 | # via pytest-vcr |
|
246 |
windows-curses==2. |
|
|
247 | --hash=sha256:1452d771ec6f9b3fef037da2b169196a9a12be4e86a6c27dd579adac70c42028 \ | |
|
248 | --hash=sha256:267544e4f60c09af6505e50a69d7f01d7f8a281cf4bd4fc7efc3b32b9a4ef64e \ | |
|
249 | --hash=sha256:389228a3df556102e72450f599283094168aa82eee189f501ad9f131a0fc92e1 \ | |
|
250 | --hash=sha256:84336fe470fa07288daec5c684dec74c0766fec6b3511ccedb4c494804acfbb7 \ | |
|
251 | --hash=sha256:9aa6ff60be76f5de696dc6dbf7897e3b1e6abcf4c0f741e9a0ee22cd6ef382f8 \ | |
|
252 | --hash=sha256:c4a8ce00e82635f06648cc40d99f470be4e3ffeb84f9f7ae9d6a4f68ec6361e7 \ | |
|
253 | --hash=sha256:c5cd032bc7d0f03224ab55c925059d98e81795098d59bbd10f7d05c7ea9677ce \ | |
|
254 | --hash=sha256:fc0be372fe6da3c39d7093154ce029115a927bf287f34b4c615e2b3f8c23dfaa \ | |
|
271 | windows-curses==2.3.0 \ | |
|
272 | --hash=sha256:170c0d941c2e0cdf864e7f0441c1bdf0709232bf4aa7ce7f54d90fc76a4c0504 \ | |
|
273 | --hash=sha256:4d5fb991d1b90a41c2332f02241a1f84c8a1e6bc8f6e0d26f532d0da7a9f7b51 \ | |
|
274 | --hash=sha256:7a35eda4cb120b9e1a5ae795f3bc06c55b92c9d391baba6be1903285a05f3551 \ | |
|
275 | --hash=sha256:935be95cfdb9213f6f5d3d5bcd489960e3a8fbc9b574e7b2e8a3a3cc46efff49 \ | |
|
276 | --hash=sha256:a3a63a0597729e10f923724c2cf972a23ea677b400d2387dee1d668cf7116177 \ | |
|
277 | --hash=sha256:c860f596d28377e47f322b7382be4d3573fd76d1292234996bb7f72e0bc0ed0d \ | |
|
278 | --hash=sha256:cc5fa913780d60f4a40824d374a4f8ca45b4e205546e83a2d85147315a57457e \ | |
|
279 | --hash=sha256:d5cde8ec6d582aa77af791eca54f60858339fb3f391945f9cad11b1ab71062e3 \ | |
|
280 | --hash=sha256:e913dc121446d92b33fe4f5bcca26d3a34e4ad19f2af160370d57c3d1e93b4e1 \ | |
|
281 | --hash=sha256:fbc2131cec57e422c6660e6cdb3420aff5be5169b8e45bb7c471f884b0590a2b | |
|
255 | 282 | # via -r contrib/packaging/requirements-windows.txt.in |
|
256 | 283 | wrapt==1.12.1 \ |
|
257 |
--hash=sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7 |
|
|
284 | --hash=sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7 | |
|
258 | 285 | # via vcrpy |
|
259 | 286 | yarl==1.6.3 \ |
|
260 | 287 | --hash=sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e \ |
@@ -293,9 +320,9 b' yarl==1.6.3 \\' | |||
|
293 | 320 | --hash=sha256:e6b5460dc5ad42ad2b36cca524491dfcaffbfd9c8df50508bddc354e787b8dc2 \ |
|
294 | 321 | --hash=sha256:f040bcc6725c821a4c0665f3aa96a4d0805a7aaf2caf266d256b8ed71b9f041c \ |
|
295 | 322 | --hash=sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a \ |
|
296 |
--hash=sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71 |
|
|
323 | --hash=sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71 | |
|
297 | 324 | # via vcrpy |
|
298 | 325 | zipp==3.4.0 \ |
|
299 | 326 | --hash=sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108 \ |
|
300 |
--hash=sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb |
|
|
327 | --hash=sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb | |
|
301 | 328 | # via importlib-metadata |
@@ -1,16 +1,16 b'' | |||
|
1 | 1 | # |
|
2 | # This file is autogenerated by pip-compile | |
|
2 | # This file is autogenerated by pip-compile with python 3.7 | |
|
3 | 3 | # To update, run: |
|
4 | 4 | # |
|
5 | 5 | # pip-compile --generate-hashes --output-file=contrib/packaging/requirements.txt contrib/packaging/requirements.txt.in |
|
6 | 6 | # |
|
7 | 7 | docutils==0.16 \ |
|
8 | 8 | --hash=sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af \ |
|
9 |
--hash=sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc |
|
|
9 | --hash=sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc | |
|
10 | 10 | # via -r contrib/packaging/requirements.txt.in |
|
11 | 11 | jinja2==2.11.2 \ |
|
12 | 12 | --hash=sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0 \ |
|
13 |
--hash=sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035 |
|
|
13 | --hash=sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035 | |
|
14 | 14 | # via -r contrib/packaging/requirements.txt.in |
|
15 | 15 | markupsafe==1.1.1 \ |
|
16 | 16 | --hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \ |
@@ -45,5 +45,5 b' markupsafe==1.1.1 \\' | |||
|
45 | 45 | --hash=sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f \ |
|
46 | 46 | --hash=sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2 \ |
|
47 | 47 | --hash=sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7 \ |
|
48 |
--hash=sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be |
|
|
48 | --hash=sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be | |
|
49 | 49 | # via jinja2 |
@@ -1,6 +1,11 b'' | |||
|
1 | 1 | #!/bin/bash |
|
2 | 2 | set -eu |
|
3 | 3 | |
|
4 | if [[ "$PHABRICATOR_TOKEN" == "NO-PHAB" ]]; then | |
|
5 | echo 'Skipping Phabricator Step' >&2 | |
|
6 | exit 0 | |
|
7 | fi | |
|
8 | ||
|
4 | 9 | revision_in_stack=`hg log \ |
|
5 | 10 | --rev '.#stack and ::. and topic()' \ |
|
6 | 11 | -T '\nONE-REV\n' \ |
@@ -27,6 +32,7 b' fi' | |||
|
27 | 32 | |
|
28 | 33 | if [[ "$PHABRICATOR_TOKEN" == "" ]]; then |
|
29 | 34 | echo 'missing $PHABRICATOR_TOKEN variable' >&2 |
|
35 | echo '(use PHABRICATOR_TOKEN="NO-PHAB" to disable this step)' >&2 | |
|
30 | 36 | exit 2 |
|
31 | 37 | fi |
|
32 | 38 |
@@ -13,9 +13,9 b' from mercurial import (' | |||
|
13 | 13 | context, |
|
14 | 14 | error, |
|
15 | 15 | fancyopts, |
|
16 | pycompat, | |
|
17 | 16 | simplemerge, |
|
18 | 17 | ui as uimod, |
|
18 | util, | |
|
19 | 19 | ) |
|
20 | 20 | from mercurial.utils import procutil, stringutil |
|
21 | 21 | |
@@ -65,6 +65,17 b' def showhelp():' | |||
|
65 | 65 | procutil.stdout.write(b' %-*s %s\n' % (opts_len, first, second)) |
|
66 | 66 | |
|
67 | 67 | |
|
68 | def _verifytext(input, ui, quiet=False, allow_binary=False): | |
|
69 | """verifies that text is non-binary (unless opts[text] is passed, | |
|
70 | then we just warn)""" | |
|
71 | if stringutil.binary(input.text()): | |
|
72 | msg = _(b"%s looks like a binary file.") % input.fctx.path() | |
|
73 | if not quiet: | |
|
74 | ui.warn(_(b'warning: %s\n') % msg) | |
|
75 | if not allow_binary: | |
|
76 | sys.exit(1) | |
|
77 | ||
|
78 | ||
|
68 | 79 | try: |
|
69 | 80 | for fp in (sys.stdin, procutil.stdout, sys.stderr): |
|
70 | 81 | procutil.setbinary(fp) |
@@ -80,16 +91,44 b' try:' | |||
|
80 | 91 | sys.exit(0) |
|
81 | 92 | if len(args) != 3: |
|
82 | 93 | raise ParseError(_(b'wrong number of arguments').decode('utf8')) |
|
94 | mode = b'merge' | |
|
95 | if len(opts[b'label']) > 2: | |
|
96 | mode = b'merge3' | |
|
83 | 97 | local, base, other = args |
|
84 | sys.exit( | |
|
85 | simplemerge.simplemerge( | |
|
86 | uimod.ui.load(), | |
|
87 | context.arbitraryfilectx(local), | |
|
88 | context.arbitraryfilectx(base), | |
|
89 | context.arbitraryfilectx(other), | |
|
90 | **pycompat.strkwargs(opts) | |
|
98 | overrides = opts[b'label'] | |
|
99 | if len(overrides) > 3: | |
|
100 | raise error.InputError(b'can only specify three labels.') | |
|
101 | labels = [local, other, base] | |
|
102 | labels[: len(overrides)] = overrides | |
|
103 | local_input = simplemerge.MergeInput( | |
|
104 | context.arbitraryfilectx(local), labels[0] | |
|
105 | ) | |
|
106 | other_input = simplemerge.MergeInput( | |
|
107 | context.arbitraryfilectx(other), labels[1] | |
|
108 | ) | |
|
109 | base_input = simplemerge.MergeInput( | |
|
110 | context.arbitraryfilectx(base), labels[2] | |
|
91 | 111 |
|
|
112 | ||
|
113 | quiet = opts.get(b'quiet') | |
|
114 | allow_binary = opts.get(b'text') | |
|
115 | ui = uimod.ui.load() | |
|
116 | _verifytext(local_input, ui, quiet=quiet, allow_binary=allow_binary) | |
|
117 | _verifytext(base_input, ui, quiet=quiet, allow_binary=allow_binary) | |
|
118 | _verifytext(other_input, ui, quiet=quiet, allow_binary=allow_binary) | |
|
119 | ||
|
120 | merged_text, conflicts = simplemerge.simplemerge( | |
|
121 | local_input, | |
|
122 | base_input, | |
|
123 | other_input, | |
|
124 | mode, | |
|
125 | allow_binary=allow_binary, | |
|
92 | 126 | ) |
|
127 | if opts.get(b'print'): | |
|
128 | ui.fout.write(merged_text) | |
|
129 | else: | |
|
130 | util.writefile(local, merged_text) | |
|
131 | sys.exit(1 if conflicts else 0) | |
|
93 | 132 | except ParseError as e: |
|
94 | 133 | e = stringutil.forcebytestr(e) |
|
95 | 134 | procutil.stdout.write(b"%s: %s\n" % (sys.argv[0].encode('utf8'), e)) |
@@ -36,7 +36,7 b' Examples::' | |||
|
36 | 36 | maxfiles = 3 |
|
37 | 37 | |
|
38 | 38 | [blackbox] |
|
39 |
# Include |
|
|
39 | # Include microseconds in log entries with %f (see Python function | |
|
40 | 40 | # datetime.datetime.strftime) |
|
41 | 41 | date-format = %Y-%m-%d @ %H:%M:%S.%f |
|
42 | 42 | |
@@ -101,11 +101,7 b' configitem(' | |||
|
101 | 101 | b'ignore', |
|
102 | 102 | default=lambda: [b'chgserver', b'cmdserver', b'extension'], |
|
103 | 103 | ) |
|
104 | configitem( | |
|
105 | b'blackbox', | |
|
106 | b'date-format', | |
|
107 | default=b'%Y/%m/%d %H:%M:%S', | |
|
108 | ) | |
|
104 | configitem(b'blackbox', b'date-format', default=b'') | |
|
109 | 105 | |
|
110 | 106 | _lastlogger = loggingutil.proxylogger() |
|
111 | 107 | |
@@ -138,7 +134,14 b' class blackboxlogger(object):' | |||
|
138 | 134 | |
|
139 | 135 | def _log(self, ui, event, msg, opts): |
|
140 | 136 | default = ui.configdate(b'devel', b'default-date') |
|
141 |
date = |
|
|
137 | dateformat = ui.config(b'blackbox', b'date-format') | |
|
138 | if dateformat: | |
|
139 | date = dateutil.datestr(default, dateformat) | |
|
140 | else: | |
|
141 | # We want to display milliseconds (more precision seems | |
|
142 | # unnecessary). Since %.3f is not supported, use %f and truncate | |
|
143 | # microseconds. | |
|
144 | date = dateutil.datestr(default, b'%Y-%m-%d %H:%M:%S.%f')[:-3] | |
|
142 | 145 | user = procutil.getuser() |
|
143 | 146 | pid = b'%d' % procutil.getpid() |
|
144 | 147 | changed = b'' |
@@ -224,8 +227,14 b' def blackbox(ui, repo, *revs, **opts):' | |||
|
224 | 227 | if count >= limit: |
|
225 | 228 | break |
|
226 | 229 | |
|
227 |
# count the commands by matching lines like: |
|
|
228 | if re.match(br'^\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} .*> .*', line): | |
|
230 | # count the commands by matching lines like: | |
|
231 | # 2013/01/23 19:13:36 root> | |
|
232 | # 2013/01/23 19:13:36 root (1234)> | |
|
233 | # 2013/01/23 19:13:36 root @0000000000000000000000000000000000000000 (1234)> | |
|
234 | # 2013-01-23 19:13:36.000 root @0000000000000000000000000000000000000000 (1234)> | |
|
235 | if re.match( | |
|
236 | br'^\d{4}[-/]\d{2}[-/]\d{2} \d{2}:\d{2}:\d{2}(.\d*)? .*> .*', line | |
|
237 | ): | |
|
229 | 238 | count += 1 |
|
230 | 239 | output.append(line) |
|
231 | 240 |
@@ -65,23 +65,23 b' def _commit(orig, ui, repo, *pats, **opt' | |||
|
65 | 65 | b"unable to parse '%s', should follow " |
|
66 | 66 | b"KEY=VALUE format" |
|
67 | 67 | ) |
|
68 |
raise error. |
|
|
68 | raise error.InputError(msg % raw) | |
|
69 | 69 | k, v = raw.split(b'=', 1) |
|
70 | 70 | if not k: |
|
71 | 71 | msg = _(b"unable to parse '%s', keys can't be empty") |
|
72 |
raise error. |
|
|
72 | raise error.InputError(msg % raw) | |
|
73 | 73 | if re.search(br'[^\w-]', k): |
|
74 | 74 | msg = _( |
|
75 | 75 | b"keys can only contain ascii letters, digits," |
|
76 | 76 | b" '_' and '-'" |
|
77 | 77 | ) |
|
78 |
raise error. |
|
|
78 | raise error.InputError(msg) | |
|
79 | 79 | if k in usedinternally: |
|
80 | 80 | msg = _( |
|
81 | 81 | b"key '%s' is used internally, can't be set " |
|
82 | 82 | b"manually" |
|
83 | 83 | ) |
|
84 |
raise error. |
|
|
84 | raise error.InputError(msg % k) | |
|
85 | 85 | inneropts['extra'][k] = v |
|
86 | 86 | return super(repoextra, self).commit(*innerpats, **inneropts) |
|
87 | 87 |
@@ -38,6 +38,7 b' from mercurial import (' | |||
|
38 | 38 | lock as lockmod, |
|
39 | 39 | logcmdutil, |
|
40 | 40 | merge as mergemod, |
|
41 | mergestate, | |
|
41 | 42 | phases, |
|
42 | 43 | pycompat, |
|
43 | 44 | util, |
@@ -241,7 +242,7 b' class mercurial_sink(common.converter_si' | |||
|
241 | 242 | |
|
242 | 243 | # If the file requires actual merging, abort. We don't have enough |
|
243 | 244 | # context to resolve merges correctly. |
|
244 | if action in [b'm', b'dm', b'cd', b'dc']: | |
|
245 | if action in mergestate.CONVERT_MERGE_ACTIONS: | |
|
245 | 246 | raise error.Abort( |
|
246 | 247 | _( |
|
247 | 248 | b"unable to convert merge commit " |
@@ -250,7 +251,7 b' class mercurial_sink(common.converter_si' | |||
|
250 | 251 | ) |
|
251 | 252 | % (file, p1ctx, p2ctx) |
|
252 | 253 | ) |
|
253 |
elif action == |
|
|
254 | elif action == mergestate.ACTION_KEEP: | |
|
254 | 255 | # 'keep' means nothing changed from p1 |
|
255 | 256 | continue |
|
256 | 257 | else: |
@@ -149,7 +149,6 b' from mercurial import (' | |||
|
149 | 149 | mdiff, |
|
150 | 150 | merge, |
|
151 | 151 | mergestate as mergestatemod, |
|
152 | obsolete, | |
|
153 | 152 | pycompat, |
|
154 | 153 | registrar, |
|
155 | 154 | rewriteutil, |
@@ -463,8 +462,6 b' def getrevstofix(ui, repo, opts):' | |||
|
463 | 462 | revs = set(logcmdutil.revrange(repo, opts[b'rev'])) |
|
464 | 463 | if opts.get(b'working_dir'): |
|
465 | 464 | revs.add(wdirrev) |
|
466 | for rev in revs: | |
|
467 | checkfixablectx(ui, repo, repo[rev]) | |
|
468 | 465 | # Allow fixing only wdir() even if there's an unfinished operation |
|
469 | 466 | if not (len(revs) == 1 and wdirrev in revs): |
|
470 | 467 | cmdutil.checkunfinished(repo) |
@@ -481,16 +478,6 b' def getrevstofix(ui, repo, opts):' | |||
|
481 | 478 | return revs |
|
482 | 479 | |
|
483 | 480 | |
|
484 | def checkfixablectx(ui, repo, ctx): | |
|
485 | """Aborts if the revision shouldn't be replaced with a fixed one.""" | |
|
486 | if ctx.obsolete(): | |
|
487 | # It would be better to actually check if the revision has a successor. | |
|
488 | if not obsolete.isenabled(repo, obsolete.allowdivergenceopt): | |
|
489 | raise error.Abort( | |
|
490 | b'fixing obsolete revision could cause divergence' | |
|
491 | ) | |
|
492 | ||
|
493 | ||
|
494 | 481 | def pathstofix(ui, repo, pats, opts, match, basectxs, fixctx): |
|
495 | 482 | """Returns the set of files that should be fixed in a context |
|
496 | 483 |
@@ -51,6 +51,7 b' getversion = gitutil.pygit2_version' | |||
|
51 | 51 | class gitstore(object): # store.basicstore): |
|
52 | 52 | def __init__(self, path, vfstype): |
|
53 | 53 | self.vfs = vfstype(path) |
|
54 | self.opener = self.vfs | |
|
54 | 55 | self.path = self.vfs.base |
|
55 | 56 | self.createmode = store._calcmode(self.vfs) |
|
56 | 57 | # above lines should go away in favor of: |
@@ -257,7 +257,7 b' class gitdirstate(object):' | |||
|
257 | 257 | if match(p): |
|
258 | 258 | yield p |
|
259 | 259 | |
|
260 |
def set_clean(self, f, parentfiledata |
|
|
260 | def set_clean(self, f, parentfiledata): | |
|
261 | 261 | """Mark a file normal and clean.""" |
|
262 | 262 | # TODO: for now we just let libgit2 re-stat the file. We can |
|
263 | 263 | # clearly do better. |
@@ -667,7 +667,15 b' def applychanges(ui, repo, ctx, opts):' | |||
|
667 | 667 | repo.ui.setconfig( |
|
668 | 668 | b'ui', b'forcemerge', opts.get(b'tool', b''), b'histedit' |
|
669 | 669 | ) |
|
670 |
stats = mergemod.graft( |
|
|
670 | stats = mergemod.graft( | |
|
671 | repo, | |
|
672 | ctx, | |
|
673 | labels=[ | |
|
674 | b'already edited', | |
|
675 | b'current change', | |
|
676 | b'parent of current change', | |
|
677 | ], | |
|
678 | ) | |
|
671 | 679 | finally: |
|
672 | 680 | repo.ui.setconfig(b'ui', b'forcemerge', b'', b'histedit') |
|
673 | 681 | return stats |
@@ -1324,6 +1332,10 b' pgup: prev page, space/pgdn: next page, ' | |||
|
1324 | 1332 | d: drop, e: edit, f: fold, m: mess, p: pick, r: roll |
|
1325 | 1333 | pgup/K: move patch up, pgdn/J: move patch down, c: commit, q: abort |
|
1326 | 1334 | """ |
|
1335 | if self.later_on_top: | |
|
1336 | help += b"Newer commits are shown above older commits.\n" | |
|
1337 | else: | |
|
1338 | help += b"Older commits are shown above newer commits.\n" | |
|
1327 | 1339 | return help.splitlines() |
|
1328 | 1340 | |
|
1329 | 1341 | def render_help(self, win): |
@@ -116,6 +116,7 b' from mercurial.utils import (' | |||
|
116 | 116 | dateutil, |
|
117 | 117 | stringutil, |
|
118 | 118 | ) |
|
119 | from mercurial.dirstateutils import timestamp | |
|
119 | 120 | |
|
120 | 121 | cmdtable = {} |
|
121 | 122 | command = registrar.command(cmdtable) |
@@ -326,6 +327,7 b' class kwtemplater(object):' | |||
|
326 | 327 | msg = _(b'overwriting %s expanding keywords\n') |
|
327 | 328 | else: |
|
328 | 329 | msg = _(b'overwriting %s shrinking keywords\n') |
|
330 | wctx = self.repo[None] | |
|
329 | 331 | for f in candidates: |
|
330 | 332 | if self.restrict: |
|
331 | 333 | data = self.repo.file(f).read(mf[f]) |
@@ -356,7 +358,12 b' class kwtemplater(object):' | |||
|
356 | 358 | fp.write(data) |
|
357 | 359 | fp.close() |
|
358 | 360 | if kwcmd: |
|
359 |
s |
|
|
361 | s = wctx[f].lstat() | |
|
362 | mode = s.st_mode | |
|
363 | size = s.st_size | |
|
364 | mtime = timestamp.mtime_of(s) | |
|
365 | cache_data = (mode, size, mtime) | |
|
366 | self.repo.dirstate.set_clean(f, cache_data) | |
|
360 | 367 | elif self.postcommit: |
|
361 | 368 | self.repo.dirstate.update_file_p1(f, p1_tracked=True) |
|
362 | 369 |
@@ -32,6 +32,7 b' from mercurial import (' | |||
|
32 | 32 | vfs as vfsmod, |
|
33 | 33 | ) |
|
34 | 34 | from mercurial.utils import hashutil |
|
35 | from mercurial.dirstateutils import timestamp | |
|
35 | 36 | |
|
36 | 37 | shortname = b'.hglf' |
|
37 | 38 | shortnameslash = shortname + b'/' |
@@ -243,10 +244,11 b' def openlfdirstate(ui, repo, create=True' | |||
|
243 | 244 | def lfdirstatestatus(lfdirstate, repo): |
|
244 | 245 | pctx = repo[b'.'] |
|
245 | 246 | match = matchmod.always() |
|
246 | unsure, s = lfdirstate.status( | |
|
247 | unsure, s, mtime_boundary = lfdirstate.status( | |
|
247 | 248 | match, subrepos=[], ignored=False, clean=False, unknown=False |
|
248 | 249 | ) |
|
249 | 250 | modified, clean = s.modified, s.clean |
|
251 | wctx = repo[None] | |
|
250 | 252 | for lfile in unsure: |
|
251 | 253 | try: |
|
252 | 254 | fctx = pctx[standin(lfile)] |
@@ -256,7 +258,13 b' def lfdirstatestatus(lfdirstate, repo):' | |||
|
256 | 258 | modified.append(lfile) |
|
257 | 259 | else: |
|
258 | 260 | clean.append(lfile) |
|
259 | lfdirstate.set_clean(lfile) | |
|
261 | st = wctx[lfile].lstat() | |
|
262 | mode = st.st_mode | |
|
263 | size = st.st_size | |
|
264 | mtime = timestamp.reliable_mtime_of(st, mtime_boundary) | |
|
265 | if mtime is not None: | |
|
266 | cache_data = (mode, size, mtime) | |
|
267 | lfdirstate.set_clean(lfile, cache_data) | |
|
260 | 268 | return s |
|
261 | 269 | |
|
262 | 270 | |
@@ -663,7 +671,7 b' def updatestandinsbymatch(repo, match):' | |||
|
663 | 671 | # large. |
|
664 | 672 | lfdirstate = openlfdirstate(ui, repo) |
|
665 | 673 | dirtymatch = matchmod.always() |
|
666 | unsure, s = lfdirstate.status( | |
|
674 | unsure, s, mtime_boundary = lfdirstate.status( | |
|
667 | 675 | dirtymatch, subrepos=[], ignored=False, clean=False, unknown=False |
|
668 | 676 | ) |
|
669 | 677 | modifiedfiles = unsure + s.modified + s.added + s.removed |
@@ -51,11 +51,17 b' from . import (' | |||
|
51 | 51 | storefactory, |
|
52 | 52 | ) |
|
53 | 53 | |
|
54 | ACTION_ADD = mergestatemod.ACTION_ADD | |
|
55 | ACTION_DELETED_CHANGED = mergestatemod.ACTION_DELETED_CHANGED | |
|
56 | ACTION_GET = mergestatemod.ACTION_GET | |
|
57 | ACTION_KEEP = mergestatemod.ACTION_KEEP | |
|
58 | ACTION_REMOVE = mergestatemod.ACTION_REMOVE | |
|
59 | ||
|
54 | 60 | eh = exthelper.exthelper() |
|
55 | 61 | |
|
56 | 62 | lfstatus = lfutil.lfstatus |
|
57 | 63 | |
|
58 |
MERGE_ACTION_LARGEFILE_MARK_REMOVED = |
|
|
64 | MERGE_ACTION_LARGEFILE_MARK_REMOVED = mergestatemod.MergeAction('lfmr') | |
|
59 | 65 | |
|
60 | 66 | # -- Utility functions: commonly/repeatedly needed functionality --------------- |
|
61 | 67 | |
@@ -563,8 +569,9 b' def overridecalculateupdates(' | |||
|
563 | 569 | standin = lfutil.standin(lfile) |
|
564 | 570 | (lm, largs, lmsg) = mresult.getfile(lfile, (None, None, None)) |
|
565 | 571 | (sm, sargs, smsg) = mresult.getfile(standin, (None, None, None)) |
|
566 | if sm in (b'g', b'dc') and lm != b'r': | |
|
567 | if sm == b'dc': | |
|
572 | ||
|
573 | if sm in (ACTION_GET, ACTION_DELETED_CHANGED) and lm != ACTION_REMOVE: | |
|
574 | if sm == ACTION_DELETED_CHANGED: | |
|
568 | 575 | f1, f2, fa, move, anc = sargs |
|
569 | 576 | sargs = (p2[f2].flags(), False) |
|
570 | 577 | # Case 1: normal file in the working copy, largefile in |
@@ -578,26 +585,28 b' def overridecalculateupdates(' | |||
|
578 | 585 | % lfile |
|
579 | 586 | ) |
|
580 | 587 | if repo.ui.promptchoice(usermsg, 0) == 0: # pick remote largefile |
|
581 |
mresult.addfile( |
|
|
582 |
|
|
|
588 | mresult.addfile( | |
|
589 | lfile, ACTION_REMOVE, None, b'replaced by standin' | |
|
590 | ) | |
|
591 | mresult.addfile(standin, ACTION_GET, sargs, b'replaces standin') | |
|
583 | 592 | else: # keep local normal file |
|
584 |
mresult.addfile(lfile, |
|
|
593 | mresult.addfile(lfile, ACTION_KEEP, None, b'replaces standin') | |
|
585 | 594 | if branchmerge: |
|
586 | 595 | mresult.addfile( |
|
587 | 596 | standin, |
|
588 |
|
|
|
597 | ACTION_KEEP, | |
|
589 | 598 | None, |
|
590 | 599 | b'replaced by non-standin', |
|
591 | 600 | ) |
|
592 | 601 | else: |
|
593 | 602 | mresult.addfile( |
|
594 | 603 | standin, |
|
595 |
|
|
|
604 | ACTION_REMOVE, | |
|
596 | 605 | None, |
|
597 | 606 | b'replaced by non-standin', |
|
598 | 607 | ) |
|
599 | elif lm in (b'g', b'dc') and sm != b'r': | |
|
600 |
if lm == |
|
|
608 | if lm in (ACTION_GET, ACTION_DELETED_CHANGED) and sm != ACTION_REMOVE: | |
|
609 | if lm == ACTION_DELETED_CHANGED: | |
|
601 | 610 | f1, f2, fa, move, anc = largs |
|
602 | 611 | largs = (p2[f2].flags(), False) |
|
603 | 612 | # Case 2: largefile in the working copy, normal file in |
@@ -615,11 +624,13 b' def overridecalculateupdates(' | |||
|
615 | 624 | # largefile can be restored from standin safely |
|
616 | 625 | mresult.addfile( |
|
617 | 626 | lfile, |
|
618 |
|
|
|
627 | ACTION_KEEP, | |
|
619 | 628 | None, |
|
620 | 629 | b'replaced by standin', |
|
621 | 630 | ) |
|
622 |
mresult.addfile( |
|
|
631 | mresult.addfile( | |
|
632 | standin, ACTION_KEEP, None, b'replaces standin' | |
|
633 | ) | |
|
623 | 634 | else: |
|
624 | 635 | # "lfile" should be marked as "removed" without |
|
625 | 636 | # removal of itself |
@@ -631,12 +642,12 b' def overridecalculateupdates(' | |||
|
631 | 642 | ) |
|
632 | 643 | |
|
633 | 644 | # linear-merge should treat this largefile as 're-added' |
|
634 |
mresult.addfile(standin, |
|
|
645 | mresult.addfile(standin, ACTION_ADD, None, b'keep standin') | |
|
635 | 646 | else: # pick remote normal file |
|
636 |
mresult.addfile(lfile, |
|
|
647 | mresult.addfile(lfile, ACTION_GET, largs, b'replaces standin') | |
|
637 | 648 | mresult.addfile( |
|
638 | 649 | standin, |
|
639 |
|
|
|
650 | ACTION_REMOVE, | |
|
640 | 651 | None, |
|
641 | 652 | b'replaced by non-standin', |
|
642 | 653 | ) |
@@ -666,14 +677,12 b' def mergerecordupdates(orig, repo, actio' | |||
|
666 | 677 | |
|
667 | 678 | # Override filemerge to prompt the user about how they wish to merge |
|
668 | 679 | # largefiles. This will handle identical edits without prompting the user. |
|
669 |
@eh.wrapfunction(filemerge, b' |
|
|
680 | @eh.wrapfunction(filemerge, b'filemerge') | |
|
670 | 681 | def overridefilemerge( |
|
671 |
origfn |
|
|
682 | origfn, repo, wctx, mynode, orig, fcd, fco, fca, labels=None | |
|
672 | 683 | ): |
|
673 | 684 | if not lfutil.isstandin(orig) or fcd.isabsent() or fco.isabsent(): |
|
674 | return origfn( | |
|
675 | premerge, repo, wctx, mynode, orig, fcd, fco, fca, labels=labels | |
|
676 | ) | |
|
685 | return origfn(repo, wctx, mynode, orig, fcd, fco, fca, labels=labels) | |
|
677 | 686 | |
|
678 | 687 | ahash = lfutil.readasstandin(fca).lower() |
|
679 | 688 | dhash = lfutil.readasstandin(fcd).lower() |
@@ -697,7 +706,7 b' def overridefilemerge(' | |||
|
697 | 706 | ) |
|
698 | 707 | ): |
|
699 | 708 | repo.wwrite(fcd.path(), fco.data(), fco.flags()) |
|
700 |
return |
|
|
709 | return 0, False | |
|
701 | 710 | |
|
702 | 711 | |
|
703 | 712 | @eh.wrapfunction(copiesmod, b'pathcopies') |
@@ -1519,7 +1528,7 b' def scmutiladdremove(orig, repo, matcher' | |||
|
1519 | 1528 | return orig(repo, matcher, prefix, uipathfn, opts) |
|
1520 | 1529 | # Get the list of missing largefiles so we can remove them |
|
1521 | 1530 | lfdirstate = lfutil.openlfdirstate(repo.ui, repo) |
|
1522 | unsure, s = lfdirstate.status( | |
|
1531 | unsure, s, mtime_boundary = lfdirstate.status( | |
|
1523 | 1532 | matchmod.always(), |
|
1524 | 1533 | subrepos=[], |
|
1525 | 1534 | ignored=False, |
@@ -1746,7 +1755,7 b' def mergeupdate(orig, repo, node, branch' | |||
|
1746 | 1755 | # (*1) deprecated, but used internally (e.g: "rebase --collapse") |
|
1747 | 1756 | |
|
1748 | 1757 | lfdirstate = lfutil.openlfdirstate(repo.ui, repo) |
|
1749 | unsure, s = lfdirstate.status( | |
|
1758 | unsure, s, mtime_boundary = lfdirstate.status( | |
|
1750 | 1759 | matchmod.always(), |
|
1751 | 1760 | subrepos=[], |
|
1752 | 1761 | ignored=False, |
@@ -22,6 +22,8 b' from mercurial import (' | |||
|
22 | 22 | util, |
|
23 | 23 | ) |
|
24 | 24 | |
|
25 | from mercurial.dirstateutils import timestamp | |
|
26 | ||
|
25 | 27 | from . import ( |
|
26 | 28 | lfcommands, |
|
27 | 29 | lfutil, |
@@ -195,7 +197,7 b' def reposetup(ui, repo):' | |||
|
195 | 197 | match._files = [f for f in match._files if sfindirstate(f)] |
|
196 | 198 | # Don't waste time getting the ignored and unknown |
|
197 | 199 | # files from lfdirstate |
|
198 | unsure, s = lfdirstate.status( | |
|
200 | unsure, s, mtime_boundary = lfdirstate.status( | |
|
199 | 201 | match, |
|
200 | 202 | subrepos=[], |
|
201 | 203 | ignored=False, |
@@ -210,6 +212,7 b' def reposetup(ui, repo):' | |||
|
210 | 212 | s.clean, |
|
211 | 213 | ) |
|
212 | 214 | if parentworking: |
|
215 | wctx = repo[None] | |
|
213 | 216 | for lfile in unsure: |
|
214 | 217 | standin = lfutil.standin(lfile) |
|
215 | 218 | if standin not in ctx1: |
@@ -222,7 +225,15 b' def reposetup(ui, repo):' | |||
|
222 | 225 | else: |
|
223 | 226 | if listclean: |
|
224 | 227 | clean.append(lfile) |
|
225 |
|
|
|
228 | s = wctx[lfile].lstat() | |
|
229 | mode = s.st_mode | |
|
230 | size = s.st_size | |
|
231 | mtime = timestamp.reliable_mtime_of( | |
|
232 | s, mtime_boundary | |
|
233 | ) | |
|
234 | if mtime is not None: | |
|
235 | cache_data = (mode, size, mtime) | |
|
236 | lfdirstate.set_clean(lfile, cache_data) | |
|
226 | 237 | else: |
|
227 | 238 | tocheck = unsure + modified + added + clean |
|
228 | 239 | modified, added, clean = [], [], [] |
@@ -444,6 +455,7 b' def reposetup(ui, repo):' | |||
|
444 | 455 | repo.prepushoutgoinghooks.add(b"largefiles", prepushoutgoinghook) |
|
445 | 456 | |
|
446 | 457 | def checkrequireslfiles(ui, repo, **kwargs): |
|
458 | with repo.lock(): | |
|
447 | 459 | if b'largefiles' not in repo.requirements and any( |
|
448 | 460 | lfutil.shortname + b'/' in f[1] for f in repo.store.datafiles() |
|
449 | 461 | ): |
@@ -257,6 +257,7 b' def _reposetup(ui, repo):' | |||
|
257 | 257 | if b'lfs' not in repo.requirements: |
|
258 | 258 | |
|
259 | 259 | def checkrequireslfs(ui, repo, **kwargs): |
|
260 | with repo.lock(): | |
|
260 | 261 | if b'lfs' in repo.requirements: |
|
261 | 262 | return 0 |
|
262 | 263 | |
@@ -269,7 +270,9 b' def _reposetup(ui, repo):' | |||
|
269 | 270 | for ctx in s: |
|
270 | 271 | # TODO: is there a way to just walk the files in the commit? |
|
271 | 272 | if any( |
|
272 | ctx[f].islfs() for f in ctx.files() if f in ctx and match(f) | |
|
273 | ctx[f].islfs() | |
|
274 | for f in ctx.files() | |
|
275 | if f in ctx and match(f) | |
|
273 | 276 | ): |
|
274 | 277 | repo.requirements.add(b'lfs') |
|
275 | 278 | repo.features.add(repository.REPO_FEATURE_LFS) |
@@ -38,8 +38,8 b' def wrapdirstate(repo, dirstate):' | |||
|
38 | 38 | return super(narrowdirstate, self).normal(*args, **kwargs) |
|
39 | 39 | |
|
40 | 40 | @_editfunc |
|
41 | def set_tracked(self, *args): | |
|
42 | return super(narrowdirstate, self).set_tracked(*args) | |
|
41 | def set_tracked(self, *args, **kwargs): | |
|
42 | return super(narrowdirstate, self).set_tracked(*args, **kwargs) | |
|
43 | 43 | |
|
44 | 44 | @_editfunc |
|
45 | 45 | def set_untracked(self, *args): |
@@ -435,7 +435,10 b' class notifier(object):' | |||
|
435 | 435 | if spec is None: |
|
436 | 436 | subs.add(sub) |
|
437 | 437 | continue |
|
438 | try: | |
|
438 | 439 | revs = self.repo.revs(b'%r and %d:', spec, ctx.rev()) |
|
440 | except error.RepoLookupError: | |
|
441 | continue | |
|
439 | 442 | if len(revs): |
|
440 | 443 | subs.add(sub) |
|
441 | 444 | continue |
@@ -1544,7 +1544,7 b' def rebasenode(repo, rev, p1, p2, base, ' | |||
|
1544 | 1544 | force=True, |
|
1545 | 1545 | ancestor=base, |
|
1546 | 1546 | mergeancestor=mergeancestor, |
|
1547 | labels=[b'dest', b'source'], | |
|
1547 | labels=[b'dest', b'source', b'parent of source'], | |
|
1548 | 1548 | wc=wctx, |
|
1549 | 1549 | ) |
|
1550 | 1550 | wctx.setparents(p1ctx.node(), repo[p2].node()) |
@@ -88,7 +88,9 b' 3. remotefilelog only works with ssh bas' | |||
|
88 | 88 | |
|
89 | 89 | 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. |
|
90 | 90 | |
|
91 | 5. A few commands will be slower. `hg log <filename>` will be much slower since it has to walk the entire commit history instead of just the filelog. Use `hg log -f <filename>` instead, which remains very fast. | |
|
91 | 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. | |
|
92 | ||
|
93 | 6. A few commands will be slower. `hg log <filename>` will be much slower since it has to walk the entire commit history instead of just the filelog. Use `hg log -f <filename>` instead, which remains very fast. | |
|
92 | 94 | |
|
93 | 95 | Contributing |
|
94 | 96 | ============ |
@@ -520,7 +520,7 b' def checkunknownfiles(orig, repo, wctx, ' | |||
|
520 | 520 | |
|
521 | 521 | |
|
522 | 522 | # Prefetch files before status attempts to look at their size and contents |
|
523 | def checklookup(orig, self, files): | |
|
523 | def checklookup(orig, self, files, mtime_boundary): | |
|
524 | 524 | repo = self._repo |
|
525 | 525 | if isenabled(repo): |
|
526 | 526 | prefetchfiles = [] |
@@ -530,7 +530,7 b' def checklookup(orig, self, files):' | |||
|
530 | 530 | prefetchfiles.append((f, hex(parent.filenode(f)))) |
|
531 | 531 | # batch fetch the needed files from the server |
|
532 | 532 | repo.fileservice.prefetch(prefetchfiles) |
|
533 | return orig(self, files) | |
|
533 | return orig(self, files, mtime_boundary) | |
|
534 | 534 | |
|
535 | 535 | |
|
536 | 536 | # Prefetch the logic that compares added and removed files for renames |
@@ -18,7 +18,6 b' from mercurial import (' | |||
|
18 | 18 | mdiff, |
|
19 | 19 | pycompat, |
|
20 | 20 | revlog, |
|
21 | util, | |
|
22 | 21 | ) |
|
23 | 22 | from mercurial.utils import storageutil |
|
24 | 23 | from mercurial.revlogutils import flagutil |
@@ -245,11 +244,11 b' class remotefilelog(object):' | |||
|
245 | 244 | __bool__ = __nonzero__ |
|
246 | 245 | |
|
247 | 246 | def __len__(self): |
|
248 |
if self.filename |
|
|
249 | # The length of .hgtags is used to fast path tag checking. | |
|
250 | # remotefilelog doesn't support .hgtags since the entire .hgtags | |
|
251 | # history is needed. Use the excludepattern setting to make | |
|
252 |
# |
|
|
247 | if self.filename in (b'.hgtags', b'.hgsub', b'.hgsubstate'): | |
|
248 | # Global tag and subrepository support require access to the | |
|
249 | # file history for various performance sensitive operations. | |
|
250 | # excludepattern should be used for repositories depending on | |
|
251 | # those features to fallback to regular filelog. | |
|
253 | 252 | return 0 |
|
254 | 253 | |
|
255 | 254 | raise RuntimeError(b"len not supported") |
@@ -360,17 +359,6 b' class remotefilelog(object):' | |||
|
360 | 359 | ) |
|
361 | 360 | return rev |
|
362 | 361 | |
|
363 | def _processflags(self, text, flags, operation, raw=False): | |
|
364 | """deprecated entry point to access flag processors""" | |
|
365 | msg = b'_processflag(...) use the specialized variant' | |
|
366 | util.nouideprecwarn(msg, b'5.2', stacklevel=2) | |
|
367 | if raw: | |
|
368 | return text, flagutil.processflagsraw(self, text, flags) | |
|
369 | elif operation == b'read': | |
|
370 | return flagutil.processflagsread(self, text, flags) | |
|
371 | else: # write operation | |
|
372 | return flagutil.processflagswrite(self, text, flags) | |
|
373 | ||
|
374 | 362 | def revision(self, node, raw=False): |
|
375 | 363 | """returns the revlog contents at this node. |
|
376 | 364 | this includes the meta data traditionally included in file revlogs. |
@@ -76,6 +76,7 b' from __future__ import absolute_import' | |||
|
76 | 76 | from mercurial.i18n import _ |
|
77 | 77 | from mercurial.pycompat import setattr |
|
78 | 78 | from mercurial import ( |
|
79 | cmdutil, | |
|
79 | 80 | commands, |
|
80 | 81 | dirstate, |
|
81 | 82 | error, |
@@ -153,22 +154,11 b' def _setuplog(ui):' | |||
|
153 | 154 | |
|
154 | 155 | |
|
155 | 156 | def _clonesparsecmd(orig, ui, repo, *args, **opts): |
|
156 |
include |
|
|
157 |
exclude |
|
|
158 |
enableprofile |
|
|
157 | include = opts.get('include') | |
|
158 | exclude = opts.get('exclude') | |
|
159 | enableprofile = opts.get('enable_profile') | |
|
159 | 160 | narrow_pat = opts.get('narrow') |
|
160 | include = exclude = enableprofile = False | |
|
161 | if include_pat: | |
|
162 | pat = include_pat | |
|
163 | include = True | |
|
164 | if exclude_pat: | |
|
165 | pat = exclude_pat | |
|
166 | exclude = True | |
|
167 | if enableprofile_pat: | |
|
168 | pat = enableprofile_pat | |
|
169 | enableprofile = True | |
|
170 | if sum([include, exclude, enableprofile]) > 1: | |
|
171 | raise error.Abort(_(b"too many flags specified.")) | |
|
161 | ||
|
172 | 162 | # if --narrow is passed, it means they are includes and excludes for narrow |
|
173 | 163 | # clone |
|
174 | 164 | if not narrow_pat and (include or exclude or enableprofile): |
@@ -176,7 +166,6 b' def _clonesparsecmd(orig, ui, repo, *arg' | |||
|
176 | 166 | def clonesparse(orig, ctx, *args, **kwargs): |
|
177 | 167 | sparse.updateconfig( |
|
178 | 168 | ctx.repo().unfiltered(), |
|
179 | pat, | |
|
180 | 169 | {}, |
|
181 | 170 | include=include, |
|
182 | 171 | exclude=exclude, |
@@ -214,7 +203,7 b' def _setupadd(ui):' | |||
|
214 | 203 | for pat in pats: |
|
215 | 204 | dirname, basename = util.split(pat) |
|
216 | 205 | dirs.add(dirname) |
|
217 |
sparse.updateconfig(repo, |
|
|
206 | sparse.updateconfig(repo, opts, include=list(dirs)) | |
|
218 | 207 | return orig(ui, repo, *pats, **opts) |
|
219 | 208 | |
|
220 | 209 | extensions.wrapcommand(commands.table, b'add', _add) |
@@ -286,18 +275,54 b' def _setupdirstate(ui):' | |||
|
286 | 275 | @command( |
|
287 | 276 | b'debugsparse', |
|
288 | 277 | [ |
|
289 | (b'I', b'include', False, _(b'include files in the sparse checkout')), | |
|
290 | (b'X', b'exclude', False, _(b'exclude files in the sparse checkout')), | |
|
291 | (b'd', b'delete', False, _(b'delete an include/exclude rule')), | |
|
278 | ( | |
|
279 | b'I', | |
|
280 | b'include', | |
|
281 | [], | |
|
282 | _(b'include files in the sparse checkout'), | |
|
283 | _(b'PATTERN'), | |
|
284 | ), | |
|
285 | ( | |
|
286 | b'X', | |
|
287 | b'exclude', | |
|
288 | [], | |
|
289 | _(b'exclude files in the sparse checkout'), | |
|
290 | _(b'PATTERN'), | |
|
291 | ), | |
|
292 | ( | |
|
293 | b'd', | |
|
294 | b'delete', | |
|
295 | [], | |
|
296 | _(b'delete an include/exclude rule'), | |
|
297 | _(b'PATTERN'), | |
|
298 | ), | |
|
292 | 299 | ( |
|
293 | 300 | b'f', |
|
294 | 301 | b'force', |
|
295 | 302 | False, |
|
296 | 303 | _(b'allow changing rules even with pending changes'), |
|
297 | 304 | ), |
|
298 | (b'', b'enable-profile', False, _(b'enables the specified profile')), | |
|
299 | (b'', b'disable-profile', False, _(b'disables the specified profile')), | |
|
300 | (b'', b'import-rules', False, _(b'imports rules from a file')), | |
|
305 | ( | |
|
306 | b'', | |
|
307 | b'enable-profile', | |
|
308 | [], | |
|
309 | _(b'enables the specified profile'), | |
|
310 | _(b'PATTERN'), | |
|
311 | ), | |
|
312 | ( | |
|
313 | b'', | |
|
314 | b'disable-profile', | |
|
315 | [], | |
|
316 | _(b'disables the specified profile'), | |
|
317 | _(b'PATTERN'), | |
|
318 | ), | |
|
319 | ( | |
|
320 | b'', | |
|
321 | b'import-rules', | |
|
322 | [], | |
|
323 | _(b'imports rules from a file'), | |
|
324 | _(b'PATTERN'), | |
|
325 | ), | |
|
301 | 326 | (b'', b'clear-rules', False, _(b'clears local include/exclude rules')), |
|
302 | 327 | ( |
|
303 | 328 | b'', |
@@ -308,10 +333,10 b' def _setupdirstate(ui):' | |||
|
308 | 333 | (b'', b'reset', False, _(b'makes the repo full again')), |
|
309 | 334 | ] |
|
310 | 335 | + commands.templateopts, |
|
311 |
_(b'[--OPTION] |
|
|
336 | _(b'[--OPTION]'), | |
|
312 | 337 | helpbasic=True, |
|
313 | 338 | ) |
|
314 |
def debugsparse(ui, repo, * |
|
|
339 | def debugsparse(ui, repo, **opts): | |
|
315 | 340 | """make the current checkout sparse, or edit the existing checkout |
|
316 | 341 | |
|
317 | 342 | The sparse command is used to make the current checkout sparse. |
@@ -363,19 +388,13 b' def debugsparse(ui, repo, *pats, **opts)' | |||
|
363 | 388 | delete = opts.get(b'delete') |
|
364 | 389 | refresh = opts.get(b'refresh') |
|
365 | 390 | reset = opts.get(b'reset') |
|
366 | count = sum( | |
|
367 | [ | |
|
368 | include, | |
|
369 | exclude, | |
|
370 | enableprofile, | |
|
371 | disableprofile, | |
|
372 | delete, | |
|
373 | importrules, | |
|
374 | refresh, | |
|
375 | clearrules, | |
|
376 | reset, | |
|
377 | ] | |
|
391 | action = cmdutil.check_at_most_one_arg( | |
|
392 | opts, b'import_rules', b'clear_rules', b'refresh' | |
|
378 | 393 | ) |
|
394 | updateconfig = bool( | |
|
395 | include or exclude or delete or reset or enableprofile or disableprofile | |
|
396 | ) | |
|
397 | count = sum([updateconfig, bool(action)]) | |
|
379 | 398 | if count > 1: |
|
380 | 399 | raise error.Abort(_(b"too many flags specified")) |
|
381 | 400 | |
@@ -397,10 +416,9 b' def debugsparse(ui, repo, *pats, **opts)' | |||
|
397 | 416 | ) |
|
398 | 417 | ) |
|
399 | 418 | |
|
400 | if include or exclude or delete or reset or enableprofile or disableprofile: | |
|
419 | if updateconfig: | |
|
401 | 420 | sparse.updateconfig( |
|
402 | 421 | repo, |
|
403 | pats, | |
|
404 | 422 | opts, |
|
405 | 423 | include=include, |
|
406 | 424 | exclude=exclude, |
@@ -412,7 +430,7 b' def debugsparse(ui, repo, *pats, **opts)' | |||
|
412 | 430 | ) |
|
413 | 431 | |
|
414 | 432 | if importrules: |
|
415 |
sparse.importfromfiles(repo, opts, |
|
|
433 | sparse.importfromfiles(repo, opts, importrules, force=force) | |
|
416 | 434 | |
|
417 | 435 | if clearrules: |
|
418 | 436 | sparse.clearrules(repo, force=force) |
@@ -47,6 +47,8 b' import re' | |||
|
47 | 47 | from mercurial.i18n import _ |
|
48 | 48 | from mercurial.node import short |
|
49 | 49 | from mercurial import ( |
|
50 | cmdutil, | |
|
51 | extensions, | |
|
50 | 52 | pycompat, |
|
51 | 53 | registrar, |
|
52 | 54 | ) |
@@ -215,6 +217,23 b' def reposetup(ui, repo):' | |||
|
215 | 217 | repo.adddatafilter(name, fn) |
|
216 | 218 | |
|
217 | 219 | |
|
220 | def wrap_revert(orig, repo, ctx, names, uipathfn, actions, *args, **kwargs): | |
|
221 | # reset dirstate cache for file we touch | |
|
222 | ds = repo.dirstate | |
|
223 | with ds.parentchange(): | |
|
224 | for filename in actions[b'revert'][0]: | |
|
225 | entry = ds.get_entry(filename) | |
|
226 | if entry is not None: | |
|
227 | if entry.p1_tracked: | |
|
228 | ds.update_file( | |
|
229 | filename, | |
|
230 | entry.tracked, | |
|
231 | p1_tracked=True, | |
|
232 | p2_info=entry.p2_info, | |
|
233 | ) | |
|
234 | return orig(repo, ctx, names, uipathfn, actions, *args, **kwargs) | |
|
235 | ||
|
236 | ||
|
218 | 237 | def extsetup(ui): |
|
219 | 238 | # deprecated config: win32text.warn |
|
220 | 239 | if ui.configbool(b'win32text', b'warn'): |
@@ -224,3 +243,4 b' def extsetup(ui):' | |||
|
224 | 243 | b"https://mercurial-scm.org/wiki/Win32TextExtension\n" |
|
225 | 244 | ) |
|
226 | 245 | ) |
|
246 | extensions.wrapfunction(cmdutil, '_performrevert', wrap_revert) |
@@ -22,6 +22,7 b' from . import (' | |||
|
22 | 22 | error, |
|
23 | 23 | obsutil, |
|
24 | 24 | pycompat, |
|
25 | requirements, | |
|
25 | 26 | scmutil, |
|
26 | 27 | txnutil, |
|
27 | 28 | util, |
@@ -36,11 +37,9 b' from .utils import (' | |||
|
36 | 37 | # custom styles |
|
37 | 38 | activebookmarklabel = b'bookmarks.active bookmarks.current' |
|
38 | 39 | |
|
39 | BOOKMARKS_IN_STORE_REQUIREMENT = b'bookmarksinstore' | |
|
40 | ||
|
41 | 40 | |
|
42 | 41 | def bookmarksinstore(repo): |
|
43 | return BOOKMARKS_IN_STORE_REQUIREMENT in repo.requirements | |
|
42 | return requirements.BOOKMARKS_IN_STORE_REQUIREMENT in repo.requirements | |
|
44 | 43 | |
|
45 | 44 | |
|
46 | 45 | def bookmarksvfs(repo): |
@@ -213,7 +212,11 b' class bmstore(object):' | |||
|
213 | 212 | The transaction is then responsible for updating the file content.""" |
|
214 | 213 | location = b'' if bookmarksinstore(self._repo) else b'plain' |
|
215 | 214 | tr.addfilegenerator( |
|
216 | b'bookmarks', (b'bookmarks',), self._write, location=location | |
|
215 | b'bookmarks', | |
|
216 | (b'bookmarks',), | |
|
217 | self._write, | |
|
218 | location=location, | |
|
219 | post_finalize=True, | |
|
217 | 220 | ) |
|
218 | 221 | tr.hookargs[b'bookmark_moved'] = b'1' |
|
219 | 222 |
@@ -17,6 +17,7 b' from .node import (' | |||
|
17 | 17 | from . import ( |
|
18 | 18 | encoding, |
|
19 | 19 | error, |
|
20 | obsolete, | |
|
20 | 21 | pycompat, |
|
21 | 22 | scmutil, |
|
22 | 23 | util, |
@@ -184,7 +185,7 b' class branchcache(object):' | |||
|
184 | 185 | |
|
185 | 186 | The first line is used to check if the cache is still valid. If the |
|
186 | 187 | branch cache is for a filtered repo view, an optional third hash is |
|
187 | included that hashes the hashes of all filtered revisions. | |
|
188 | included that hashes the hashes of all filtered and obsolete revisions. | |
|
188 | 189 | |
|
189 | 190 | The open/closed state is represented by a single letter 'o' or 'c'. |
|
190 | 191 | This field can be used to avoid changelog reads when determining if a |
@@ -351,16 +352,25 b' class branchcache(object):' | |||
|
351 | 352 | return filename |
|
352 | 353 | |
|
353 | 354 | def validfor(self, repo): |
|
354 |
""" |
|
|
355 | """check that cache contents are valid for (a subset of) this repo | |
|
355 | 356 | |
|
356 |
- False when |
|
|
357 |
- True when cache is up |
|
|
357 | - False when the order of changesets changed or if we detect a strip. | |
|
358 | - True when cache is up-to-date for the current repo or its subset.""" | |
|
358 | 359 | try: |
|
359 |
|
|
|
360 | self.filteredhash == scmutil.filteredhash(repo, self.tiprev) | |
|
361 | ) | |
|
360 | node = repo.changelog.node(self.tiprev) | |
|
362 | 361 | except IndexError: |
|
362 | # changesets were stripped and now we don't even have enough to | |
|
363 | # find tiprev | |
|
363 | 364 | return False |
|
365 | if self.tipnode != node: | |
|
366 | # tiprev doesn't correspond to tipnode: repo was stripped, or this | |
|
367 | # repo has a different order of changesets | |
|
368 | return False | |
|
369 | tiphash = scmutil.filteredhash(repo, self.tiprev, needobsolete=True) | |
|
370 | # hashes don't match if this repo view has a different set of filtered | |
|
371 | # revisions (e.g. due to phase changes) or obsolete revisions (e.g. | |
|
372 | # history was rewritten) | |
|
373 | return self.filteredhash == tiphash | |
|
364 | 374 | |
|
365 | 375 | def _branchtip(self, heads): |
|
366 | 376 | """Return tuple with last open head in heads and false, |
@@ -478,6 +488,9 b' class branchcache(object):' | |||
|
478 | 488 | # use the faster unfiltered parent accessor. |
|
479 | 489 | parentrevs = repo.unfiltered().changelog.parentrevs |
|
480 | 490 | |
|
491 | # Faster than using ctx.obsolete() | |
|
492 | obsrevs = obsolete.getrevs(repo, b'obsolete') | |
|
493 | ||
|
481 | 494 | for branch, newheadrevs in pycompat.iteritems(newbranches): |
|
482 | 495 | # For every branch, compute the new branchheads. |
|
483 | 496 | # A branchhead is a revision such that no descendant is on |
@@ -514,10 +527,15 b' class branchcache(object):' | |||
|
514 | 527 | # checks can be skipped. Otherwise, the ancestors of the |
|
515 | 528 | # "uncertain" set are removed from branchheads. |
|
516 | 529 | # This computation is heavy and avoided if at all possible. |
|
517 |
bheads = self._entries. |
|
|
530 | bheads = self._entries.get(branch, []) | |
|
518 | 531 | bheadset = {cl.rev(node) for node in bheads} |
|
519 | 532 | uncertain = set() |
|
520 | 533 | for newrev in sorted(newheadrevs): |
|
534 | if newrev in obsrevs: | |
|
535 | # We ignore obsolete changesets as they shouldn't be | |
|
536 | # considered heads. | |
|
537 | continue | |
|
538 | ||
|
521 | 539 | if not bheadset: |
|
522 | 540 | bheadset.add(newrev) |
|
523 | 541 | continue |
@@ -525,13 +543,22 b' class branchcache(object):' | |||
|
525 | 543 | parents = [p for p in parentrevs(newrev) if p != nullrev] |
|
526 | 544 | samebranch = set() |
|
527 | 545 | otherbranch = set() |
|
546 | obsparents = set() | |
|
528 | 547 | for p in parents: |
|
529 |
if p in |
|
|
548 | if p in obsrevs: | |
|
549 | # We ignored this obsolete changeset earlier, but now | |
|
550 | # that it has non-ignored children, we need to make | |
|
551 | # sure their ancestors are not considered heads. To | |
|
552 | # achieve that, we will simply treat this obsolete | |
|
553 | # changeset as a parent from other branch. | |
|
554 | obsparents.add(p) | |
|
555 | elif p in bheadset or getbranchinfo(p)[0] == branch: | |
|
530 | 556 | samebranch.add(p) |
|
531 | 557 | else: |
|
532 | 558 | otherbranch.add(p) |
|
533 |
if |
|
|
559 | if not (len(bheadset) == len(samebranch) == 1): | |
|
534 | 560 | uncertain.update(otherbranch) |
|
561 | uncertain.update(obsparents) | |
|
535 | 562 | bheadset.difference_update(samebranch) |
|
536 | 563 | bheadset.add(newrev) |
|
537 | 564 | |
@@ -540,11 +567,12 b' class branchcache(object):' | |||
|
540 | 567 | topoheads = set(cl.headrevs()) |
|
541 | 568 | if bheadset - topoheads: |
|
542 | 569 | floorrev = min(bheadset) |
|
543 | ancestors = set(cl.ancestors(newheadrevs, floorrev)) | |
|
570 | if floorrev <= max(uncertain): | |
|
571 | ancestors = set(cl.ancestors(uncertain, floorrev)) | |
|
544 | 572 | bheadset -= ancestors |
|
545 |
|
|
|
546 |
self[branch] = [cl.node(rev) for rev in bhead |
|
|
547 |
tiprev = |
|
|
573 | if bheadset: | |
|
574 | self[branch] = [cl.node(rev) for rev in sorted(bheadset)] | |
|
575 | tiprev = max(newheadrevs) | |
|
548 | 576 | if tiprev > ntiprev: |
|
549 | 577 | ntiprev = tiprev |
|
550 | 578 | |
@@ -553,15 +581,24 b' class branchcache(object):' | |||
|
553 | 581 | self.tipnode = cl.node(ntiprev) |
|
554 | 582 | |
|
555 | 583 | if not self.validfor(repo): |
|
556 | # cache key are not valid anymore | |
|
584 | # old cache key is now invalid for the repo, but we've just updated | |
|
585 | # the cache and we assume it's valid, so let's make the cache key | |
|
586 | # valid as well by recomputing it from the cached data | |
|
557 | 587 | self.tipnode = repo.nullid |
|
558 | 588 | self.tiprev = nullrev |
|
559 | 589 | for heads in self.iterheads(): |
|
590 | if not heads: | |
|
591 | # all revisions on a branch are obsolete | |
|
592 | continue | |
|
593 | # note: tiprev is not necessarily the tip revision of repo, | |
|
594 | # because the tip could be obsolete (i.e. not a head) | |
|
560 | 595 | tiprev = max(cl.rev(node) for node in heads) |
|
561 | 596 | if tiprev > self.tiprev: |
|
562 | 597 | self.tipnode = cl.node(tiprev) |
|
563 | 598 | self.tiprev = tiprev |
|
564 |
self.filteredhash = scmutil.filteredhash( |
|
|
599 | self.filteredhash = scmutil.filteredhash( | |
|
600 | repo, self.tiprev, needobsolete=True | |
|
601 | ) | |
|
565 | 602 | |
|
566 | 603 | duration = util.timer() - starttime |
|
567 | 604 | repo.ui.log( |
@@ -1886,7 +1886,8 b' def addpartbundlestream2(bundler, repo, ' | |||
|
1886 | 1886 | filecount, bytecount, it = streamclone.generatev2( |
|
1887 | 1887 | repo, includepats, excludepats, includeobsmarkers |
|
1888 | 1888 | ) |
|
1889 |
requirements = |
|
|
1889 | requirements = streamclone.streamed_requirements(repo) | |
|
1890 | requirements = _formatrequirementsspec(requirements) | |
|
1890 | 1891 | part = bundler.newpart(b'stream2', data=it) |
|
1891 | 1892 | part.addparam(b'bytecount', b'%d' % bytecount, mandatory=True) |
|
1892 | 1893 | part.addparam(b'filecount', b'%d' % filecount, mandatory=True) |
@@ -2419,7 +2420,7 b' def handlebookmark(op, inpart):' | |||
|
2419 | 2420 | op.records.add(b'bookmarks', record) |
|
2420 | 2421 | else: |
|
2421 | 2422 | raise error.ProgrammingError( |
|
2422 | b'unkown bookmark mode: %s' % bookmarksmode | |
|
2423 | b'unknown bookmark mode: %s' % bookmarksmode | |
|
2423 | 2424 | ) |
|
2424 | 2425 | |
|
2425 | 2426 |
@@ -195,7 +195,7 b' def parsebundlespec(repo, spec, strict=T' | |||
|
195 | 195 | # repo supports and error if the bundle isn't compatible. |
|
196 | 196 | if version == b'packed1' and b'requirements' in params: |
|
197 | 197 | requirements = set(params[b'requirements'].split(b',')) |
|
198 |
missingreqs = requirements - re |
|
|
198 | missingreqs = requirements - requirementsmod.STREAM_FIXED_REQUIREMENTS | |
|
199 | 199 | if missingreqs: |
|
200 | 200 | raise error.UnsupportedBundleSpecification( |
|
201 | 201 | _(b'missing support for repository features: %s') |
@@ -61,11 +61,13 b' static PyObject *dirstate_item_new(PyTyp' | |||
|
61 | 61 | int p2_info; |
|
62 | 62 | int has_meaningful_data; |
|
63 | 63 | int has_meaningful_mtime; |
|
64 | int mtime_second_ambiguous; | |
|
64 | 65 | int mode; |
|
65 | 66 | int size; |
|
66 | 67 | int mtime_s; |
|
67 | 68 | int mtime_ns; |
|
68 | 69 | PyObject *parentfiledata; |
|
70 | PyObject *mtime; | |
|
69 | 71 | PyObject *fallback_exec; |
|
70 | 72 | PyObject *fallback_symlink; |
|
71 | 73 | static char *keywords_name[] = { |
@@ -78,6 +80,7 b' static PyObject *dirstate_item_new(PyTyp' | |||
|
78 | 80 | p2_info = 0; |
|
79 | 81 | has_meaningful_mtime = 1; |
|
80 | 82 | has_meaningful_data = 1; |
|
83 | mtime_second_ambiguous = 0; | |
|
81 | 84 | parentfiledata = Py_None; |
|
82 | 85 | fallback_exec = Py_None; |
|
83 | 86 | fallback_symlink = Py_None; |
@@ -118,10 +121,18 b' static PyObject *dirstate_item_new(PyTyp' | |||
|
118 | 121 | } |
|
119 | 122 | |
|
120 | 123 | if (parentfiledata != Py_None) { |
|
121 |
if (!PyArg_ParseTuple(parentfiledata, "ii |
|
|
122 |
&mtime |
|
|
124 | if (!PyArg_ParseTuple(parentfiledata, "iiO", &mode, &size, | |
|
125 | &mtime)) { | |
|
123 | 126 | return NULL; |
|
124 | 127 | } |
|
128 | if (mtime != Py_None) { | |
|
129 | if (!PyArg_ParseTuple(mtime, "iii", &mtime_s, &mtime_ns, | |
|
130 | &mtime_second_ambiguous)) { | |
|
131 | return NULL; | |
|
132 | } | |
|
133 | } else { | |
|
134 | has_meaningful_mtime = 0; | |
|
135 | } | |
|
125 | 136 | } else { |
|
126 | 137 | has_meaningful_data = 0; |
|
127 | 138 | has_meaningful_mtime = 0; |
@@ -130,6 +141,9 b' static PyObject *dirstate_item_new(PyTyp' | |||
|
130 | 141 | t->flags |= dirstate_flag_has_meaningful_data; |
|
131 | 142 | t->mode = mode; |
|
132 | 143 | t->size = size; |
|
144 | if (mtime_second_ambiguous) { | |
|
145 | t->flags |= dirstate_flag_mtime_second_ambiguous; | |
|
146 | } | |
|
133 | 147 | } else { |
|
134 | 148 | t->mode = 0; |
|
135 | 149 | t->size = 0; |
@@ -255,7 +269,8 b' static inline int dirstate_item_c_v1_mti' | |||
|
255 | 269 | } else if (!(self->flags & dirstate_flag_has_mtime) || |
|
256 | 270 | !(self->flags & dirstate_flag_p1_tracked) || |
|
257 | 271 | !(self->flags & dirstate_flag_wc_tracked) || |
|
258 |
(self->flags & dirstate_flag_p2_info) |
|
|
272 | (self->flags & dirstate_flag_p2_info) || | |
|
273 | (self->flags & dirstate_flag_mtime_second_ambiguous)) { | |
|
259 | 274 | return ambiguous_time; |
|
260 | 275 | } else { |
|
261 | 276 | return self->mtime_s; |
@@ -311,33 +326,30 b' static PyObject *dirstate_item_v1_mtime(' | |||
|
311 | 326 | return PyInt_FromLong(dirstate_item_c_v1_mtime(self)); |
|
312 | 327 | }; |
|
313 | 328 | |
|
314 | static PyObject *dirstate_item_need_delay(dirstateItemObject *self, | |
|
315 | PyObject *now) | |
|
316 | { | |
|
317 | int now_s; | |
|
318 | int now_ns; | |
|
319 | if (!PyArg_ParseTuple(now, "ii", &now_s, &now_ns)) { | |
|
320 | return NULL; | |
|
321 | } | |
|
322 | if (dirstate_item_c_v1_state(self) == 'n' && self->mtime_s == now_s) { | |
|
323 | Py_RETURN_TRUE; | |
|
324 | } else { | |
|
325 | Py_RETURN_FALSE; | |
|
326 | } | |
|
327 | }; | |
|
328 | ||
|
329 | 329 | static PyObject *dirstate_item_mtime_likely_equal_to(dirstateItemObject *self, |
|
330 | 330 | PyObject *other) |
|
331 | 331 | { |
|
332 | 332 | int other_s; |
|
333 | 333 | int other_ns; |
|
334 | if (!PyArg_ParseTuple(other, "ii", &other_s, &other_ns)) { | |
|
334 | int other_second_ambiguous; | |
|
335 | if (!PyArg_ParseTuple(other, "iii", &other_s, &other_ns, | |
|
336 | &other_second_ambiguous)) { | |
|
335 | 337 | return NULL; |
|
336 | 338 | } |
|
337 |
if ((self->flags & dirstate_flag_has_mtime) |
|
|
338 | self->mtime_s == other_s && | |
|
339 | (self->mtime_ns == other_ns || self->mtime_ns == 0 || | |
|
340 | other_ns == 0)) { | |
|
339 | if (!(self->flags & dirstate_flag_has_mtime)) { | |
|
340 | Py_RETURN_FALSE; | |
|
341 | } | |
|
342 | if (self->mtime_s != other_s) { | |
|
343 | Py_RETURN_FALSE; | |
|
344 | } | |
|
345 | if (self->mtime_ns == 0 || other_ns == 0) { | |
|
346 | if (self->flags & dirstate_flag_mtime_second_ambiguous) { | |
|
347 | Py_RETURN_FALSE; | |
|
348 | } else { | |
|
349 | Py_RETURN_TRUE; | |
|
350 | } | |
|
351 | } | |
|
352 | if (self->mtime_ns == other_ns) { | |
|
341 | 353 | Py_RETURN_TRUE; |
|
342 | 354 | } else { |
|
343 | 355 | Py_RETURN_FALSE; |
@@ -438,14 +450,6 b' static PyObject *dirstate_item_from_v2_m' | |||
|
438 | 450 | dirstate_flag_has_meaningful_data | |
|
439 | 451 | dirstate_flag_has_mtime); |
|
440 | 452 | } |
|
441 | if (t->flags & dirstate_flag_mtime_second_ambiguous) { | |
|
442 | /* The current code is not able to do the more subtle comparison | |
|
443 | * that the MTIME_SECOND_AMBIGUOUS requires. So we ignore the | |
|
444 | * mtime */ | |
|
445 | t->flags &= ~(dirstate_flag_mtime_second_ambiguous | | |
|
446 | dirstate_flag_has_meaningful_data | | |
|
447 | dirstate_flag_has_mtime); | |
|
448 | } | |
|
449 | 453 | t->mode = 0; |
|
450 | 454 | if (t->flags & dirstate_flag_has_meaningful_data) { |
|
451 | 455 | if (t->flags & dirstate_flag_mode_exec_perm) { |
@@ -474,14 +478,28 b' static PyObject *dirstate_item_set_possi' | |||
|
474 | 478 | static PyObject *dirstate_item_set_clean(dirstateItemObject *self, |
|
475 | 479 | PyObject *args) |
|
476 | 480 | { |
|
477 | int size, mode, mtime_s, mtime_ns; | |
|
478 | if (!PyArg_ParseTuple(args, "ii(ii)", &mode, &size, &mtime_s, | |
|
479 | &mtime_ns)) { | |
|
481 | int size, mode, mtime_s, mtime_ns, mtime_second_ambiguous; | |
|
482 | PyObject *mtime; | |
|
483 | mtime_s = 0; | |
|
484 | mtime_ns = 0; | |
|
485 | mtime_second_ambiguous = 0; | |
|
486 | if (!PyArg_ParseTuple(args, "iiO", &mode, &size, &mtime)) { | |
|
480 | 487 | return NULL; |
|
481 | 488 | } |
|
489 | if (mtime != Py_None) { | |
|
490 | if (!PyArg_ParseTuple(mtime, "iii", &mtime_s, &mtime_ns, | |
|
491 | &mtime_second_ambiguous)) { | |
|
492 | return NULL; | |
|
493 | } | |
|
494 | } else { | |
|
495 | self->flags &= ~dirstate_flag_has_mtime; | |
|
496 | } | |
|
482 | 497 | self->flags = dirstate_flag_wc_tracked | dirstate_flag_p1_tracked | |
|
483 | 498 | dirstate_flag_has_meaningful_data | |
|
484 | 499 | dirstate_flag_has_mtime; |
|
500 | if (mtime_second_ambiguous) { | |
|
501 | self->flags |= dirstate_flag_mtime_second_ambiguous; | |
|
502 | } | |
|
485 | 503 | self->mode = mode; |
|
486 | 504 | self->size = size; |
|
487 | 505 | self->mtime_s = mtime_s; |
@@ -530,8 +548,6 b' static PyMethodDef dirstate_item_methods' | |||
|
530 | 548 | "return a \"size\" suitable for v1 serialization"}, |
|
531 | 549 | {"v1_mtime", (PyCFunction)dirstate_item_v1_mtime, METH_NOARGS, |
|
532 | 550 | "return a \"mtime\" suitable for v1 serialization"}, |
|
533 | {"need_delay", (PyCFunction)dirstate_item_need_delay, METH_O, | |
|
534 | "True if the stored mtime would be ambiguous with the current time"}, | |
|
535 | 551 | {"mtime_likely_equal_to", (PyCFunction)dirstate_item_mtime_likely_equal_to, |
|
536 | 552 | METH_O, "True if the stored mtime is likely equal to the given mtime"}, |
|
537 | 553 | {"from_v1_data", (PyCFunction)dirstate_item_from_v1_meth, |
@@ -904,12 +920,9 b' static PyObject *pack_dirstate(PyObject ' | |||
|
904 | 920 | Py_ssize_t nbytes, pos, l; |
|
905 | 921 | PyObject *k, *v = NULL, *pn; |
|
906 | 922 | char *p, *s; |
|
907 | int now_s; | |
|
908 | int now_ns; | |
|
909 | 923 | |
|
910 |
if (!PyArg_ParseTuple(args, "O!O!O! |
|
|
911 |
|
|
|
912 | &now_s, &now_ns)) { | |
|
924 | if (!PyArg_ParseTuple(args, "O!O!O!:pack_dirstate", &PyDict_Type, &map, | |
|
925 | &PyDict_Type, ©map, &PyTuple_Type, &pl)) { | |
|
913 | 926 | return NULL; |
|
914 | 927 | } |
|
915 | 928 | |
@@ -978,21 +991,6 b' static PyObject *pack_dirstate(PyObject ' | |||
|
978 | 991 | mode = dirstate_item_c_v1_mode(tuple); |
|
979 | 992 | size = dirstate_item_c_v1_size(tuple); |
|
980 | 993 | mtime = dirstate_item_c_v1_mtime(tuple); |
|
981 | if (state == 'n' && tuple->mtime_s == now_s) { | |
|
982 | /* See pure/parsers.py:pack_dirstate for why we do | |
|
983 | * this. */ | |
|
984 | mtime = -1; | |
|
985 | mtime_unset = (PyObject *)dirstate_item_from_v1_data( | |
|
986 | state, mode, size, mtime); | |
|
987 | if (!mtime_unset) { | |
|
988 | goto bail; | |
|
989 | } | |
|
990 | if (PyDict_SetItem(map, k, mtime_unset) == -1) { | |
|
991 | goto bail; | |
|
992 | } | |
|
993 | Py_DECREF(mtime_unset); | |
|
994 | mtime_unset = NULL; | |
|
995 | } | |
|
996 | 994 | *p++ = state; |
|
997 | 995 | putbe32((uint32_t)mode, p); |
|
998 | 996 | putbe32((uint32_t)size, p + 4); |
@@ -103,8 +103,7 b' struct indexObjectStruct {' | |||
|
103 | 103 | */ |
|
104 | 104 | long rust_ext_compat; /* compatibility with being used in rust |
|
105 | 105 | extensions */ |
|
106 | char format_version; /* size of index headers. Differs in v1 v.s. v2 | |
|
107 | format */ | |
|
106 | long format_version; /* format version selector (format_*) */ | |
|
108 | 107 | }; |
|
109 | 108 | |
|
110 | 109 | static Py_ssize_t index_length(const indexObject *self) |
@@ -120,9 +119,11 b' static Py_ssize_t inline_scan(indexObjec' | |||
|
120 | 119 | static int index_find_node(indexObject *self, const char *node); |
|
121 | 120 | |
|
122 | 121 | #if LONG_MAX == 0x7fffffffL |
|
123 |
static const char *const tuple_format = |
|
|
122 | static const char *const tuple_format = | |
|
123 | PY23("Kiiiiiis#KiBBi", "Kiiiiiiy#KiBBi"); | |
|
124 | 124 | #else |
|
125 |
static const char *const tuple_format = |
|
|
125 | static const char *const tuple_format = | |
|
126 | PY23("kiiiiiis#kiBBi", "kiiiiiiy#kiBBi"); | |
|
126 | 127 | #endif |
|
127 | 128 | |
|
128 | 129 | /* A RevlogNG v1 index entry is 64 bytes long. */ |
@@ -131,10 +132,54 b' static const long v1_entry_size = 64;' | |||
|
131 | 132 | /* A Revlogv2 index entry is 96 bytes long. */ |
|
132 | 133 | static const long v2_entry_size = 96; |
|
133 | 134 | |
|
134 | static const long format_v1 = 1; /* Internal only, could be any number */ | |
|
135 | static const long format_v2 = 2; /* Internal only, could be any number */ | |
|
135 | /* A Changelogv2 index entry is 96 bytes long. */ | |
|
136 | static const long cl2_entry_size = 96; | |
|
137 | ||
|
138 | /* Internal format version. | |
|
139 | * Must match their counterparts in revlogutils/constants.py */ | |
|
140 | static const long format_v1 = 1; /* constants.py: REVLOGV1 */ | |
|
141 | static const long format_v2 = 0xDEAD; /* constants.py: REVLOGV2 */ | |
|
142 | static const long format_cl2 = 0xD34D; /* constants.py: CHANGELOGV2 */ | |
|
143 | ||
|
144 | static const long entry_v1_offset_high = 0; | |
|
145 | static const long entry_v1_offset_offset_flags = 4; | |
|
146 | static const long entry_v1_offset_comp_len = 8; | |
|
147 | static const long entry_v1_offset_uncomp_len = 12; | |
|
148 | static const long entry_v1_offset_base_rev = 16; | |
|
149 | static const long entry_v1_offset_link_rev = 20; | |
|
150 | static const long entry_v1_offset_parent_1 = 24; | |
|
151 | static const long entry_v1_offset_parent_2 = 28; | |
|
152 | static const long entry_v1_offset_node_id = 32; | |
|
153 | ||
|
154 | static const long entry_v2_offset_high = 0; | |
|
155 | static const long entry_v2_offset_offset_flags = 4; | |
|
156 | static const long entry_v2_offset_comp_len = 8; | |
|
157 | static const long entry_v2_offset_uncomp_len = 12; | |
|
158 | static const long entry_v2_offset_base_rev = 16; | |
|
159 | static const long entry_v2_offset_link_rev = 20; | |
|
160 | static const long entry_v2_offset_parent_1 = 24; | |
|
161 | static const long entry_v2_offset_parent_2 = 28; | |
|
162 | static const long entry_v2_offset_node_id = 32; | |
|
163 | static const long entry_v2_offset_sidedata_offset = 64; | |
|
164 | static const long entry_v2_offset_sidedata_comp_len = 72; | |
|
165 | static const long entry_v2_offset_all_comp_mode = 76; | |
|
166 | /* next free offset: 77 */ | |
|
167 | ||
|
168 | static const long entry_cl2_offset_high = 0; | |
|
169 | static const long entry_cl2_offset_offset_flags = 4; | |
|
170 | static const long entry_cl2_offset_comp_len = 8; | |
|
171 | static const long entry_cl2_offset_uncomp_len = 12; | |
|
172 | static const long entry_cl2_offset_parent_1 = 16; | |
|
173 | static const long entry_cl2_offset_parent_2 = 20; | |
|
174 | static const long entry_cl2_offset_node_id = 24; | |
|
175 | static const long entry_cl2_offset_sidedata_offset = 56; | |
|
176 | static const long entry_cl2_offset_sidedata_comp_len = 64; | |
|
177 | static const long entry_cl2_offset_all_comp_mode = 68; | |
|
178 | static const long entry_cl2_offset_rank = 69; | |
|
179 | /* next free offset: 73 */ | |
|
136 | 180 | |
|
137 | 181 | static const char comp_mode_inline = 2; |
|
182 | static const char rank_unknown = -1; | |
|
138 | 183 | |
|
139 | 184 | static void raise_revlog_error(void) |
|
140 | 185 | { |
@@ -203,8 +248,19 b' static inline int index_get_parents(inde' | |||
|
203 | 248 | { |
|
204 | 249 | const char *data = index_deref(self, rev); |
|
205 | 250 | |
|
206 | ps[0] = getbe32(data + 24); | |
|
207 |
ps[ |
|
|
251 | if (self->format_version == format_v1) { | |
|
252 | ps[0] = getbe32(data + entry_v1_offset_parent_1); | |
|
253 | ps[1] = getbe32(data + entry_v1_offset_parent_2); | |
|
254 | } else if (self->format_version == format_v2) { | |
|
255 | ps[0] = getbe32(data + entry_v2_offset_parent_1); | |
|
256 | ps[1] = getbe32(data + entry_v2_offset_parent_2); | |
|
257 | } else if (self->format_version == format_cl2) { | |
|
258 | ps[0] = getbe32(data + entry_cl2_offset_parent_1); | |
|
259 | ps[1] = getbe32(data + entry_cl2_offset_parent_2); | |
|
260 | } else { | |
|
261 | raise_revlog_error(); | |
|
262 | return -1; | |
|
263 | } | |
|
208 | 264 | |
|
209 | 265 | /* If index file is corrupted, ps[] may point to invalid revisions. So |
|
210 | 266 | * there is a risk of buffer overflow to trust them unconditionally. */ |
@@ -251,14 +307,36 b' static inline int64_t index_get_start(in' | |||
|
251 | 307 | return 0; |
|
252 | 308 | |
|
253 | 309 | data = index_deref(self, rev); |
|
254 | offset = getbe32(data + 4); | |
|
310 | ||
|
311 | if (self->format_version == format_v1) { | |
|
312 | offset = getbe32(data + entry_v1_offset_offset_flags); | |
|
255 | 313 | if (rev == 0) { |
|
256 | 314 | /* mask out version number for the first entry */ |
|
257 | 315 | offset &= 0xFFFF; |
|
258 | 316 | } else { |
|
259 |
uint32_t offset_high = |
|
|
317 | uint32_t offset_high = | |
|
318 | getbe32(data + entry_v1_offset_high); | |
|
260 | 319 | offset |= ((uint64_t)offset_high) << 32; |
|
261 | 320 | } |
|
321 | } else if (self->format_version == format_v2) { | |
|
322 | offset = getbe32(data + entry_v2_offset_offset_flags); | |
|
323 | if (rev == 0) { | |
|
324 | /* mask out version number for the first entry */ | |
|
325 | offset &= 0xFFFF; | |
|
326 | } else { | |
|
327 | uint32_t offset_high = | |
|
328 | getbe32(data + entry_v2_offset_high); | |
|
329 | offset |= ((uint64_t)offset_high) << 32; | |
|
330 | } | |
|
331 | } else if (self->format_version == format_cl2) { | |
|
332 | uint32_t offset_high = getbe32(data + entry_cl2_offset_high); | |
|
333 | offset = getbe32(data + entry_cl2_offset_offset_flags); | |
|
334 | offset |= ((uint64_t)offset_high) << 32; | |
|
335 | } else { | |
|
336 | raise_revlog_error(); | |
|
337 | return -1; | |
|
338 | } | |
|
339 | ||
|
262 | 340 | return (int64_t)(offset >> 16); |
|
263 | 341 | } |
|
264 | 342 | |
@@ -272,7 +350,16 b' static inline int index_get_length(index' | |||
|
272 | 350 | |
|
273 | 351 | data = index_deref(self, rev); |
|
274 | 352 | |
|
275 | tmp = (int)getbe32(data + 8); | |
|
353 | if (self->format_version == format_v1) { | |
|
354 | tmp = (int)getbe32(data + entry_v1_offset_comp_len); | |
|
355 | } else if (self->format_version == format_v2) { | |
|
356 | tmp = (int)getbe32(data + entry_v2_offset_comp_len); | |
|
357 | } else if (self->format_version == format_cl2) { | |
|
358 | tmp = (int)getbe32(data + entry_cl2_offset_comp_len); | |
|
359 | } else { | |
|
360 | raise_revlog_error(); | |
|
361 | return -1; | |
|
362 | } | |
|
276 | 363 | if (tmp < 0) { |
|
277 | 364 | PyErr_Format(PyExc_OverflowError, |
|
278 | 365 | "revlog entry size out of bound (%d)", tmp); |
@@ -297,7 +384,7 b' static PyObject *index_get(indexObject *' | |||
|
297 | 384 | { |
|
298 | 385 | uint64_t offset_flags, sidedata_offset; |
|
299 | 386 | int comp_len, uncomp_len, base_rev, link_rev, parent_1, parent_2, |
|
300 | sidedata_comp_len; | |
|
387 | sidedata_comp_len, rank = rank_unknown; | |
|
301 | 388 | char data_comp_mode, sidedata_comp_mode; |
|
302 | 389 | const char *c_node_id; |
|
303 | 390 | const char *data; |
@@ -317,42 +404,96 b' static PyObject *index_get(indexObject *' | |||
|
317 | 404 | if (data == NULL) |
|
318 | 405 | return NULL; |
|
319 | 406 | |
|
320 | offset_flags = getbe32(data + 4); | |
|
407 | if (self->format_version == format_v1) { | |
|
408 | offset_flags = getbe32(data + entry_v1_offset_offset_flags); | |
|
321 | 409 | /* |
|
322 | 410 | * The first entry on-disk needs the version number masked out, |
|
323 |
* but this doesn't apply if entries are added to an empty |
|
|
411 | * but this doesn't apply if entries are added to an empty | |
|
412 | * index. | |
|
413 | */ | |
|
414 | if (self->length && pos == 0) | |
|
415 | offset_flags &= 0xFFFF; | |
|
416 | else { | |
|
417 | uint32_t offset_high = | |
|
418 | getbe32(data + entry_v1_offset_high); | |
|
419 | offset_flags |= ((uint64_t)offset_high) << 32; | |
|
420 | } | |
|
421 | ||
|
422 | comp_len = getbe32(data + entry_v1_offset_comp_len); | |
|
423 | uncomp_len = getbe32(data + entry_v1_offset_uncomp_len); | |
|
424 | base_rev = getbe32(data + entry_v1_offset_base_rev); | |
|
425 | link_rev = getbe32(data + entry_v1_offset_link_rev); | |
|
426 | parent_1 = getbe32(data + entry_v1_offset_parent_1); | |
|
427 | parent_2 = getbe32(data + entry_v1_offset_parent_2); | |
|
428 | c_node_id = data + entry_v1_offset_node_id; | |
|
429 | ||
|
430 | sidedata_offset = 0; | |
|
431 | sidedata_comp_len = 0; | |
|
432 | data_comp_mode = comp_mode_inline; | |
|
433 | sidedata_comp_mode = comp_mode_inline; | |
|
434 | } else if (self->format_version == format_v2) { | |
|
435 | offset_flags = getbe32(data + entry_v2_offset_offset_flags); | |
|
436 | /* | |
|
437 | * The first entry on-disk needs the version number masked out, | |
|
438 | * but this doesn't apply if entries are added to an empty | |
|
439 | * index. | |
|
324 | 440 | */ |
|
325 | 441 | if (self->length && pos == 0) |
|
326 | 442 | offset_flags &= 0xFFFF; |
|
327 | 443 | else { |
|
328 |
uint32_t offset_high = |
|
|
444 | uint32_t offset_high = | |
|
445 | getbe32(data + entry_v2_offset_high); | |
|
446 | offset_flags |= ((uint64_t)offset_high) << 32; | |
|
447 | } | |
|
448 | ||
|
449 | comp_len = getbe32(data + entry_v2_offset_comp_len); | |
|
450 | uncomp_len = getbe32(data + entry_v2_offset_uncomp_len); | |
|
451 | base_rev = getbe32(data + entry_v2_offset_base_rev); | |
|
452 | link_rev = getbe32(data + entry_v2_offset_link_rev); | |
|
453 | parent_1 = getbe32(data + entry_v2_offset_parent_1); | |
|
454 | parent_2 = getbe32(data + entry_v2_offset_parent_2); | |
|
455 | c_node_id = data + entry_v2_offset_node_id; | |
|
456 | ||
|
457 | sidedata_offset = | |
|
458 | getbe64(data + entry_v2_offset_sidedata_offset); | |
|
459 | sidedata_comp_len = | |
|
460 | getbe32(data + entry_v2_offset_sidedata_comp_len); | |
|
461 | data_comp_mode = data[entry_v2_offset_all_comp_mode] & 3; | |
|
462 | sidedata_comp_mode = | |
|
463 | ((data[entry_v2_offset_all_comp_mode] >> 2) & 3); | |
|
464 | } else if (self->format_version == format_cl2) { | |
|
465 | uint32_t offset_high = getbe32(data + entry_cl2_offset_high); | |
|
466 | offset_flags = getbe32(data + entry_cl2_offset_offset_flags); | |
|
329 | 467 | offset_flags |= ((uint64_t)offset_high) << 32; |
|
330 | } | |
|
331 | ||
|
332 | comp_len = getbe32(data + 8); | |
|
333 | uncomp_len = getbe32(data + 12); | |
|
334 | base_rev = getbe32(data + 16); | |
|
335 | link_rev = getbe32(data + 20); | |
|
336 | parent_1 = getbe32(data + 24); | |
|
337 | parent_2 = getbe32(data + 28); | |
|
338 | c_node_id = data + 32; | |
|
339 | ||
|
340 | if (self->format_version == format_v1) { | |
|
341 | sidedata_offset = 0; | |
|
342 |
sidedata_ |
|
|
343 | data_comp_mode = comp_mode_inline; | |
|
344 |
sidedata_comp_ |
|
|
468 | comp_len = getbe32(data + entry_cl2_offset_comp_len); | |
|
469 | uncomp_len = getbe32(data + entry_cl2_offset_uncomp_len); | |
|
470 | /* base_rev and link_rev are not stored in changelogv2, but are | |
|
471 | still used by some functions shared with the other revlogs. | |
|
472 | They are supposed to contain links to other revisions, | |
|
473 | but they always point to themselves in the case of a changelog. | |
|
474 | */ | |
|
475 | base_rev = pos; | |
|
476 | link_rev = pos; | |
|
477 | parent_1 = getbe32(data + entry_cl2_offset_parent_1); | |
|
478 | parent_2 = getbe32(data + entry_cl2_offset_parent_2); | |
|
479 | c_node_id = data + entry_cl2_offset_node_id; | |
|
480 | sidedata_offset = | |
|
481 | getbe64(data + entry_cl2_offset_sidedata_offset); | |
|
482 | sidedata_comp_len = | |
|
483 | getbe32(data + entry_cl2_offset_sidedata_comp_len); | |
|
484 | data_comp_mode = data[entry_cl2_offset_all_comp_mode] & 3; | |
|
485 | sidedata_comp_mode = | |
|
486 | ((data[entry_cl2_offset_all_comp_mode] >> 2) & 3); | |
|
487 | rank = getbe32(data + entry_cl2_offset_rank); | |
|
345 | 488 | } else { |
|
346 | sidedata_offset = getbe64(data + 64); | |
|
347 | sidedata_comp_len = getbe32(data + 72); | |
|
348 | data_comp_mode = data[76] & 3; | |
|
349 | sidedata_comp_mode = ((data[76] >> 2) & 3); | |
|
489 | raise_revlog_error(); | |
|
490 | return NULL; | |
|
350 | 491 | } |
|
351 | 492 | |
|
352 | 493 | return Py_BuildValue(tuple_format, offset_flags, comp_len, uncomp_len, |
|
353 | 494 | base_rev, link_rev, parent_1, parent_2, c_node_id, |
|
354 | 495 | self->nodelen, sidedata_offset, sidedata_comp_len, |
|
355 | data_comp_mode, sidedata_comp_mode); | |
|
496 | data_comp_mode, sidedata_comp_mode, rank); | |
|
356 | 497 | } |
|
357 | 498 | /* |
|
358 | 499 | * Pack header information in binary |
@@ -410,6 +551,7 b' static const char *index_node(indexObjec' | |||
|
410 | 551 | { |
|
411 | 552 | Py_ssize_t length = index_length(self); |
|
412 | 553 | const char *data; |
|
554 | const char *node_id; | |
|
413 | 555 | |
|
414 | 556 | if (pos == nullrev) |
|
415 | 557 | return nullid; |
@@ -418,7 +560,19 b' static const char *index_node(indexObjec' | |||
|
418 | 560 | return NULL; |
|
419 | 561 | |
|
420 | 562 | data = index_deref(self, pos); |
|
421 | return data ? data + 32 : NULL; | |
|
563 | ||
|
564 | if (self->format_version == format_v1) { | |
|
565 | node_id = data + entry_v1_offset_node_id; | |
|
566 | } else if (self->format_version == format_v2) { | |
|
567 | node_id = data + entry_v2_offset_node_id; | |
|
568 | } else if (self->format_version == format_cl2) { | |
|
569 | node_id = data + entry_cl2_offset_node_id; | |
|
570 | } else { | |
|
571 | raise_revlog_error(); | |
|
572 | return NULL; | |
|
573 | } | |
|
574 | ||
|
575 | return data ? node_id : NULL; | |
|
422 | 576 | } |
|
423 | 577 | |
|
424 | 578 | /* |
@@ -453,7 +607,7 b' static PyObject *index_append(indexObjec' | |||
|
453 | 607 | { |
|
454 | 608 | uint64_t offset_flags, sidedata_offset; |
|
455 | 609 | int rev, comp_len, uncomp_len, base_rev, link_rev, parent_1, parent_2, |
|
456 | sidedata_comp_len; | |
|
610 | sidedata_comp_len, rank; | |
|
457 | 611 | char data_comp_mode, sidedata_comp_mode; |
|
458 | 612 | Py_ssize_t c_node_id_len; |
|
459 | 613 | const char *c_node_id; |
@@ -464,8 +618,8 b' static PyObject *index_append(indexObjec' | |||
|
464 | 618 | &uncomp_len, &base_rev, &link_rev, &parent_1, |
|
465 | 619 | &parent_2, &c_node_id, &c_node_id_len, |
|
466 | 620 | &sidedata_offset, &sidedata_comp_len, |
|
467 | &data_comp_mode, &sidedata_comp_mode)) { | |
|
468 |
PyErr_SetString(PyExc_TypeError, "1 |
|
|
621 | &data_comp_mode, &sidedata_comp_mode, &rank)) { | |
|
622 | PyErr_SetString(PyExc_TypeError, "12-tuple required"); | |
|
469 | 623 | return NULL; |
|
470 | 624 | } |
|
471 | 625 | |
@@ -501,25 +655,61 b' static PyObject *index_append(indexObjec' | |||
|
501 | 655 | } |
|
502 | 656 | rev = self->length + self->new_length; |
|
503 | 657 | data = self->added + self->entry_size * self->new_length++; |
|
504 | putbe32(offset_flags >> 32, data); | |
|
505 | putbe32(offset_flags & 0xffffffffU, data + 4); | |
|
506 | putbe32(comp_len, data + 8); | |
|
507 | putbe32(uncomp_len, data + 12); | |
|
508 | putbe32(base_rev, data + 16); | |
|
509 | putbe32(link_rev, data + 20); | |
|
510 | putbe32(parent_1, data + 24); | |
|
511 | putbe32(parent_2, data + 28); | |
|
512 | memcpy(data + 32, c_node_id, c_node_id_len); | |
|
513 | /* Padding since SHA-1 is only 20 bytes for now */ | |
|
514 | memset(data + 32 + c_node_id_len, 0, 32 - c_node_id_len); | |
|
515 | if (self->format_version == format_v2) { | |
|
516 | putbe64(sidedata_offset, data + 64); | |
|
517 | putbe32(sidedata_comp_len, data + 72); | |
|
658 | ||
|
659 | memset(data, 0, self->entry_size); | |
|
660 | ||
|
661 | if (self->format_version == format_v1) { | |
|
662 | putbe32(offset_flags >> 32, data + entry_v1_offset_high); | |
|
663 | putbe32(offset_flags & 0xffffffffU, | |
|
664 | data + entry_v1_offset_offset_flags); | |
|
665 | putbe32(comp_len, data + entry_v1_offset_comp_len); | |
|
666 | putbe32(uncomp_len, data + entry_v1_offset_uncomp_len); | |
|
667 | putbe32(base_rev, data + entry_v1_offset_base_rev); | |
|
668 | putbe32(link_rev, data + entry_v1_offset_link_rev); | |
|
669 | putbe32(parent_1, data + entry_v1_offset_parent_1); | |
|
670 | putbe32(parent_2, data + entry_v1_offset_parent_2); | |
|
671 | memcpy(data + entry_v1_offset_node_id, c_node_id, | |
|
672 | c_node_id_len); | |
|
673 | } else if (self->format_version == format_v2) { | |
|
674 | putbe32(offset_flags >> 32, data + entry_v2_offset_high); | |
|
675 | putbe32(offset_flags & 0xffffffffU, | |
|
676 | data + entry_v2_offset_offset_flags); | |
|
677 | putbe32(comp_len, data + entry_v2_offset_comp_len); | |
|
678 | putbe32(uncomp_len, data + entry_v2_offset_uncomp_len); | |
|
679 | putbe32(base_rev, data + entry_v2_offset_base_rev); | |
|
680 | putbe32(link_rev, data + entry_v2_offset_link_rev); | |
|
681 | putbe32(parent_1, data + entry_v2_offset_parent_1); | |
|
682 | putbe32(parent_2, data + entry_v2_offset_parent_2); | |
|
683 | memcpy(data + entry_v2_offset_node_id, c_node_id, | |
|
684 | c_node_id_len); | |
|
685 | putbe64(sidedata_offset, | |
|
686 | data + entry_v2_offset_sidedata_offset); | |
|
687 | putbe32(sidedata_comp_len, | |
|
688 | data + entry_v2_offset_sidedata_comp_len); | |
|
518 | 689 | comp_field = data_comp_mode & 3; |
|
519 | 690 | comp_field = comp_field | (sidedata_comp_mode & 3) << 2; |
|
520 | data[76] = comp_field; | |
|
521 | /* Padding for 96 bytes alignment */ | |
|
522 | memset(data + 77, 0, self->entry_size - 77); | |
|
691 | data[entry_v2_offset_all_comp_mode] = comp_field; | |
|
692 | } else if (self->format_version == format_cl2) { | |
|
693 | putbe32(offset_flags >> 32, data + entry_cl2_offset_high); | |
|
694 | putbe32(offset_flags & 0xffffffffU, | |
|
695 | data + entry_cl2_offset_offset_flags); | |
|
696 | putbe32(comp_len, data + entry_cl2_offset_comp_len); | |
|
697 | putbe32(uncomp_len, data + entry_cl2_offset_uncomp_len); | |
|
698 | putbe32(parent_1, data + entry_cl2_offset_parent_1); | |
|
699 | putbe32(parent_2, data + entry_cl2_offset_parent_2); | |
|
700 | memcpy(data + entry_cl2_offset_node_id, c_node_id, | |
|
701 | c_node_id_len); | |
|
702 | putbe64(sidedata_offset, | |
|
703 | data + entry_cl2_offset_sidedata_offset); | |
|
704 | putbe32(sidedata_comp_len, | |
|
705 | data + entry_cl2_offset_sidedata_comp_len); | |
|
706 | comp_field = data_comp_mode & 3; | |
|
707 | comp_field = comp_field | (sidedata_comp_mode & 3) << 2; | |
|
708 | data[entry_cl2_offset_all_comp_mode] = comp_field; | |
|
709 | putbe32(rank, data + entry_cl2_offset_rank); | |
|
710 | } else { | |
|
711 | raise_revlog_error(); | |
|
712 | return NULL; | |
|
523 | 713 | } |
|
524 | 714 | |
|
525 | 715 | if (self->ntinitialized) |
@@ -574,10 +764,28 b' static PyObject *index_replace_sidedata_' | |||
|
574 | 764 | /* Find the newly added node, offset from the "already on-disk" length |
|
575 | 765 | */ |
|
576 | 766 | data = self->added + self->entry_size * (rev - self->length); |
|
577 | putbe64(offset_flags, data); | |
|
578 | putbe64(sidedata_offset, data + 64); | |
|
579 | putbe32(sidedata_comp_len, data + 72); | |
|
580 | data[76] = (data[76] & ~(3 << 2)) | ((comp_mode & 3) << 2); | |
|
767 | if (self->format_version == format_v2) { | |
|
768 | putbe64(offset_flags, data + entry_v2_offset_high); | |
|
769 | putbe64(sidedata_offset, | |
|
770 | data + entry_v2_offset_sidedata_offset); | |
|
771 | putbe32(sidedata_comp_len, | |
|
772 | data + entry_v2_offset_sidedata_comp_len); | |
|
773 | data[entry_v2_offset_all_comp_mode] = | |
|
774 | (data[entry_v2_offset_all_comp_mode] & ~(3 << 2)) | | |
|
775 | ((comp_mode & 3) << 2); | |
|
776 | } else if (self->format_version == format_cl2) { | |
|
777 | putbe64(offset_flags, data + entry_cl2_offset_high); | |
|
778 | putbe64(sidedata_offset, | |
|
779 | data + entry_cl2_offset_sidedata_offset); | |
|
780 | putbe32(sidedata_comp_len, | |
|
781 | data + entry_cl2_offset_sidedata_comp_len); | |
|
782 | data[entry_cl2_offset_all_comp_mode] = | |
|
783 | (data[entry_cl2_offset_all_comp_mode] & ~(3 << 2)) | | |
|
784 | ((comp_mode & 3) << 2); | |
|
785 | } else { | |
|
786 | raise_revlog_error(); | |
|
787 | return NULL; | |
|
788 | } | |
|
581 | 789 | |
|
582 | 790 | Py_RETURN_NONE; |
|
583 | 791 | } |
@@ -1120,7 +1328,17 b' static inline int index_baserev(indexObj' | |||
|
1120 | 1328 | data = index_deref(self, rev); |
|
1121 | 1329 | if (data == NULL) |
|
1122 | 1330 | return -2; |
|
1123 | result = getbe32(data + 16); | |
|
1331 | ||
|
1332 | if (self->format_version == format_v1) { | |
|
1333 | result = getbe32(data + entry_v1_offset_base_rev); | |
|
1334 | } else if (self->format_version == format_v2) { | |
|
1335 | result = getbe32(data + entry_v2_offset_base_rev); | |
|
1336 | } else if (self->format_version == format_cl2) { | |
|
1337 | return rev; | |
|
1338 | } else { | |
|
1339 | raise_revlog_error(); | |
|
1340 | return -1; | |
|
1341 | } | |
|
1124 | 1342 | |
|
1125 | 1343 | if (result > rev) { |
|
1126 | 1344 | PyErr_Format( |
@@ -2598,8 +2816,10 b' static void index_invalidate_added(index' | |||
|
2598 | 2816 | if (i < 0) |
|
2599 | 2817 | return; |
|
2600 | 2818 | |
|
2601 | for (i = start; i < len; i++) | |
|
2602 | nt_delete_node(&self->nt, index_deref(self, i) + 32); | |
|
2819 | for (i = start; i < len; i++) { | |
|
2820 | const char *node = index_node(self, i); | |
|
2821 | nt_delete_node(&self->nt, node); | |
|
2822 | } | |
|
2603 | 2823 | |
|
2604 | 2824 | self->new_length = start - self->length; |
|
2605 | 2825 | } |
@@ -2732,9 +2952,18 b' static Py_ssize_t inline_scan(indexObjec' | |||
|
2732 | 2952 | while (pos + self->entry_size <= end && pos >= 0) { |
|
2733 | 2953 | uint32_t comp_len, sidedata_comp_len = 0; |
|
2734 | 2954 | /* 3rd element of header is length of compressed inline data */ |
|
2735 | comp_len = getbe32(data + pos + 8); | |
|
2736 | if (self->entry_size == v2_entry_size) { | |
|
2737 | sidedata_comp_len = getbe32(data + pos + 72); | |
|
2955 | if (self->format_version == format_v1) { | |
|
2956 | comp_len = | |
|
2957 | getbe32(data + pos + entry_v1_offset_comp_len); | |
|
2958 | sidedata_comp_len = 0; | |
|
2959 | } else if (self->format_version == format_v2) { | |
|
2960 | comp_len = | |
|
2961 | getbe32(data + pos + entry_v2_offset_comp_len); | |
|
2962 | sidedata_comp_len = getbe32( | |
|
2963 | data + pos + entry_v2_offset_sidedata_comp_len); | |
|
2964 | } else { | |
|
2965 | raise_revlog_error(); | |
|
2966 | return -1; | |
|
2738 | 2967 | } |
|
2739 | 2968 | incr = self->entry_size + comp_len + sidedata_comp_len; |
|
2740 | 2969 | if (offsets) |
@@ -2754,10 +2983,10 b' static Py_ssize_t inline_scan(indexObjec' | |||
|
2754 | 2983 | |
|
2755 | 2984 | static int index_init(indexObject *self, PyObject *args, PyObject *kwargs) |
|
2756 | 2985 | { |
|
2757 |
PyObject *data_obj, *inlined_obj |
|
|
2986 | PyObject *data_obj, *inlined_obj; | |
|
2758 | 2987 | Py_ssize_t size; |
|
2759 | 2988 | |
|
2760 |
static char *kwlist[] = {"data", "inlined", " |
|
|
2989 | static char *kwlist[] = {"data", "inlined", "format", NULL}; | |
|
2761 | 2990 | |
|
2762 | 2991 | /* Initialize before argument-checking to avoid index_dealloc() crash. |
|
2763 | 2992 | */ |
@@ -2774,10 +3003,11 b' static int index_init(indexObject *self,' | |||
|
2774 | 3003 | self->nodelen = 20; |
|
2775 | 3004 | self->nullentry = NULL; |
|
2776 | 3005 | self->rust_ext_compat = 1; |
|
2777 | ||
|
2778 | revlogv2 = NULL; | |
|
2779 |
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO| |
|
|
2780 |
&data_obj, &inlined_obj, |
|
|
3006 | self->format_version = format_v1; | |
|
3007 | ||
|
3008 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|l", kwlist, | |
|
3009 | &data_obj, &inlined_obj, | |
|
3010 | &(self->format_version))) | |
|
2781 | 3011 | return -1; |
|
2782 | 3012 | if (!PyObject_CheckBuffer(data_obj)) { |
|
2783 | 3013 | PyErr_SetString(PyExc_TypeError, |
@@ -2789,17 +3019,18 b' static int index_init(indexObject *self,' | |||
|
2789 | 3019 | return -1; |
|
2790 | 3020 | } |
|
2791 | 3021 | |
|
2792 | if (revlogv2 && PyObject_IsTrue(revlogv2)) { | |
|
2793 | self->format_version = format_v2; | |
|
2794 | self->entry_size = v2_entry_size; | |
|
2795 | } else { | |
|
2796 | self->format_version = format_v1; | |
|
3022 | if (self->format_version == format_v1) { | |
|
2797 | 3023 | self->entry_size = v1_entry_size; |
|
2798 | } | |
|
2799 | ||
|
2800 | self->nullentry = Py_BuildValue( | |
|
2801 | PY23("iiiiiiis#iiBB", "iiiiiiiy#iiBB"), 0, 0, 0, -1, -1, -1, -1, | |
|
2802 | nullid, self->nodelen, 0, 0, comp_mode_inline, comp_mode_inline); | |
|
3024 | } else if (self->format_version == format_v2) { | |
|
3025 | self->entry_size = v2_entry_size; | |
|
3026 | } else if (self->format_version == format_cl2) { | |
|
3027 | self->entry_size = cl2_entry_size; | |
|
3028 | } | |
|
3029 | ||
|
3030 | self->nullentry = | |
|
3031 | Py_BuildValue(PY23("iiiiiiis#iiBBi", "iiiiiiiy#iiBBi"), 0, 0, 0, -1, | |
|
3032 | -1, -1, -1, nullid, self->nodelen, 0, 0, | |
|
3033 | comp_mode_inline, comp_mode_inline, rank_unknown); | |
|
2803 | 3034 | |
|
2804 | 3035 | if (!self->nullentry) |
|
2805 | 3036 | return -1; |
@@ -350,10 +350,11 b' class cg1unpacker(object):' | |||
|
350 | 350 | |
|
351 | 351 | def ondupchangelog(cl, rev): |
|
352 | 352 | if rev < clstart: |
|
353 | duprevs.append(rev) | |
|
353 | duprevs.append(rev) # pytype: disable=attribute-error | |
|
354 | 354 | |
|
355 | 355 | def onchangelog(cl, rev): |
|
356 | 356 | ctx = cl.changelogrevision(rev) |
|
357 | assert efilesset is not None # help pytype | |
|
357 | 358 | efilesset.update(ctx.files) |
|
358 | 359 | repo.register_changeset(rev, ctx) |
|
359 | 360 |
@@ -643,6 +643,13 b' class chgunixservicehandler(object):' | |||
|
643 | 643 | |
|
644 | 644 | def __init__(self, ui): |
|
645 | 645 | self.ui = ui |
|
646 | ||
|
647 | # TODO: use PEP 526 syntax (`_hashstate: hashstate` at the class level) | |
|
648 | # when 3.5 support is dropped. | |
|
649 | self._hashstate = None # type: hashstate | |
|
650 | self._baseaddress = None # type: bytes | |
|
651 | self._realaddress = None # type: bytes | |
|
652 | ||
|
646 | 653 | self._idletimeout = ui.configint(b'chgserver', b'idletimeout') |
|
647 | 654 | self._lastactive = time.time() |
|
648 | 655 |
@@ -522,8 +522,10 b' def dorecord(' | |||
|
522 | 522 | # 1. filter patch, since we are intending to apply subset of it |
|
523 | 523 | try: |
|
524 | 524 | chunks, newopts = filterfn(ui, original_headers, match) |
|
525 | except error.PatchError as err: | |
|
525 | except error.PatchParseError as err: | |
|
526 | 526 | raise error.InputError(_(b'error parsing patch: %s') % err) |
|
527 | except error.PatchApplicationError as err: | |
|
528 | raise error.StateError(_(b'error applying patch: %s') % err) | |
|
527 | 529 | opts.update(newopts) |
|
528 | 530 | |
|
529 | 531 | # We need to keep a backup of files that have been newly added and |
@@ -608,8 +610,10 b' def dorecord(' | |||
|
608 | 610 | ui.debug(b'applying patch\n') |
|
609 | 611 | ui.debug(fp.getvalue()) |
|
610 | 612 | patch.internalpatch(ui, repo, fp, 1, eolmode=None) |
|
611 | except error.PatchError as err: | |
|
613 | except error.PatchParseError as err: | |
|
612 | 614 | raise error.InputError(pycompat.bytestr(err)) |
|
615 | except error.PatchApplicationError as err: | |
|
616 | raise error.StateError(pycompat.bytestr(err)) | |
|
613 | 617 | del fp |
|
614 | 618 | |
|
615 | 619 | # 4. We prepared working directory according to filtered |
@@ -2020,9 +2024,16 b' def tryimportone(ui, repo, patchdata, pa' | |||
|
2020 | 2024 | eolmode=None, |
|
2021 | 2025 | similarity=sim / 100.0, |
|
2022 | 2026 | ) |
|
2023 | except error.PatchError as e: | |
|
2027 | except error.PatchParseError as e: | |
|
2028 | raise error.InputError( | |
|
2029 | pycompat.bytestr(e), | |
|
2030 | hint=_( | |
|
2031 | b'check that whitespace in the patch has not been mangled' | |
|
2032 | ), | |
|
2033 | ) | |
|
2034 | except error.PatchApplicationError as e: | |
|
2024 | 2035 | if not partial: |
|
2025 |
raise error. |
|
|
2036 | raise error.StateError(pycompat.bytestr(e)) | |
|
2026 | 2037 | if partial: |
|
2027 | 2038 | rejects = True |
|
2028 | 2039 | |
@@ -2079,8 +2090,15 b' def tryimportone(ui, repo, patchdata, pa' | |||
|
2079 | 2090 | files, |
|
2080 | 2091 | eolmode=None, |
|
2081 | 2092 | ) |
|
2082 | except error.PatchError as e: | |
|
2083 |
raise error. |
|
|
2093 | except error.PatchParseError as e: | |
|
2094 | raise error.InputError( | |
|
2095 | stringutil.forcebytestr(e), | |
|
2096 | hint=_( | |
|
2097 | b'check that whitespace in the patch has not been mangled' | |
|
2098 | ), | |
|
2099 | ) | |
|
2100 | except error.PatchApplicationError as e: | |
|
2101 | raise error.StateError(stringutil.forcebytestr(e)) | |
|
2084 | 2102 | if opts.get(b'exact'): |
|
2085 | 2103 | editor = None |
|
2086 | 2104 | else: |
@@ -3628,15 +3646,14 b' def _performrevert(' | |||
|
3628 | 3646 | prntstatusmsg(b'drop', f) |
|
3629 | 3647 | repo.dirstate.set_untracked(f) |
|
3630 | 3648 | |
|
3631 | normal = None | |
|
3632 | if node == parent: | |
|
3633 | # We're reverting to our parent. If possible, we'd like status | |
|
3634 | # to report the file as clean. We have to use normallookup for | |
|
3635 | # merges to avoid losing information about merged/dirty files. | |
|
3636 | if p2 != repo.nullid: | |
|
3637 | normal = repo.dirstate.set_tracked | |
|
3638 | else: | |
|
3639 | normal = repo.dirstate.set_clean | |
|
3649 | # We are reverting to our parent. If possible, we had like `hg status` | |
|
3650 | # to report the file as clean. We have to be less agressive for | |
|
3651 | # merges to avoid losing information about copy introduced by the merge. | |
|
3652 | # This might comes with bugs ? | |
|
3653 | reset_copy = p2 == repo.nullid | |
|
3654 | ||
|
3655 | def normal(filename): | |
|
3656 | return repo.dirstate.set_tracked(filename, reset_copy=reset_copy) | |
|
3640 | 3657 | |
|
3641 | 3658 | newlyaddedandmodifiedfiles = set() |
|
3642 | 3659 | if interactive: |
@@ -3674,8 +3691,10 b' def _performrevert(' | |||
|
3674 | 3691 | if operation == b'discard': |
|
3675 | 3692 | chunks = patch.reversehunks(chunks) |
|
3676 | 3693 | |
|
3677 | except error.PatchError as err: | |
|
3678 |
raise error. |
|
|
3694 | except error.PatchParseError as err: | |
|
3695 | raise error.InputError(_(b'error parsing patch: %s') % err) | |
|
3696 | except error.PatchApplicationError as err: | |
|
3697 | raise error.StateError(_(b'error applying patch: %s') % err) | |
|
3679 | 3698 | |
|
3680 | 3699 | # FIXME: when doing an interactive revert of a copy, there's no way of |
|
3681 | 3700 | # performing a partial revert of the added file, the only option is |
@@ -3710,8 +3729,10 b' def _performrevert(' | |||
|
3710 | 3729 | if dopatch: |
|
3711 | 3730 | try: |
|
3712 | 3731 | patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None) |
|
3713 | except error.PatchError as err: | |
|
3714 |
raise error. |
|
|
3732 | except error.PatchParseError as err: | |
|
3733 | raise error.InputError(pycompat.bytestr(err)) | |
|
3734 | except error.PatchApplicationError as err: | |
|
3735 | raise error.StateError(pycompat.bytestr(err)) | |
|
3715 | 3736 | del fp |
|
3716 | 3737 | else: |
|
3717 | 3738 | for f in actions[b'revert'][0]: |
@@ -3727,9 +3748,6 b' def _performrevert(' | |||
|
3727 | 3748 | checkout(f) |
|
3728 | 3749 | repo.dirstate.set_tracked(f) |
|
3729 | 3750 | |
|
3730 | normal = repo.dirstate.set_tracked | |
|
3731 | if node == parent and p2 == repo.nullid: | |
|
3732 | normal = repo.dirstate.set_clean | |
|
3733 | 3751 | for f in actions[b'undelete'][0]: |
|
3734 | 3752 | if interactive: |
|
3735 | 3753 | choice = repo.ui.promptchoice( |
@@ -248,28 +248,19 b' def _modesetup(ui):' | |||
|
248 | 248 | if pycompat.iswindows: |
|
249 | 249 | from . import win32 |
|
250 | 250 | |
|
251 | term = encoding.environ.get(b'TERM') | |
|
252 | # TERM won't be defined in a vanilla cmd.exe environment. | |
|
253 | ||
|
254 | # UNIX-like environments on Windows such as Cygwin and MSYS will | |
|
255 | # set TERM. They appear to make a best effort attempt at setting it | |
|
256 | # to something appropriate. However, not all environments with TERM | |
|
257 | # defined support ANSI. | |
|
258 | ansienviron = term and b'xterm' in term | |
|
259 | ||
|
260 | 251 | if mode == b'auto': |
|
261 | 252 | # Since "ansi" could result in terminal gibberish, we error on the |
|
262 | 253 | # side of selecting "win32". However, if w32effects is not defined, |
|
263 | 254 | # we almost certainly don't support "win32", so don't even try. |
|
264 | 255 | # w32effects is not populated when stdout is redirected, so checking |
|
265 | 256 | # it first avoids win32 calls in a state known to error out. |
|
266 |
if |
|
|
257 | if not w32effects or win32.enablevtmode(): | |
|
267 | 258 | realmode = b'ansi' |
|
268 | 259 | else: |
|
269 | 260 | realmode = b'win32' |
|
270 | 261 | # An empty w32effects is a clue that stdout is redirected, and thus |
|
271 | 262 | # cannot enable VT mode. |
|
272 |
elif mode == b'ansi' and w32effects |
|
|
263 | elif mode == b'ansi' and w32effects: | |
|
273 | 264 | win32.enablevtmode() |
|
274 | 265 | elif mode == b'auto': |
|
275 | 266 | realmode = b'ansi' |
@@ -3309,7 +3309,9 b' def _dograft(ui, repo, *revs, **opts):' | |||
|
3309 | 3309 | overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')} |
|
3310 | 3310 | base = ctx.p1() if basectx is None else basectx |
|
3311 | 3311 | with ui.configoverride(overrides, b'graft'): |
|
3312 |
stats = mergemod.graft( |
|
|
3312 | stats = mergemod.graft( | |
|
3313 | repo, ctx, base, [b'local', b'graft', b'parent of graft'] | |
|
3314 | ) | |
|
3313 | 3315 | # report any conflicts |
|
3314 | 3316 | if stats.unresolvedcount > 0: |
|
3315 | 3317 | # write out state for --continue |
@@ -4914,7 +4916,7 b' def merge(ui, repo, node=None, **opts):' | |||
|
4914 | 4916 | overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')} |
|
4915 | 4917 | with ui.configoverride(overrides, b'merge'): |
|
4916 | 4918 | force = opts.get(b'force') |
|
4917 | labels = [b'working copy', b'merge rev'] | |
|
4919 | labels = [b'working copy', b'merge rev', b'common ancestor'] | |
|
4918 | 4920 | return hg.merge(ctx, force=force, labels=labels) |
|
4919 | 4921 | |
|
4920 | 4922 | |
@@ -6130,7 +6132,6 b' def resolve(ui, repo, *pats, **opts):' | |||
|
6130 | 6132 | ret = 0 |
|
6131 | 6133 | didwork = False |
|
6132 | 6134 | |
|
6133 | tocomplete = [] | |
|
6134 | 6135 | hasconflictmarkers = [] |
|
6135 | 6136 | if mark: |
|
6136 | 6137 | markcheck = ui.config(b'commands', b'resolve.mark-check') |
@@ -6183,17 +6184,13 b' def resolve(ui, repo, *pats, **opts):' | |||
|
6183 | 6184 | # preresolve file |
|
6184 | 6185 | overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')} |
|
6185 | 6186 | with ui.configoverride(overrides, b'resolve'): |
|
6186 |
|
|
|
6187 |
if |
|
|
6188 | tocomplete.append(f) | |
|
6189 | elif r: | |
|
6187 | r = ms.resolve(f, wctx) | |
|
6188 | if r: | |
|
6190 | 6189 | ret = 1 |
|
6191 | 6190 | finally: |
|
6192 | 6191 | ms.commit() |
|
6193 | 6192 | |
|
6194 |
# replace filemerge's .orig file with our resolve file |
|
|
6195 | # for merges that are complete | |
|
6196 | if complete: | |
|
6193 | # replace filemerge's .orig file with our resolve file | |
|
6197 | 6194 | try: |
|
6198 | 6195 | util.rename( |
|
6199 | 6196 | a + b".resolve", scmutil.backuppath(ui, repo, f) |
@@ -6218,25 +6215,6 b' def resolve(ui, repo, *pats, **opts):' | |||
|
6218 | 6215 | hint=_(b'use --all to mark anyway'), |
|
6219 | 6216 | ) |
|
6220 | 6217 | |
|
6221 | for f in tocomplete: | |
|
6222 | try: | |
|
6223 | # resolve file | |
|
6224 | overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')} | |
|
6225 | with ui.configoverride(overrides, b'resolve'): | |
|
6226 | r = ms.resolve(f, wctx) | |
|
6227 | if r: | |
|
6228 | ret = 1 | |
|
6229 | finally: | |
|
6230 | ms.commit() | |
|
6231 | ||
|
6232 | # replace filemerge's .orig file with our resolve file | |
|
6233 | a = repo.wjoin(f) | |
|
6234 | try: | |
|
6235 | util.rename(a + b".resolve", scmutil.backuppath(ui, repo, f)) | |
|
6236 | except OSError as inst: | |
|
6237 | if inst.errno != errno.ENOENT: | |
|
6238 | raise | |
|
6239 | ||
|
6240 | 6218 | ms.commit() |
|
6241 | 6219 | branchmerge = repo.dirstate.p2() != repo.nullid |
|
6242 | 6220 | # resolve is not doing a parent change here, however, `record updates` |
@@ -6897,9 +6875,9 b' def status(ui, repo, *pats, **opts):' | |||
|
6897 | 6875 | |
|
6898 | 6876 | cmdutil.check_at_most_one_arg(opts, 'rev', 'change') |
|
6899 | 6877 | opts = pycompat.byteskwargs(opts) |
|
6900 | revs = opts.get(b'rev') | |
|
6901 | change = opts.get(b'change') | |
|
6902 | terse = opts.get(b'terse') | |
|
6878 | revs = opts.get(b'rev', []) | |
|
6879 | change = opts.get(b'change', b'') | |
|
6880 | terse = opts.get(b'terse', _NOTTERSE) | |
|
6903 | 6881 | if terse is _NOTTERSE: |
|
6904 | 6882 | if revs: |
|
6905 | 6883 | terse = b'' |
@@ -7832,9 +7810,9 b' def update(ui, repo, node=None, **opts):' | |||
|
7832 | 7810 | raise error.InputError(_(b"you can't specify a revision and a date")) |
|
7833 | 7811 | |
|
7834 | 7812 | updatecheck = None |
|
7835 | if check: | |
|
7813 | if check or merge is not None and not merge: | |
|
7836 | 7814 | updatecheck = b'abort' |
|
7837 | elif merge: | |
|
7815 | elif merge or check is not None and not check: | |
|
7838 | 7816 | updatecheck = b'none' |
|
7839 | 7817 | |
|
7840 | 7818 | with repo.wlock(): |
@@ -134,7 +134,13 b' def _prepare_files(tr, ctx, error=False,' | |||
|
134 | 134 | for s in salvaged: |
|
135 | 135 | files.mark_salvaged(s) |
|
136 | 136 | |
|
137 | if ctx.manifestnode(): | |
|
137 | narrow_files = {} | |
|
138 | if not ctx.repo().narrowmatch().always(): | |
|
139 | for f, e in ms.allextras().items(): | |
|
140 | action = e.get(b'outside-narrow-merge-action') | |
|
141 | if action is not None: | |
|
142 | narrow_files[f] = action | |
|
143 | if ctx.manifestnode() and not narrow_files: | |
|
138 | 144 | # reuse an existing manifest revision |
|
139 | 145 | repo.ui.debug(b'reusing known manifest\n') |
|
140 | 146 | mn = ctx.manifestnode() |
@@ -142,11 +148,11 b' def _prepare_files(tr, ctx, error=False,' | |||
|
142 | 148 | if writechangesetcopy: |
|
143 | 149 | files.update_added(ctx.filesadded()) |
|
144 | 150 | files.update_removed(ctx.filesremoved()) |
|
145 | elif not ctx.files(): | |
|
151 | elif not ctx.files() and not narrow_files: | |
|
146 | 152 | repo.ui.debug(b'reusing manifest from p1 (no file change)\n') |
|
147 | 153 | mn = p1.manifestnode() |
|
148 | 154 | else: |
|
149 | mn = _process_files(tr, ctx, ms, files, error=error) | |
|
155 | mn = _process_files(tr, ctx, ms, files, narrow_files, error=error) | |
|
150 | 156 | |
|
151 | 157 | if origctx and origctx.manifestnode() == mn: |
|
152 | 158 | origfiles = origctx.files() |
@@ -177,7 +183,7 b' def _get_salvaged(repo, ms, ctx):' | |||
|
177 | 183 | return salvaged |
|
178 | 184 | |
|
179 | 185 | |
|
180 | def _process_files(tr, ctx, ms, files, error=False): | |
|
186 | def _process_files(tr, ctx, ms, files, narrow_files=None, error=False): | |
|
181 | 187 | repo = ctx.repo() |
|
182 | 188 | p1 = ctx.p1() |
|
183 | 189 | p2 = ctx.p2() |
@@ -198,8 +204,33 b' def _process_files(tr, ctx, ms, files, e' | |||
|
198 | 204 | linkrev = len(repo) |
|
199 | 205 | repo.ui.note(_(b"committing files:\n")) |
|
200 | 206 | uipathfn = scmutil.getuipathfn(repo) |
|
201 |
|
|
|
207 | all_files = ctx.modified() + ctx.added() | |
|
208 | all_files.extend(narrow_files.keys()) | |
|
209 | all_files.sort() | |
|
210 | for f in all_files: | |
|
202 | 211 | repo.ui.note(uipathfn(f) + b"\n") |
|
212 | if f in narrow_files: | |
|
213 | narrow_action = narrow_files.get(f) | |
|
214 | if narrow_action == mergestate.CHANGE_REMOVED: | |
|
215 | files.mark_removed(f) | |
|
216 | removed.append(f) | |
|
217 | elif narrow_action == mergestate.CHANGE_ADDED: | |
|
218 | files.mark_added(f) | |
|
219 | added.append(f) | |
|
220 | m[f] = m2[f] | |
|
221 | flags = m2ctx.find(f)[1] or b'' | |
|
222 | m.setflag(f, flags) | |
|
223 | elif narrow_action == mergestate.CHANGE_MODIFIED: | |
|
224 | files.mark_touched(f) | |
|
225 | added.append(f) | |
|
226 | m[f] = m2[f] | |
|
227 | flags = m2ctx.find(f)[1] or b'' | |
|
228 | m.setflag(f, flags) | |
|
229 | else: | |
|
230 | msg = _(b"corrupted mergestate, unknown narrow action: %b") | |
|
231 | hint = _(b"restart the merge") | |
|
232 | raise error.Abort(msg, hint=hint) | |
|
233 | continue | |
|
203 | 234 | try: |
|
204 | 235 | fctx = ctx[f] |
|
205 | 236 | if fctx is None: |
@@ -239,7 +270,17 b' def _process_files(tr, ctx, ms, files, e' | |||
|
239 | 270 | if not rf(f): |
|
240 | 271 | files.mark_removed(f) |
|
241 | 272 | |
|
242 | mn = _commit_manifest(tr, linkrev, ctx, mctx, m, files.touched, added, drop) | |
|
273 | mn = _commit_manifest( | |
|
274 | tr, | |
|
275 | linkrev, | |
|
276 | ctx, | |
|
277 | mctx, | |
|
278 | m, | |
|
279 | files.touched, | |
|
280 | added, | |
|
281 | drop, | |
|
282 | bool(narrow_files), | |
|
283 | ) | |
|
243 | 284 | |
|
244 | 285 | return mn |
|
245 | 286 | |
@@ -409,7 +450,17 b' def _filecommit(' | |||
|
409 | 450 | return fnode, touched |
|
410 | 451 | |
|
411 | 452 | |
|
412 | def _commit_manifest(tr, linkrev, ctx, mctx, manifest, files, added, drop): | |
|
453 | def _commit_manifest( | |
|
454 | tr, | |
|
455 | linkrev, | |
|
456 | ctx, | |
|
457 | mctx, | |
|
458 | manifest, | |
|
459 | files, | |
|
460 | added, | |
|
461 | drop, | |
|
462 | has_some_narrow_action=False, | |
|
463 | ): | |
|
413 | 464 | """make a new manifest entry (or reuse a new one) |
|
414 | 465 | |
|
415 | 466 | given an initialised manifest context and precomputed list of |
@@ -451,6 +502,10 b' def _commit_manifest(tr, linkrev, ctx, m' | |||
|
451 | 502 | # at this point is merges, and we already error out in the |
|
452 | 503 | # case where the merge has files outside of the narrowspec, |
|
453 | 504 | # so this is safe. |
|
505 | if has_some_narrow_action: | |
|
506 | match = None | |
|
507 | else: | |
|
508 | match = repo.narrowmatch() | |
|
454 | 509 | mn = mctx.write( |
|
455 | 510 | tr, |
|
456 | 511 | linkrev, |
@@ -458,7 +513,7 b' def _commit_manifest(tr, linkrev, ctx, m' | |||
|
458 | 513 | p2.manifestnode(), |
|
459 | 514 | added, |
|
460 | 515 | drop, |
|
461 |
match= |
|
|
516 | match=match, | |
|
462 | 517 | ) |
|
463 | 518 | else: |
|
464 | 519 | repo.ui.debug( |
@@ -1042,11 +1042,6 b' coreconfigitem(' | |||
|
1042 | 1042 | ) |
|
1043 | 1043 | coreconfigitem( |
|
1044 | 1044 | b'experimental', |
|
1045 | b'mergetempdirprefix', | |
|
1046 | default=None, | |
|
1047 | ) | |
|
1048 | coreconfigitem( | |
|
1049 | b'experimental', | |
|
1050 | 1045 | b'mmapindexthreshold', |
|
1051 | 1046 | default=None, |
|
1052 | 1047 | ) |
@@ -1102,16 +1097,6 b' coreconfigitem(' | |||
|
1102 | 1097 | ) |
|
1103 | 1098 | coreconfigitem( |
|
1104 | 1099 | b'experimental', |
|
1105 | b'httppeer.advertise-v2', | |
|
1106 | default=False, | |
|
1107 | ) | |
|
1108 | coreconfigitem( | |
|
1109 | b'experimental', | |
|
1110 | b'httppeer.v2-encoder-order', | |
|
1111 | default=None, | |
|
1112 | ) | |
|
1113 | coreconfigitem( | |
|
1114 | b'experimental', | |
|
1115 | 1100 | b'httppostargs', |
|
1116 | 1101 | default=False, |
|
1117 | 1102 | ) |
@@ -1211,11 +1196,6 b' coreconfigitem(' | |||
|
1211 | 1196 | ) |
|
1212 | 1197 | coreconfigitem( |
|
1213 | 1198 | b'experimental', |
|
1214 | b'sshserver.support-v2', | |
|
1215 | default=False, | |
|
1216 | ) | |
|
1217 | coreconfigitem( | |
|
1218 | b'experimental', | |
|
1219 | 1199 | b'sparse-read', |
|
1220 | 1200 | default=False, |
|
1221 | 1201 | ) |
@@ -1241,26 +1221,6 b' coreconfigitem(' | |||
|
1241 | 1221 | ) |
|
1242 | 1222 | coreconfigitem( |
|
1243 | 1223 | b'experimental', |
|
1244 | b'sshpeer.advertise-v2', | |
|
1245 | default=False, | |
|
1246 | ) | |
|
1247 | coreconfigitem( | |
|
1248 | b'experimental', | |
|
1249 | b'web.apiserver', | |
|
1250 | default=False, | |
|
1251 | ) | |
|
1252 | coreconfigitem( | |
|
1253 | b'experimental', | |
|
1254 | b'web.api.http-v2', | |
|
1255 | default=False, | |
|
1256 | ) | |
|
1257 | coreconfigitem( | |
|
1258 | b'experimental', | |
|
1259 | b'web.api.debugreflect', | |
|
1260 | default=False, | |
|
1261 | ) | |
|
1262 | coreconfigitem( | |
|
1263 | b'experimental', | |
|
1264 | 1224 | b'web.full-garbage-collection-rate', |
|
1265 | 1225 | default=1, # still forcing a full collection on each request |
|
1266 | 1226 | ) |
@@ -1281,11 +1241,17 b' coreconfigitem(' | |||
|
1281 | 1241 | ) |
|
1282 | 1242 | coreconfigitem( |
|
1283 | 1243 | b'extensions', |
|
1284 |
b' |
|
|
1244 | b'[^:]*', | |
|
1285 | 1245 | default=None, |
|
1286 | 1246 | generic=True, |
|
1287 | 1247 | ) |
|
1288 | 1248 | coreconfigitem( |
|
1249 | b'extensions', | |
|
1250 | b'[^:]*:required', | |
|
1251 | default=False, | |
|
1252 | generic=True, | |
|
1253 | ) | |
|
1254 | coreconfigitem( | |
|
1289 | 1255 | b'extdata', |
|
1290 | 1256 | b'.*', |
|
1291 | 1257 | default=None, |
@@ -1313,6 +1279,18 b' coreconfigitem(' | |||
|
1313 | 1279 | ) |
|
1314 | 1280 | coreconfigitem( |
|
1315 | 1281 | b'format', |
|
1282 | b'use-dirstate-tracked-hint', | |
|
1283 | default=False, | |
|
1284 | experimental=True, | |
|
1285 | ) | |
|
1286 | coreconfigitem( | |
|
1287 | b'format', | |
|
1288 | b'use-dirstate-tracked-hint.version', | |
|
1289 | default=1, | |
|
1290 | experimental=True, | |
|
1291 | ) | |
|
1292 | coreconfigitem( | |
|
1293 | b'format', | |
|
1316 | 1294 | b'dotencode', |
|
1317 | 1295 | default=True, |
|
1318 | 1296 | ) |
@@ -1352,10 +1330,10 b' coreconfigitem(' | |||
|
1352 | 1330 | ) |
|
1353 | 1331 | # Experimental TODOs: |
|
1354 | 1332 | # |
|
1355 | # * Same as for evlogv2 (but for the reduction of the number of files) | |
|
1333 | # * Same as for revlogv2 (but for the reduction of the number of files) | |
|
1334 | # * Actually computing the rank of changesets | |
|
1356 | 1335 | # * Improvement to investigate |
|
1357 | 1336 | # - storing .hgtags fnode |
|
1358 | # - storing `rank` of changesets | |
|
1359 | 1337 | # - storing branch related identifier |
|
1360 | 1338 | |
|
1361 | 1339 | coreconfigitem( |
@@ -1405,7 +1383,7 b' coreconfigitem(' | |||
|
1405 | 1383 | coreconfigitem( |
|
1406 | 1384 | b'format', |
|
1407 | 1385 | b'use-share-safe', |
|
1408 |
default= |
|
|
1386 | default=True, | |
|
1409 | 1387 | ) |
|
1410 | 1388 | coreconfigitem( |
|
1411 | 1389 | b'format', |
@@ -20,7 +20,6 b' from .node import (' | |||
|
20 | 20 | ) |
|
21 | 21 | from .pycompat import ( |
|
22 | 22 | getattr, |
|
23 | open, | |
|
24 | 23 | ) |
|
25 | 24 | from . import ( |
|
26 | 25 | dagop, |
@@ -46,6 +45,9 b' from .utils import (' | |||
|
46 | 45 | dateutil, |
|
47 | 46 | stringutil, |
|
48 | 47 | ) |
|
48 | from .dirstateutils import ( | |
|
49 | timestamp, | |
|
50 | ) | |
|
49 | 51 | |
|
50 | 52 | propertycache = util.propertycache |
|
51 | 53 | |
@@ -682,6 +684,14 b' class changectx(basectx):' | |||
|
682 | 684 | """Return a list of byte bookmark names.""" |
|
683 | 685 | return self._repo.nodebookmarks(self._node) |
|
684 | 686 | |
|
687 | def fast_rank(self): | |
|
688 | repo = self._repo | |
|
689 | if self._maybe_filtered: | |
|
690 | cl = repo.changelog | |
|
691 | else: | |
|
692 | cl = repo.unfiltered().changelog | |
|
693 | return cl.fast_rank(self._rev) | |
|
694 | ||
|
685 | 695 | def phase(self): |
|
686 | 696 | return self._repo._phasecache.phase(self._repo, self._rev) |
|
687 | 697 | |
@@ -1793,13 +1803,14 b' class workingctx(committablectx):' | |||
|
1793 | 1803 | sane.append(f) |
|
1794 | 1804 | return sane |
|
1795 | 1805 | |
|
1796 | def _checklookup(self, files): | |
|
1806 | def _checklookup(self, files, mtime_boundary): | |
|
1797 | 1807 | # check for any possibly clean files |
|
1798 | 1808 | if not files: |
|
1799 | return [], [], [] | |
|
1809 | return [], [], [], [] | |
|
1800 | 1810 | |
|
1801 | 1811 | modified = [] |
|
1802 | 1812 | deleted = [] |
|
1813 | clean = [] | |
|
1803 | 1814 | fixup = [] |
|
1804 | 1815 | pctx = self._parents[0] |
|
1805 | 1816 | # do a full compare of any files that might have changed |
@@ -1813,8 +1824,18 b' class workingctx(committablectx):' | |||
|
1813 | 1824 | or pctx[f].cmp(self[f]) |
|
1814 | 1825 | ): |
|
1815 | 1826 | modified.append(f) |
|
1827 | elif mtime_boundary is None: | |
|
1828 | clean.append(f) | |
|
1816 | 1829 | else: |
|
1817 |
|
|
|
1830 | s = self[f].lstat() | |
|
1831 | mode = s.st_mode | |
|
1832 | size = s.st_size | |
|
1833 | file_mtime = timestamp.reliable_mtime_of(s, mtime_boundary) | |
|
1834 | if file_mtime is not None: | |
|
1835 | cache_info = (mode, size, file_mtime) | |
|
1836 | fixup.append((f, cache_info)) | |
|
1837 | else: | |
|
1838 | clean.append(f) | |
|
1818 | 1839 | except (IOError, OSError): |
|
1819 | 1840 | # A file become inaccessible in between? Mark it as deleted, |
|
1820 | 1841 | # matching dirstate behavior (issue5584). |
@@ -1824,7 +1845,7 b' class workingctx(committablectx):' | |||
|
1824 | 1845 | # it's in the dirstate. |
|
1825 | 1846 | deleted.append(f) |
|
1826 | 1847 | |
|
1827 | return modified, deleted, fixup | |
|
1848 | return modified, deleted, clean, fixup | |
|
1828 | 1849 | |
|
1829 | 1850 | def _poststatusfixup(self, status, fixup): |
|
1830 | 1851 | """update dirstate for files that are actually clean""" |
@@ -1842,13 +1863,13 b' class workingctx(committablectx):' | |||
|
1842 | 1863 | if dirstate.identity() == oldid: |
|
1843 | 1864 | if fixup: |
|
1844 | 1865 | if dirstate.pendingparentchange(): |
|
1845 | normal = lambda f: dirstate.update_file( | |
|
1866 | normal = lambda f, pfd: dirstate.update_file( | |
|
1846 | 1867 | f, p1_tracked=True, wc_tracked=True |
|
1847 | 1868 | ) |
|
1848 | 1869 | else: |
|
1849 | 1870 | normal = dirstate.set_clean |
|
1850 | for f in fixup: | |
|
1851 | normal(f) | |
|
1871 | for f, pdf in fixup: | |
|
1872 | normal(f, pdf) | |
|
1852 | 1873 | # write changes out explicitly, because nesting |
|
1853 | 1874 | # wlock at runtime may prevent 'wlock.release()' |
|
1854 | 1875 | # after this block from doing so for subsequent |
@@ -1878,19 +1899,23 b' class workingctx(committablectx):' | |||
|
1878 | 1899 | subrepos = [] |
|
1879 | 1900 | if b'.hgsub' in self: |
|
1880 | 1901 | subrepos = sorted(self.substate) |
|
1881 | cmp, s = self._repo.dirstate.status( | |
|
1902 | cmp, s, mtime_boundary = self._repo.dirstate.status( | |
|
1882 | 1903 | match, subrepos, ignored=ignored, clean=clean, unknown=unknown |
|
1883 | 1904 | ) |
|
1884 | 1905 | |
|
1885 | 1906 | # check for any possibly clean files |
|
1886 | 1907 | fixup = [] |
|
1887 | 1908 | if cmp: |
|
1888 |
modified2, deleted2, fixup = self._checklookup( |
|
|
1909 | modified2, deleted2, clean_set, fixup = self._checklookup( | |
|
1910 | cmp, mtime_boundary | |
|
1911 | ) | |
|
1889 | 1912 | s.modified.extend(modified2) |
|
1890 | 1913 | s.deleted.extend(deleted2) |
|
1891 | 1914 | |
|
1915 | if clean_set and clean: | |
|
1916 | s.clean.extend(clean_set) | |
|
1892 | 1917 | if fixup and clean: |
|
1893 | s.clean.extend(fixup) | |
|
1918 | s.clean.extend((f for f, _ in fixup)) | |
|
1894 | 1919 | |
|
1895 | 1920 | self._poststatusfixup(s, fixup) |
|
1896 | 1921 | |
@@ -3111,13 +3136,11 b' class arbitraryfilectx(object):' | |||
|
3111 | 3136 | return util.readfile(self._path) |
|
3112 | 3137 | |
|
3113 | 3138 | def decodeddata(self): |
|
3114 | with open(self._path, b"rb") as f: | |
|
3115 | return f.read() | |
|
3139 | return util.readfile(self._path) | |
|
3116 | 3140 | |
|
3117 | 3141 | def remove(self): |
|
3118 | 3142 | util.unlink(self._path) |
|
3119 | 3143 | |
|
3120 | 3144 | def write(self, data, flags, **kwargs): |
|
3121 | 3145 | assert not flags |
|
3122 |
|
|
|
3123 | f.write(data) | |
|
3146 | util.writefile(self._path, data) |
@@ -246,7 +246,6 b' def _changesetforwardcopies(a, b, match)' | |||
|
246 | 246 | return {} |
|
247 | 247 | |
|
248 | 248 | repo = a.repo().unfiltered() |
|
249 | children = {} | |
|
250 | 249 | |
|
251 | 250 | cl = repo.changelog |
|
252 | 251 | isancestor = cl.isancestorrev |
@@ -290,7 +289,7 b' def _changesetforwardcopies(a, b, match)' | |||
|
290 | 289 | # no common revision to track copies from |
|
291 | 290 | return {} |
|
292 | 291 | if has_graph_roots: |
|
293 |
# this deal with the special case mention |
|
|
292 | # this deal with the special case mentioned in the [1] footnotes. We | |
|
294 | 293 | # must filter out revisions that leads to non-common graphroots. |
|
295 | 294 | roots = list(roots) |
|
296 | 295 | m = min(roots) |
@@ -301,11 +300,11 b' def _changesetforwardcopies(a, b, match)' | |||
|
301 | 300 | |
|
302 | 301 | if repo.filecopiesmode == b'changeset-sidedata': |
|
303 | 302 | # When using side-data, we will process the edges "from" the children. |
|
304 | # We iterate over the childre, gathering previous collected data for | |
|
303 | # We iterate over the children, gathering previous collected data for | |
|
305 | 304 | # the parents. Do know when the parents data is no longer necessary, we |
|
306 | 305 | # keep a counter of how many children each revision has. |
|
307 | 306 | # |
|
308 |
# An inter |
|
|
307 | # An interesting property of `children_count` is that it only contains | |
|
309 | 308 | # revision that will be relevant for a edge of the graph. So if a |
|
310 | 309 | # children has parent not in `children_count`, that edges should not be |
|
311 | 310 | # processed. |
@@ -449,7 +448,11 b' def _combine_changeset_copies(' | |||
|
449 | 448 | |
|
450 | 449 | # filter out internal details and return a {dest: source mapping} |
|
451 | 450 | final_copies = {} |
|
452 | for dest, (tt, source) in all_copies[targetrev].items(): | |
|
451 | ||
|
452 | targetrev_items = all_copies[targetrev] | |
|
453 | assert targetrev_items is not None # help pytype | |
|
454 | ||
|
455 | for dest, (tt, source) in targetrev_items.items(): | |
|
453 | 456 | if source is not None: |
|
454 | 457 | final_copies[dest] = source |
|
455 | 458 | if not alwaysmatch: |
@@ -9,7 +9,6 b' from __future__ import absolute_import' | |||
|
9 | 9 | |
|
10 | 10 | import heapq |
|
11 | 11 | |
|
12 | from .node import nullrev | |
|
13 | 12 | from .thirdparty import attr |
|
14 | 13 | from .node import nullrev |
|
15 | 14 | from . import ( |
@@ -91,7 +91,6 b' from . import (' | |||
|
91 | 91 | vfs as vfsmod, |
|
92 | 92 | wireprotoframing, |
|
93 | 93 | wireprotoserver, |
|
94 | wireprotov2peer, | |
|
95 | 94 | ) |
|
96 | 95 | from .interfaces import repository |
|
97 | 96 | from .utils import ( |
@@ -179,6 +178,12 b' def debugapplystreamclonebundle(ui, repo' | |||
|
179 | 178 | _(b'add single file all revs overwrite'), |
|
180 | 179 | ), |
|
181 | 180 | (b'n', b'new-file', None, _(b'add new file at each rev')), |
|
181 | ( | |
|
182 | b'', | |
|
183 | b'from-existing', | |
|
184 | None, | |
|
185 | _(b'continue from a non-empty repository'), | |
|
186 | ), | |
|
182 | 187 | ], |
|
183 | 188 | _(b'[OPTION]... [TEXT]'), |
|
184 | 189 | ) |
@@ -189,6 +194,7 b' def debugbuilddag(' | |||
|
189 | 194 | mergeable_file=False, |
|
190 | 195 | overwritten_file=False, |
|
191 | 196 | new_file=False, |
|
197 | from_existing=False, | |
|
192 | 198 | ): |
|
193 | 199 | """builds a repo with a given DAG from scratch in the current empty repo |
|
194 | 200 | |
@@ -227,7 +233,7 b' def debugbuilddag(' | |||
|
227 | 233 | text = ui.fin.read() |
|
228 | 234 | |
|
229 | 235 | cl = repo.changelog |
|
230 | if len(cl) > 0: | |
|
236 | if len(cl) > 0 and not from_existing: | |
|
231 | 237 | raise error.Abort(_(b'repository is not empty')) |
|
232 | 238 | |
|
233 | 239 | # determine number of revs in DAG |
@@ -273,7 +279,10 b' def debugbuilddag(' | |||
|
273 | 279 | x[fn].data() for x in (pa, p1, p2) |
|
274 | 280 | ] |
|
275 | 281 | m3 = simplemerge.Merge3Text(base, local, other) |
|
276 |
ml = [ |
|
|
282 | ml = [ | |
|
283 | l.strip() | |
|
284 | for l in simplemerge.render_minimized(m3)[0] | |
|
285 | ] | |
|
277 | 286 | ml.append(b"") |
|
278 | 287 | elif at > 0: |
|
279 | 288 | ml = p1[fn].data().split(b"\n") |
@@ -4352,8 +4361,8 b' def debugwireproto(ui, repo, path=None, ' | |||
|
4352 | 4361 | |
|
4353 | 4362 | ``--peer`` can be used to bypass the handshake protocol and construct a |
|
4354 | 4363 | peer instance using the specified class type. Valid values are ``raw``, |
|
4355 |
`` |
|
|
4356 |
|
|
|
4364 | ``ssh1``. ``raw`` instances only allow sending raw data payloads and | |
|
4365 | don't support higher-level command actions. | |
|
4357 | 4366 | |
|
4358 | 4367 | ``--noreadstderr`` can be used to disable automatic reading from stderr |
|
4359 | 4368 | of the peer (for SSH connections only). Disabling automatic reading of |
@@ -4528,13 +4537,11 b' def debugwireproto(ui, repo, path=None, ' | |||
|
4528 | 4537 | |
|
4529 | 4538 | if opts[b'peer'] and opts[b'peer'] not in ( |
|
4530 | 4539 | b'raw', |
|
4531 | b'http2', | |
|
4532 | 4540 | b'ssh1', |
|
4533 | b'ssh2', | |
|
4534 | 4541 | ): |
|
4535 | 4542 | raise error.Abort( |
|
4536 | 4543 | _(b'invalid value for --peer'), |
|
4537 |
hint=_(b'valid values are "raw" |
|
|
4544 | hint=_(b'valid values are "raw" and "ssh1"'), | |
|
4538 | 4545 | ) |
|
4539 | 4546 | |
|
4540 | 4547 | if path and opts[b'localssh']: |
@@ -4602,18 +4609,6 b' def debugwireproto(ui, repo, path=None, ' | |||
|
4602 | 4609 | None, |
|
4603 | 4610 | autoreadstderr=autoreadstderr, |
|
4604 | 4611 | ) |
|
4605 | elif opts[b'peer'] == b'ssh2': | |
|
4606 | ui.write(_(b'creating ssh peer for wire protocol version 2\n')) | |
|
4607 | peer = sshpeer.sshv2peer( | |
|
4608 | ui, | |
|
4609 | url, | |
|
4610 | proc, | |
|
4611 | stdin, | |
|
4612 | stdout, | |
|
4613 | stderr, | |
|
4614 | None, | |
|
4615 | autoreadstderr=autoreadstderr, | |
|
4616 | ) | |
|
4617 | 4612 | elif opts[b'peer'] == b'raw': |
|
4618 | 4613 | ui.write(_(b'using raw connection to peer\n')) |
|
4619 | 4614 | peer = None |
@@ -4666,34 +4661,7 b' def debugwireproto(ui, repo, path=None, ' | |||
|
4666 | 4661 | |
|
4667 | 4662 | opener = urlmod.opener(ui, authinfo, **openerargs) |
|
4668 | 4663 | |
|
4669 |
if opts[b'peer'] == b' |
|
|
4670 | ui.write(_(b'creating http peer for wire protocol version 2\n')) | |
|
4671 | # We go through makepeer() because we need an API descriptor for | |
|
4672 | # the peer instance to be useful. | |
|
4673 | maybe_silent = ( | |
|
4674 | ui.silent() | |
|
4675 | if opts[b'nologhandshake'] | |
|
4676 | else util.nullcontextmanager() | |
|
4677 | ) | |
|
4678 | with maybe_silent, ui.configoverride( | |
|
4679 | {(b'experimental', b'httppeer.advertise-v2'): True} | |
|
4680 | ): | |
|
4681 | peer = httppeer.makepeer(ui, path, opener=opener) | |
|
4682 | ||
|
4683 | if not isinstance(peer, httppeer.httpv2peer): | |
|
4684 | raise error.Abort( | |
|
4685 | _( | |
|
4686 | b'could not instantiate HTTP peer for ' | |
|
4687 | b'wire protocol version 2' | |
|
4688 | ), | |
|
4689 | hint=_( | |
|
4690 | b'the server may not have the feature ' | |
|
4691 | b'enabled or is not allowing this ' | |
|
4692 | b'client version' | |
|
4693 | ), | |
|
4694 | ) | |
|
4695 | ||
|
4696 | elif opts[b'peer'] == b'raw': | |
|
4664 | if opts[b'peer'] == b'raw': | |
|
4697 | 4665 | ui.write(_(b'using raw connection to peer\n')) |
|
4698 | 4666 | peer = None |
|
4699 | 4667 | elif opts[b'peer']: |
@@ -4774,13 +4742,6 b' def debugwireproto(ui, repo, path=None, ' | |||
|
4774 | 4742 | with peer.commandexecutor() as e: |
|
4775 | 4743 | res = e.callcommand(command, args).result() |
|
4776 | 4744 | |
|
4777 | if isinstance(res, wireprotov2peer.commandresponse): | |
|
4778 | val = res.objects() | |
|
4779 | ui.status( | |
|
4780 | _(b'response: %s\n') | |
|
4781 | % stringutil.pprint(val, bprefix=True, indent=2) | |
|
4782 | ) | |
|
4783 | else: | |
|
4784 | 4745 | ui.status( |
|
4785 | 4746 | _(b'response: %s\n') |
|
4786 | 4747 | % stringutil.pprint(res, bprefix=True, indent=2) |
@@ -65,9 +65,8 b' def _destupdateobs(repo, clean):' | |||
|
65 | 65 | # replaced changesets: same as divergent except we know there |
|
66 | 66 | # is no conflict |
|
67 | 67 | # |
|
68 |
# pruned changeset: |
|
|
69 | # consider updating to the first non-obsolete parent, | |
|
70 | # similar to what is current done for 'hg prune' | |
|
68 | # pruned changeset: update to the closest non-obsolete ancestor, | |
|
69 | # similar to what 'hg prune' currently does | |
|
71 | 70 | |
|
72 | 71 | if successors: |
|
73 | 72 | # flatten the list here handles both divergent (len > 1) |
@@ -77,8 +76,15 b' def _destupdateobs(repo, clean):' | |||
|
77 | 76 | # get the max revision for the given successors set, |
|
78 | 77 | # i.e. the 'tip' of a set |
|
79 | 78 | node = repo.revs(b'max(%ln)', successors).first() |
|
80 | if bookmarks.isactivewdirparent(repo): | |
|
79 | else: | |
|
80 | p1 = p1.p1() | |
|
81 | while p1.obsolete(): | |
|
82 | p1 = p1.p1() | |
|
83 | node = p1.node() | |
|
84 | ||
|
85 | if node is not None and bookmarks.isactivewdirparent(repo): | |
|
81 | 86 |
|
|
87 | ||
|
82 | 88 | return node, movemark, None |
|
83 | 89 | |
|
84 | 90 |
@@ -12,6 +12,7 b' import contextlib' | |||
|
12 | 12 | import errno |
|
13 | 13 | import os |
|
14 | 14 | import stat |
|
15 | import uuid | |
|
15 | 16 | |
|
16 | 17 | from .i18n import _ |
|
17 | 18 | from .pycompat import delattr |
@@ -23,6 +24,7 b' from . import (' | |||
|
23 | 24 | encoding, |
|
24 | 25 | error, |
|
25 | 26 | match as matchmod, |
|
27 | node, | |
|
26 | 28 | pathutil, |
|
27 | 29 | policy, |
|
28 | 30 | pycompat, |
@@ -66,16 +68,6 b' class rootcache(filecache):' | |||
|
66 | 68 | return obj._join(fname) |
|
67 | 69 | |
|
68 | 70 | |
|
69 | def _getfsnow(vfs): | |
|
70 | '''Get "now" timestamp on filesystem''' | |
|
71 | tmpfd, tmpname = vfs.mkstemp() | |
|
72 | try: | |
|
73 | return timestamp.mtime_of(os.fstat(tmpfd)) | |
|
74 | finally: | |
|
75 | os.close(tmpfd) | |
|
76 | vfs.unlink(tmpname) | |
|
77 | ||
|
78 | ||
|
79 | 71 | def requires_parents_change(func): |
|
80 | 72 | def wrap(self, *args, **kwargs): |
|
81 | 73 | if not self.pendingparentchange(): |
@@ -109,6 +101,7 b' class dirstate(object):' | |||
|
109 | 101 | sparsematchfn, |
|
110 | 102 | nodeconstants, |
|
111 | 103 | use_dirstate_v2, |
|
104 | use_tracked_hint=False, | |
|
112 | 105 | ): |
|
113 | 106 | """Create a new dirstate object. |
|
114 | 107 | |
@@ -117,6 +110,7 b' class dirstate(object):' | |||
|
117 | 110 | the dirstate. |
|
118 | 111 | """ |
|
119 | 112 | self._use_dirstate_v2 = use_dirstate_v2 |
|
113 | self._use_tracked_hint = use_tracked_hint | |
|
120 | 114 | self._nodeconstants = nodeconstants |
|
121 | 115 | self._opener = opener |
|
122 | 116 | self._validate = validate |
@@ -125,12 +119,15 b' class dirstate(object):' | |||
|
125 | 119 | # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is |
|
126 | 120 | # UNC path pointing to root share (issue4557) |
|
127 | 121 | self._rootdir = pathutil.normasprefix(root) |
|
122 | # True is any internal state may be different | |
|
128 | 123 | self._dirty = False |
|
129 | self._lastnormaltime = timestamp.zero() | |
|
124 | # True if the set of tracked file may be different | |
|
125 | self._dirty_tracked_set = False | |
|
130 | 126 | self._ui = ui |
|
131 | 127 | self._filecache = {} |
|
132 | 128 | self._parentwriters = 0 |
|
133 | 129 | self._filename = b'dirstate' |
|
130 | self._filename_th = b'dirstate-tracked-hint' | |
|
134 | 131 | self._pendingfilename = b'%s.pending' % self._filename |
|
135 | 132 | self._plchangecallbacks = {} |
|
136 | 133 | self._origpl = None |
@@ -332,27 +329,6 b' class dirstate(object):' | |||
|
332 | 329 | return util.pconvert(path) |
|
333 | 330 | return path |
|
334 | 331 | |
|
335 | def __getitem__(self, key): | |
|
336 | """Return the current state of key (a filename) in the dirstate. | |
|
337 | ||
|
338 | States are: | |
|
339 | n normal | |
|
340 | m needs merging | |
|
341 | r marked for removal | |
|
342 | a marked for addition | |
|
343 | ? not tracked | |
|
344 | ||
|
345 | XXX The "state" is a bit obscure to be in the "public" API. we should | |
|
346 | consider migrating all user of this to going through the dirstate entry | |
|
347 | instead. | |
|
348 | """ | |
|
349 | msg = b"don't use dirstate[file], use dirstate.get_entry(file)" | |
|
350 | util.nouideprecwarn(msg, b'6.1', stacklevel=2) | |
|
351 | entry = self._map.get(key) | |
|
352 | if entry is not None: | |
|
353 | return entry.state | |
|
354 | return b'?' | |
|
355 | ||
|
356 | 332 | def get_entry(self, path): |
|
357 | 333 | """return a DirstateItem for the associated path""" |
|
358 | 334 | entry = self._map.get(path) |
@@ -440,8 +416,8 b' class dirstate(object):' | |||
|
440 | 416 | for a in ("_map", "_branch", "_ignore"): |
|
441 | 417 | if a in self.__dict__: |
|
442 | 418 | delattr(self, a) |
|
443 | self._lastnormaltime = timestamp.zero() | |
|
444 | 419 | self._dirty = False |
|
420 | self._dirty_tracked_set = False | |
|
445 | 421 | self._parentwriters = 0 |
|
446 | 422 | self._origpl = None |
|
447 | 423 | |
@@ -462,19 +438,26 b' class dirstate(object):' | |||
|
462 | 438 | return self._map.copymap |
|
463 | 439 | |
|
464 | 440 | @requires_no_parents_change |
|
465 | def set_tracked(self, filename): | |
|
441 | def set_tracked(self, filename, reset_copy=False): | |
|
466 | 442 | """a "public" method for generic code to mark a file as tracked |
|
467 | 443 | |
|
468 | 444 | This function is to be called outside of "update/merge" case. For |
|
469 | 445 | example by a command like `hg add X`. |
|
470 | 446 | |
|
447 | if reset_copy is set, any existing copy information will be dropped. | |
|
448 | ||
|
471 | 449 | return True the file was previously untracked, False otherwise. |
|
472 | 450 | """ |
|
473 | 451 | self._dirty = True |
|
474 | 452 | entry = self._map.get(filename) |
|
475 | 453 | if entry is None or not entry.tracked: |
|
476 | 454 | self._check_new_tracked_filename(filename) |
|
477 |
|
|
|
455 | pre_tracked = self._map.set_tracked(filename) | |
|
456 | if reset_copy: | |
|
457 | self._map.copymap.pop(filename, None) | |
|
458 | if pre_tracked: | |
|
459 | self._dirty_tracked_set = True | |
|
460 | return pre_tracked | |
|
478 | 461 | |
|
479 | 462 | @requires_no_parents_change |
|
480 | 463 | def set_untracked(self, filename): |
@@ -488,24 +471,17 b' class dirstate(object):' | |||
|
488 | 471 | ret = self._map.set_untracked(filename) |
|
489 | 472 | if ret: |
|
490 | 473 | self._dirty = True |
|
474 | self._dirty_tracked_set = True | |
|
491 | 475 | return ret |
|
492 | 476 | |
|
493 | 477 | @requires_no_parents_change |
|
494 |
def set_clean(self, filename, parentfiledata |
|
|
478 | def set_clean(self, filename, parentfiledata): | |
|
495 | 479 | """record that the current state of the file on disk is known to be clean""" |
|
496 | 480 | self._dirty = True |
|
497 | if parentfiledata: | |
|
498 | (mode, size, mtime) = parentfiledata | |
|
499 | else: | |
|
500 | (mode, size, mtime) = self._get_filedata(filename) | |
|
501 | 481 | if not self._map[filename].tracked: |
|
502 | 482 | self._check_new_tracked_filename(filename) |
|
483 | (mode, size, mtime) = parentfiledata | |
|
503 | 484 | self._map.set_clean(filename, mode, size, mtime) |
|
504 | if mtime > self._lastnormaltime: | |
|
505 | # Remember the most recent modification timeslot for status(), | |
|
506 | # to make sure we won't miss future size-preserving file content | |
|
507 | # modifications that happen within the same timeslot. | |
|
508 | self._lastnormaltime = mtime | |
|
509 | 485 | |
|
510 | 486 | @requires_no_parents_change |
|
511 | 487 | def set_possibly_dirty(self, filename): |
@@ -544,10 +520,6 b' class dirstate(object):' | |||
|
544 | 520 | if entry is not None and entry.added: |
|
545 | 521 | return # avoid dropping copy information (maybe?) |
|
546 | 522 | |
|
547 | parentfiledata = None | |
|
548 | if wc_tracked and p1_tracked: | |
|
549 | parentfiledata = self._get_filedata(filename) | |
|
550 | ||
|
551 | 523 | self._map.reset_state( |
|
552 | 524 | filename, |
|
553 | 525 | wc_tracked, |
@@ -555,16 +527,7 b' class dirstate(object):' | |||
|
555 | 527 | # the underlying reference might have changed, we will have to |
|
556 | 528 | # check it. |
|
557 | 529 | has_meaningful_mtime=False, |
|
558 | parentfiledata=parentfiledata, | |
|
559 | 530 | ) |
|
560 | if ( | |
|
561 | parentfiledata is not None | |
|
562 | and parentfiledata[2] > self._lastnormaltime | |
|
563 | ): | |
|
564 | # Remember the most recent modification timeslot for status(), | |
|
565 | # to make sure we won't miss future size-preserving file content | |
|
566 | # modifications that happen within the same timeslot. | |
|
567 | self._lastnormaltime = parentfiledata[2] | |
|
568 | 531 | |
|
569 | 532 | @requires_parents_change |
|
570 | 533 | def update_file( |
@@ -593,13 +556,13 b' class dirstate(object):' | |||
|
593 | 556 | # this. The test agrees |
|
594 | 557 | |
|
595 | 558 | self._dirty = True |
|
596 | ||
|
597 | need_parent_file_data = ( | |
|
598 | not possibly_dirty and not p2_info and wc_tracked and p1_tracked | |
|
599 |
|
|
|
600 | ||
|
601 | if need_parent_file_data and parentfiledata is None: | |
|
602 | parentfiledata = self._get_filedata(filename) | |
|
559 | old_entry = self._map.get(filename) | |
|
560 | if old_entry is None: | |
|
561 | prev_tracked = False | |
|
562 | else: | |
|
563 | prev_tracked = old_entry.tracked | |
|
564 | if prev_tracked != wc_tracked: | |
|
565 | self._dirty_tracked_set = True | |
|
603 | 566 | |
|
604 | 567 | self._map.reset_state( |
|
605 | 568 | filename, |
@@ -609,14 +572,6 b' class dirstate(object):' | |||
|
609 | 572 | has_meaningful_mtime=not possibly_dirty, |
|
610 | 573 | parentfiledata=parentfiledata, |
|
611 | 574 | ) |
|
612 | if ( | |
|
613 | parentfiledata is not None | |
|
614 | and parentfiledata[2] > self._lastnormaltime | |
|
615 | ): | |
|
616 | # Remember the most recent modification timeslot for status(), | |
|
617 | # to make sure we won't miss future size-preserving file content | |
|
618 | # modifications that happen within the same timeslot. | |
|
619 | self._lastnormaltime = parentfiledata[2] | |
|
620 | 575 | |
|
621 | 576 | def _check_new_tracked_filename(self, filename): |
|
622 | 577 | scmutil.checkfilename(filename) |
@@ -634,14 +589,6 b' class dirstate(object):' | |||
|
634 | 589 | msg %= (pycompat.bytestr(d), pycompat.bytestr(filename)) |
|
635 | 590 | raise error.Abort(msg) |
|
636 | 591 | |
|
637 | def _get_filedata(self, filename): | |
|
638 | """returns""" | |
|
639 | s = os.lstat(self._join(filename)) | |
|
640 | mode = s.st_mode | |
|
641 | size = s.st_size | |
|
642 | mtime = timestamp.mtime_of(s) | |
|
643 | return (mode, size, mtime) | |
|
644 | ||
|
645 | 592 | def _discoverpath(self, path, normed, ignoremissing, exists, storemap): |
|
646 | 593 | if exists is None: |
|
647 | 594 | exists = os.path.lexists(os.path.join(self._root, path)) |
@@ -720,7 +667,6 b' class dirstate(object):' | |||
|
720 | 667 | |
|
721 | 668 | def clear(self): |
|
722 | 669 | self._map.clear() |
|
723 | self._lastnormaltime = timestamp.zero() | |
|
724 | 670 | self._dirty = True |
|
725 | 671 | |
|
726 | 672 | def rebuild(self, parent, allfiles, changedfiles=None): |
@@ -728,9 +674,7 b' class dirstate(object):' | |||
|
728 | 674 | # Rebuild entire dirstate |
|
729 | 675 | to_lookup = allfiles |
|
730 | 676 | to_drop = [] |
|
731 | lastnormaltime = self._lastnormaltime | |
|
732 | 677 | self.clear() |
|
733 | self._lastnormaltime = lastnormaltime | |
|
734 | 678 | elif len(changedfiles) < 10: |
|
735 | 679 | # Avoid turning allfiles into a set, which can be expensive if it's |
|
736 | 680 | # large. |
@@ -777,28 +721,41 b' class dirstate(object):' | |||
|
777 | 721 | if not self._dirty: |
|
778 | 722 | return |
|
779 | 723 | |
|
780 | filename = self._filename | |
|
724 | write_key = self._use_tracked_hint and self._dirty_tracked_set | |
|
781 | 725 | if tr: |
|
782 | # 'dirstate.write()' is not only for writing in-memory | |
|
783 | # changes out, but also for dropping ambiguous timestamp. | |
|
784 | # delayed writing re-raise "ambiguous timestamp issue". | |
|
785 | # See also the wiki page below for detail: | |
|
786 | # https://www.mercurial-scm.org/wiki/DirstateTransactionPlan | |
|
787 | ||
|
788 | # record when mtime start to be ambiguous | |
|
789 | now = _getfsnow(self._opener) | |
|
790 | ||
|
791 | 726 | # delay writing in-memory changes out |
|
792 | 727 | tr.addfilegenerator( |
|
793 | b'dirstate', | |
|
728 | b'dirstate-1-main', | |
|
794 | 729 | (self._filename,), |
|
795 |
lambda f: self._writedirstate(tr, f |
|
|
730 | lambda f: self._writedirstate(tr, f), | |
|
796 | 731 | location=b'plain', |
|
732 | post_finalize=True, | |
|
733 | ) | |
|
734 | if write_key: | |
|
735 | tr.addfilegenerator( | |
|
736 | b'dirstate-2-key-post', | |
|
737 | (self._filename_th,), | |
|
738 | lambda f: self._write_tracked_hint(tr, f), | |
|
739 | location=b'plain', | |
|
740 | post_finalize=True, | |
|
797 | 741 | ) |
|
798 | 742 | return |
|
799 | 743 | |
|
800 |
|
|
|
801 | self._writedirstate(tr, st) | |
|
744 | file = lambda f: self._opener(f, b"w", atomictemp=True, checkambig=True) | |
|
745 | with file(self._filename) as f: | |
|
746 | self._writedirstate(tr, f) | |
|
747 | if write_key: | |
|
748 | # we update the key-file after writing to make sure reader have a | |
|
749 | # key that match the newly written content | |
|
750 | with file(self._filename_th) as f: | |
|
751 | self._write_tracked_hint(tr, f) | |
|
752 | ||
|
753 | def delete_tracked_hint(self): | |
|
754 | """remove the tracked_hint file | |
|
755 | ||
|
756 | To be used by format downgrades operation""" | |
|
757 | self._opener.unlink(self._filename_th) | |
|
758 | self._use_tracked_hint = False | |
|
802 | 759 | |
|
803 | 760 | def addparentchangecallback(self, category, callback): |
|
804 | 761 | """add a callback to be called when the wd parents are changed |
@@ -811,7 +768,7 b' class dirstate(object):' | |||
|
811 | 768 | """ |
|
812 | 769 | self._plchangecallbacks[category] = callback |
|
813 | 770 | |
|
814 |
def _writedirstate(self, tr, st |
|
|
771 | def _writedirstate(self, tr, st): | |
|
815 | 772 | # notify callbacks about parents change |
|
816 | 773 | if self._origpl is not None and self._origpl != self._pl: |
|
817 | 774 | for c, callback in sorted( |
@@ -819,34 +776,13 b' class dirstate(object):' | |||
|
819 | 776 | ): |
|
820 | 777 | callback(self, self._origpl, self._pl) |
|
821 | 778 | self._origpl = None |
|
822 | ||
|
823 | if now is None: | |
|
824 | # use the modification time of the newly created temporary file as the | |
|
825 | # filesystem's notion of 'now' | |
|
826 | now = timestamp.mtime_of(util.fstat(st)) | |
|
779 | self._map.write(tr, st) | |
|
780 | self._dirty = False | |
|
781 | self._dirty_tracked_set = False | |
|
827 | 782 | |
|
828 | # enough 'delaywrite' prevents 'pack_dirstate' from dropping | |
|
829 | # timestamp of each entries in dirstate, because of 'now > mtime' | |
|
830 | delaywrite = self._ui.configint(b'debug', b'dirstate.delaywrite') | |
|
831 | if delaywrite > 0: | |
|
832 | # do we have any files to delay for? | |
|
833 | for f, e in pycompat.iteritems(self._map): | |
|
834 | if e.need_delay(now): | |
|
835 | import time # to avoid useless import | |
|
836 | ||
|
837 | # rather than sleep n seconds, sleep until the next | |
|
838 | # multiple of n seconds | |
|
839 | clock = time.time() | |
|
840 | start = int(clock) - (int(clock) % delaywrite) | |
|
841 | end = start + delaywrite | |
|
842 | time.sleep(end - clock) | |
|
843 | # trust our estimate that the end is near now | |
|
844 | now = timestamp.timestamp((end, 0)) | |
|
845 | break | |
|
846 | ||
|
847 | self._map.write(tr, st, now) | |
|
848 | self._lastnormaltime = timestamp.zero() | |
|
849 | self._dirty = False | |
|
783 | def _write_tracked_hint(self, tr, f): | |
|
784 | key = node.hex(uuid.uuid4().bytes) | |
|
785 | f.write(b"1\n%s\n" % key) # 1 is the format version | |
|
850 | 786 | |
|
851 | 787 | def _dirignore(self, f): |
|
852 | 788 | if self._ignore(f): |
@@ -1243,7 +1179,6 b' class dirstate(object):' | |||
|
1243 | 1179 | self._rootdir, |
|
1244 | 1180 | self._ignorefiles(), |
|
1245 | 1181 | self._checkexec, |
|
1246 | self._lastnormaltime, | |
|
1247 | 1182 | bool(list_clean), |
|
1248 | 1183 | bool(list_ignored), |
|
1249 | 1184 | bool(list_unknown), |
@@ -1335,11 +1270,20 b' class dirstate(object):' | |||
|
1335 | 1270 | # Some matchers have yet to be implemented |
|
1336 | 1271 | use_rust = False |
|
1337 | 1272 | |
|
1273 | # Get the time from the filesystem so we can disambiguate files that | |
|
1274 | # appear modified in the present or future. | |
|
1275 | try: | |
|
1276 | mtime_boundary = timestamp.get_fs_now(self._opener) | |
|
1277 | except OSError: | |
|
1278 | # In largefiles or readonly context | |
|
1279 | mtime_boundary = None | |
|
1280 | ||
|
1338 | 1281 | if use_rust: |
|
1339 | 1282 | try: |
|
1340 |
re |
|
|
1283 | res = self._rust_status( | |
|
1341 | 1284 | match, listclean, listignored, listunknown |
|
1342 | 1285 | ) |
|
1286 | return res + (mtime_boundary,) | |
|
1343 | 1287 | except rustmod.FallbackError: |
|
1344 | 1288 | pass |
|
1345 | 1289 | |
@@ -1361,7 +1305,6 b' class dirstate(object):' | |||
|
1361 | 1305 | checkexec = self._checkexec |
|
1362 | 1306 | checklink = self._checklink |
|
1363 | 1307 | copymap = self._map.copymap |
|
1364 | lastnormaltime = self._lastnormaltime | |
|
1365 | 1308 | |
|
1366 | 1309 | # We need to do full walks when either |
|
1367 | 1310 | # - we're listing all clean files, or |
@@ -1417,19 +1360,17 b' class dirstate(object):' | |||
|
1417 | 1360 | else: |
|
1418 | 1361 | madd(fn) |
|
1419 | 1362 | elif not t.mtime_likely_equal_to(timestamp.mtime_of(st)): |
|
1420 | ladd(fn) | |
|
1421 | elif timestamp.mtime_of(st) == lastnormaltime: | |
|
1422 | # fn may have just been marked as normal and it may have | |
|
1423 | # changed in the same second without changing its size. | |
|
1424 | # This can happen if we quickly do multiple commits. | |
|
1425 | # Force lookup, so we don't miss such a racy file change. | |
|
1363 | # There might be a change in the future if for example the | |
|
1364 | # internal clock is off, but this is a case where the issues | |
|
1365 | # the user would face would be a lot worse and there is | |
|
1366 | # nothing we can really do. | |
|
1426 | 1367 | ladd(fn) |
|
1427 | 1368 | elif listclean: |
|
1428 | 1369 | cadd(fn) |
|
1429 | 1370 | status = scmutil.status( |
|
1430 | 1371 | modified, added, removed, deleted, unknown, ignored, clean |
|
1431 | 1372 | ) |
|
1432 | return (lookup, status) | |
|
1373 | return (lookup, status, mtime_boundary) | |
|
1433 | 1374 | |
|
1434 | 1375 | def matches(self, match): |
|
1435 | 1376 | """ |
@@ -1477,10 +1418,11 b' class dirstate(object):' | |||
|
1477 | 1418 | # changes written out above, even if dirstate is never |
|
1478 | 1419 | # changed after this |
|
1479 | 1420 | tr.addfilegenerator( |
|
1480 | b'dirstate', | |
|
1421 | b'dirstate-1-main', | |
|
1481 | 1422 | (self._filename,), |
|
1482 | 1423 | lambda f: self._writedirstate(tr, f), |
|
1483 | 1424 | location=b'plain', |
|
1425 | post_finalize=True, | |
|
1484 | 1426 | ) |
|
1485 | 1427 | |
|
1486 | 1428 | # ensure that pending file written above is unlinked at |
@@ -444,13 +444,13 b' class dirstatemap(_dirstatemapcommon):' | |||
|
444 | 444 | self.__getitem__ = self._map.__getitem__ |
|
445 | 445 | self.get = self._map.get |
|
446 | 446 | |
|
447 |
def write(self, tr, st |
|
|
447 | def write(self, tr, st): | |
|
448 | 448 | if self._use_dirstate_v2: |
|
449 |
packed, meta = v2.pack_dirstate(self._map, self.copymap |
|
|
449 | packed, meta = v2.pack_dirstate(self._map, self.copymap) | |
|
450 | 450 | self.write_v2_no_append(tr, st, meta, packed) |
|
451 | 451 | else: |
|
452 | 452 | packed = parsers.pack_dirstate( |
|
453 |
self._map, self.copymap, self.parents() |
|
|
453 | self._map, self.copymap, self.parents() | |
|
454 | 454 | ) |
|
455 | 455 | st.write(packed) |
|
456 | 456 | st.close() |
@@ -655,10 +655,10 b' if rustmod is not None:' | |||
|
655 | 655 | self._map |
|
656 | 656 | return self.identity |
|
657 | 657 | |
|
658 |
def write(self, tr, st |
|
|
658 | def write(self, tr, st): | |
|
659 | 659 | if not self._use_dirstate_v2: |
|
660 | 660 | p1, p2 = self.parents() |
|
661 |
packed = self._map.write_v1(p1, p2 |
|
|
661 | packed = self._map.write_v1(p1, p2) | |
|
662 | 662 | st.write(packed) |
|
663 | 663 | st.close() |
|
664 | 664 | self._dirtyparents = False |
@@ -666,7 +666,7 b' if rustmod is not None:' | |||
|
666 | 666 | |
|
667 | 667 | # We can only append to an existing data file if there is one |
|
668 | 668 | can_append = self.docket.uuid is not None |
|
669 |
packed, meta, append = self._map.write_v2( |
|
|
669 | packed, meta, append = self._map.write_v2(can_append) | |
|
670 | 670 | if append: |
|
671 | 671 | docket = self.docket |
|
672 | 672 | data_filename = docket.data_filename() |
@@ -6,8 +6,11 b'' | |||
|
6 | 6 | from __future__ import absolute_import |
|
7 | 7 | |
|
8 | 8 | import functools |
|
9 | import os | |
|
9 | 10 | import stat |
|
10 | 11 | |
|
12 | from .. import error | |
|
13 | ||
|
11 | 14 | |
|
12 | 15 | rangemask = 0x7FFFFFFF |
|
13 | 16 | |
@@ -18,40 +21,45 b' class timestamp(tuple):' | |||
|
18 | 21 | A Unix timestamp with optional nanoseconds precision, |
|
19 | 22 | modulo 2**31 seconds. |
|
20 | 23 | |
|
21 |
A |
|
|
24 | A 3-tuple containing: | |
|
22 | 25 | |
|
23 | 26 | `truncated_seconds`: seconds since the Unix epoch, |
|
24 | 27 | truncated to its lower 31 bits |
|
25 | 28 | |
|
26 | 29 | `subsecond_nanoseconds`: number of nanoseconds since `truncated_seconds`. |
|
27 | 30 | When this is zero, the sub-second precision is considered unknown. |
|
31 | ||
|
32 | `second_ambiguous`: whether this timestamp is still "reliable" | |
|
33 | (see `reliable_mtime_of`) if we drop its sub-second component. | |
|
28 | 34 | """ |
|
29 | 35 | |
|
30 | 36 | def __new__(cls, value): |
|
31 | truncated_seconds, subsec_nanos = value | |
|
32 | value = (truncated_seconds & rangemask, subsec_nanos) | |
|
37 | truncated_seconds, subsec_nanos, second_ambiguous = value | |
|
38 | value = (truncated_seconds & rangemask, subsec_nanos, second_ambiguous) | |
|
33 | 39 | return super(timestamp, cls).__new__(cls, value) |
|
34 | 40 | |
|
35 | 41 | def __eq__(self, other): |
|
36 | self_secs, self_subsec_nanos = self | |
|
37 | other_secs, other_subsec_nanos = other | |
|
38 | return self_secs == other_secs and ( | |
|
39 | self_subsec_nanos == other_subsec_nanos | |
|
40 | or self_subsec_nanos == 0 | |
|
41 | or other_subsec_nanos == 0 | |
|
42 | raise error.ProgrammingError( | |
|
43 | 'timestamp should never be compared directly' | |
|
42 | 44 | ) |
|
43 | 45 | |
|
44 | 46 | def __gt__(self, other): |
|
45 | self_secs, self_subsec_nanos = self | |
|
46 | other_secs, other_subsec_nanos = other | |
|
47 | if self_secs > other_secs: | |
|
48 | return True | |
|
49 | if self_secs < other_secs: | |
|
50 | return False | |
|
51 | if self_subsec_nanos == 0 or other_subsec_nanos == 0: | |
|
52 | # they are considered equal, so not "greater than" | |
|
53 | return False | |
|
54 | return self_subsec_nanos > other_subsec_nanos | |
|
47 | raise error.ProgrammingError( | |
|
48 | 'timestamp should never be compared directly' | |
|
49 | ) | |
|
50 | ||
|
51 | ||
|
52 | def get_fs_now(vfs): | |
|
53 | """return a timestamp for "now" in the current vfs | |
|
54 | ||
|
55 | This will raise an exception if no temporary files could be created. | |
|
56 | """ | |
|
57 | tmpfd, tmpname = vfs.mkstemp() | |
|
58 | try: | |
|
59 | return mtime_of(os.fstat(tmpfd)) | |
|
60 | finally: | |
|
61 | os.close(tmpfd) | |
|
62 | vfs.unlink(tmpname) | |
|
55 | 63 | |
|
56 | 64 | |
|
57 | 65 | def zero(): |
@@ -84,4 +92,37 b' def mtime_of(stat_result):' | |||
|
84 | 92 | secs = nanos // billion |
|
85 | 93 | subsec_nanos = nanos % billion |
|
86 | 94 | |
|
87 | return timestamp((secs, subsec_nanos)) | |
|
95 | return timestamp((secs, subsec_nanos, False)) | |
|
96 | ||
|
97 | ||
|
98 | def reliable_mtime_of(stat_result, present_mtime): | |
|
99 | """Same as `mtime_of`, but return `None` or a `Timestamp` with | |
|
100 | `second_ambiguous` set if the date might be ambiguous. | |
|
101 | ||
|
102 | A modification time is reliable if it is older than "present_time" (or | |
|
103 | sufficiently in the future). | |
|
104 | ||
|
105 | Otherwise a concurrent modification might happens with the same mtime. | |
|
106 | """ | |
|
107 | file_mtime = mtime_of(stat_result) | |
|
108 | file_second = file_mtime[0] | |
|
109 | file_ns = file_mtime[1] | |
|
110 | boundary_second = present_mtime[0] | |
|
111 | boundary_ns = present_mtime[1] | |
|
112 | # If the mtime of the ambiguous file is younger (or equal) to the starting | |
|
113 | # point of the `status` walk, we cannot garantee that another, racy, write | |
|
114 | # will not happen right after with the same mtime and we cannot cache the | |
|
115 | # information. | |
|
116 | # | |
|
117 | # However if the mtime is far away in the future, this is likely some | |
|
118 | # mismatch between the current clock and previous file system operation. So | |
|
119 | # mtime more than one days in the future are considered fine. | |
|
120 | if boundary_second == file_second: | |
|
121 | if file_ns and boundary_ns: | |
|
122 | if file_ns < boundary_ns: | |
|
123 | return timestamp((file_second, file_ns, True)) | |
|
124 | return None | |
|
125 | elif boundary_second < file_second < (3600 * 24 + boundary_second): | |
|
126 | return None | |
|
127 | else: | |
|
128 | return file_mtime |
@@ -174,12 +174,10 b' class Node(object):' | |||
|
174 | 174 | ) |
|
175 | 175 | |
|
176 | 176 | |
|
177 |
def pack_dirstate(map, copy_map |
|
|
177 | def pack_dirstate(map, copy_map): | |
|
178 | 178 | """ |
|
179 | 179 | Pack `map` and `copy_map` into the dirstate v2 binary format and return |
|
180 | 180 | the bytearray. |
|
181 | `now` is a timestamp of the current filesystem time used to detect race | |
|
182 | conditions in writing the dirstate to disk, see inline comment. | |
|
183 | 181 | |
|
184 | 182 | The on-disk format expects a tree-like structure where the leaves are |
|
185 | 183 | written first (and sorted per-directory), going up levels until the root |
@@ -284,17 +282,6 b' def pack_dirstate(map, copy_map, now):' | |||
|
284 | 282 | stack.append(current_node) |
|
285 | 283 | |
|
286 | 284 | for index, (path, entry) in enumerate(sorted_map, 1): |
|
287 | if entry.need_delay(now): | |
|
288 | # The file was last modified "simultaneously" with the current | |
|
289 | # write to dirstate (i.e. within the same second for file- | |
|
290 | # systems with a granularity of 1 sec). This commonly happens | |
|
291 | # for at least a couple of files on 'update'. | |
|
292 | # The user could change the file without changing its size | |
|
293 | # within the same second. Invalidate the file's mtime in | |
|
294 | # dirstate, forcing future 'status' calls to compare the | |
|
295 | # contents of the file if the size is the same. This prevents | |
|
296 | # mistakenly treating such files as clean. | |
|
297 | entry.set_possibly_dirty() | |
|
298 | 285 | nodes_with_entry_count += 1 |
|
299 | 286 | if path in copy_map: |
|
300 | 287 | nodes_with_copy_source_count += 1 |
@@ -19,6 +19,7 b' from . import (' | |||
|
19 | 19 | bookmarks, |
|
20 | 20 | branchmap, |
|
21 | 21 | error, |
|
22 | obsolete, | |
|
22 | 23 | phases, |
|
23 | 24 | pycompat, |
|
24 | 25 | scmutil, |
@@ -141,17 +142,6 b' class outgoing(object):' | |||
|
141 | 142 | self._computecommonmissing() |
|
142 | 143 | return self._missing |
|
143 | 144 | |
|
144 | @property | |
|
145 | def missingheads(self): | |
|
146 | util.nouideprecwarn( | |
|
147 | b'outgoing.missingheads never contained what the name suggests and ' | |
|
148 | b'was renamed to outgoing.ancestorsof. check your code for ' | |
|
149 | b'correctness.', | |
|
150 | b'5.5', | |
|
151 | stacklevel=2, | |
|
152 | ) | |
|
153 | return self.ancestorsof | |
|
154 | ||
|
155 | 145 | |
|
156 | 146 | def findcommonoutgoing( |
|
157 | 147 | repo, other, onlyheads=None, force=False, commoninc=None, portable=False |
@@ -556,12 +546,16 b' def _postprocessobsolete(pushop, futurec' | |||
|
556 | 546 | if len(localcandidate) == 1: |
|
557 | 547 | return unknownheads | set(candidate_newhs), set() |
|
558 | 548 | |
|
549 | obsrevs = obsolete.getrevs(unfi, b'obsolete') | |
|
550 | futurenonobsolete = frozenset(futurecommon) - obsrevs | |
|
551 | ||
|
559 | 552 | # actually process branch replacement |
|
560 | 553 | while localcandidate: |
|
561 | 554 | nh = localcandidate.pop() |
|
555 | r = torev(nh) | |
|
562 | 556 | current_branch = unfi[nh].branch() |
|
563 | 557 | # run this check early to skip the evaluation of the whole branch |
|
564 | if torev(nh) in futurecommon or ispublic(torev(nh)): | |
|
558 | if ispublic(r) or r not in obsrevs: | |
|
565 | 559 | newhs.add(nh) |
|
566 | 560 | continue |
|
567 | 561 | |
@@ -583,7 +577,7 b' def _postprocessobsolete(pushop, futurec' | |||
|
583 | 577 | # * if we have no markers to push to obsolete it. |
|
584 | 578 | if ( |
|
585 | 579 | any(ispublic(r) for r in branchrevs) |
|
586 |
or any(torev(n) in future |
|
|
580 | or any(torev(n) in futurenonobsolete for n in branchnodes) | |
|
587 | 581 | or any(not hasoutmarker(n) for n in branchnodes) |
|
588 | 582 | ): |
|
589 | 583 | newhs.add(nh) |
@@ -511,17 +511,21 b" def trim(s, width, ellipsis=b'', leftsid" | |||
|
511 | 511 | if width <= 0: # no enough room even for ellipsis |
|
512 | 512 | return ellipsis[: width + len(ellipsis)] |
|
513 | 513 | |
|
514 | chars = list(u) | |
|
514 | 515 | if leftside: |
|
515 | uslice = lambda i: u[i:] | |
|
516 | concat = lambda s: ellipsis + s | |
|
517 | else: | |
|
518 | uslice = lambda i: u[:-i] | |
|
519 | concat = lambda s: s + ellipsis | |
|
520 | for i in pycompat.xrange(1, len(u)): | |
|
521 | usub = uslice(i) | |
|
522 | if ucolwidth(usub) <= width: | |
|
523 | return concat(usub.encode(_sysstr(encoding))) | |
|
524 | return ellipsis # no enough room for multi-column characters | |
|
516 | chars.reverse() | |
|
517 | width_so_far = 0 | |
|
518 | for i, c in enumerate(chars): | |
|
519 | width_so_far += ucolwidth(c) | |
|
520 | if width_so_far > width: | |
|
521 | break | |
|
522 | chars = chars[:i] | |
|
523 | if leftside: | |
|
524 | chars.reverse() | |
|
525 | u = u''.join(chars).encode(_sysstr(encoding)) | |
|
526 | if leftside: | |
|
527 | return ellipsis + u | |
|
528 | return u + ellipsis | |
|
525 | 529 | |
|
526 | 530 | |
|
527 | 531 | class normcasespecs(object): |
@@ -388,6 +388,14 b' class PatchError(Exception):' | |||
|
388 | 388 | __bytes__ = _tobytes |
|
389 | 389 | |
|
390 | 390 | |
|
391 | class PatchParseError(PatchError): | |
|
392 | __bytes__ = _tobytes | |
|
393 | ||
|
394 | ||
|
395 | class PatchApplicationError(PatchError): | |
|
396 | __bytes__ = _tobytes | |
|
397 | ||
|
398 | ||
|
391 | 399 | def getsimilar(symbols, value): |
|
392 | 400 | # type: (Iterable[bytes], bytes) -> List[bytes] |
|
393 | 401 | sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio() |
@@ -22,7 +22,6 b' from . import (' | |||
|
22 | 22 | changegroup, |
|
23 | 23 | discovery, |
|
24 | 24 | error, |
|
25 | exchangev2, | |
|
26 | 25 | lock as lockmod, |
|
27 | 26 | logexchange, |
|
28 | 27 | narrowspec, |
@@ -522,8 +521,16 b' def _pushdiscovery(pushop):' | |||
|
522 | 521 | |
|
523 | 522 | def _checksubrepostate(pushop): |
|
524 | 523 | """Ensure all outgoing referenced subrepo revisions are present locally""" |
|
524 | ||
|
525 | repo = pushop.repo | |
|
526 | ||
|
527 | # If the repository does not use subrepos, skip the expensive | |
|
528 | # manifest checks. | |
|
529 | if not len(repo.file(b'.hgsub')) or not len(repo.file(b'.hgsubstate')): | |
|
530 | return | |
|
531 | ||
|
525 | 532 | for n in pushop.outgoing.missing: |
|
526 |
ctx = |
|
|
533 | ctx = repo[n] | |
|
527 | 534 | |
|
528 | 535 | if b'.hgsub' in ctx.manifest() and b'.hgsubstate' in ctx.files(): |
|
529 | 536 | for subpath in sorted(ctx.substate): |
@@ -1666,10 +1673,6 b' def pull(' | |||
|
1666 | 1673 | ): |
|
1667 | 1674 | add_confirm_callback(repo, pullop) |
|
1668 | 1675 | |
|
1669 | # Use the modern wire protocol, if available. | |
|
1670 | if remote.capable(b'command-changesetdata'): | |
|
1671 | exchangev2.pull(pullop) | |
|
1672 | else: | |
|
1673 | 1676 |
|
|
1674 | 1677 |
|
|
1675 | 1678 |
|
@@ -282,6 +282,7 b' def loadall(ui, whitelist=None):' | |||
|
282 | 282 | result = ui.configitems(b"extensions") |
|
283 | 283 | if whitelist is not None: |
|
284 | 284 | result = [(k, v) for (k, v) in result if k in whitelist] |
|
285 | result = [(k, v) for (k, v) in result if b':' not in k] | |
|
285 | 286 | newindex = len(_order) |
|
286 | 287 | ui.log( |
|
287 | 288 | b'extension', |
@@ -290,6 +291,8 b' def loadall(ui, whitelist=None):' | |||
|
290 | 291 | ) |
|
291 | 292 | ui.log(b'extension', b'- processing %d entries\n', len(result)) |
|
292 | 293 | with util.timedcm('load all extensions') as stats: |
|
294 | default_sub_options = ui.configsuboptions(b"extensions", b"*")[1] | |
|
295 | ||
|
293 | 296 | for (name, path) in result: |
|
294 | 297 | if path: |
|
295 | 298 | if path[0:1] == b'!': |
@@ -306,15 +309,29 b' def loadall(ui, whitelist=None):' | |||
|
306 | 309 | except Exception as inst: |
|
307 | 310 | msg = stringutil.forcebytestr(inst) |
|
308 | 311 | if path: |
|
309 |
|
|
|
310 |
|
|
|
311 | % (name, path, msg) | |
|
312 | error_msg = _( | |
|
313 | b'failed to import extension "%s" from %s: %s' | |
|
312 | 314 | ) |
|
315 | error_msg %= (name, path, msg) | |
|
313 | 316 | else: |
|
314 | ui.warn( | |
|
315 | _(b"*** failed to import extension %s: %s\n") | |
|
316 | % (name, msg) | |
|
317 | error_msg = _(b'failed to import extension "%s": %s') | |
|
318 | error_msg %= (name, msg) | |
|
319 | ||
|
320 | options = default_sub_options.copy() | |
|
321 | ext_options = ui.configsuboptions(b"extensions", name)[1] | |
|
322 | options.update(ext_options) | |
|
323 | if stringutil.parsebool(options.get(b"required", b'no')): | |
|
324 | hint = None | |
|
325 | if isinstance(inst, error.Hint) and inst.hint: | |
|
326 | hint = inst.hint | |
|
327 | if hint is None: | |
|
328 | hint = _( | |
|
329 | b"loading of this extension was required, " | |
|
330 | b"see `hg help config.extensions` for details" | |
|
317 | 331 | ) |
|
332 | raise error.Abort(error_msg, hint=hint) | |
|
333 | else: | |
|
334 | ui.warn((b"*** %s\n") % error_msg) | |
|
318 | 335 | if isinstance(inst, error.Hint) and inst.hint: |
|
319 | 336 | ui.warn(_(b"*** (%s)\n") % inst.hint) |
|
320 | 337 | ui.traceback() |
@@ -97,8 +97,8 b' class filelog(object):' | |||
|
97 | 97 | def iscensored(self, rev): |
|
98 | 98 | return self._revlog.iscensored(rev) |
|
99 | 99 | |
|
100 |
def revision(self, node, _df=None |
|
|
101 |
return self._revlog.revision(node, _df=_df |
|
|
100 | def revision(self, node, _df=None): | |
|
101 | return self._revlog.revision(node, _df=_df) | |
|
102 | 102 | |
|
103 | 103 | def rawdata(self, node, _df=None): |
|
104 | 104 | return self._revlog.rawdata(node, _df=_df) |
@@ -19,7 +19,6 b' from .node import (' | |||
|
19 | 19 | ) |
|
20 | 20 | from .pycompat import ( |
|
21 | 21 | getattr, |
|
22 | open, | |
|
23 | 22 | ) |
|
24 | 23 | |
|
25 | 24 | from . import ( |
@@ -293,9 +292,9 b' def _eoltype(data):' | |||
|
293 | 292 | return None # unknown |
|
294 | 293 | |
|
295 | 294 | |
|
296 | def _matcheol(file, back): | |
|
295 | def _matcheol(file, backup): | |
|
297 | 296 | """Convert EOL markers in a file to match origfile""" |
|
298 | tostyle = _eoltype(back.data()) # No repo.wread filters? | |
|
297 | tostyle = _eoltype(backup.data()) # No repo.wread filters? | |
|
299 | 298 | if tostyle: |
|
300 | 299 | data = util.readfile(file) |
|
301 | 300 | style = _eoltype(data) |
@@ -306,27 +305,27 b' def _matcheol(file, back):' | |||
|
306 | 305 | |
|
307 | 306 | |
|
308 | 307 | @internaltool(b'prompt', nomerge) |
|
309 |
def _iprompt(repo, mynode, |
|
|
308 | def _iprompt(repo, mynode, local, other, base, toolconf): | |
|
310 | 309 | """Asks the user which of the local `p1()` or the other `p2()` version to |
|
311 | 310 | keep as the merged version.""" |
|
312 | 311 | ui = repo.ui |
|
313 |
fd = |
|
|
312 | fd = local.fctx.path() | |
|
314 | 313 | uipathfn = scmutil.getuipathfn(repo) |
|
315 | 314 | |
|
316 | 315 | # Avoid prompting during an in-memory merge since it doesn't support merge |
|
317 | 316 | # conflicts. |
|
318 |
if |
|
|
317 | if local.fctx.changectx().isinmemory(): | |
|
319 | 318 | raise error.InMemoryMergeConflictsError( |
|
320 | 319 | b'in-memory merge does not support file conflicts' |
|
321 | 320 | ) |
|
322 | 321 | |
|
323 |
prompts = partextras( |
|
|
322 | prompts = partextras([local.label, other.label]) | |
|
324 | 323 | prompts[b'fd'] = uipathfn(fd) |
|
325 | 324 | try: |
|
326 |
if |
|
|
325 | if other.fctx.isabsent(): | |
|
327 | 326 | index = ui.promptchoice(_localchangedotherdeletedmsg % prompts, 2) |
|
328 | 327 | choice = [b'local', b'other', b'unresolved'][index] |
|
329 |
elif |
|
|
328 | elif local.fctx.isabsent(): | |
|
330 | 329 | index = ui.promptchoice(_otherchangedlocaldeletedmsg % prompts, 2) |
|
331 | 330 | choice = [b'other', b'local', b'unresolved'][index] |
|
332 | 331 | else: |
@@ -347,44 +346,48 b' def _iprompt(repo, mynode, orig, fcd, fc' | |||
|
347 | 346 | choice = [b'local', b'other', b'unresolved'][index] |
|
348 | 347 | |
|
349 | 348 | if choice == b'other': |
|
350 |
return _iother(repo, mynode, |
|
|
349 | return _iother(repo, mynode, local, other, base, toolconf) | |
|
351 | 350 | elif choice == b'local': |
|
352 |
return _ilocal(repo, mynode, |
|
|
351 | return _ilocal(repo, mynode, local, other, base, toolconf) | |
|
353 | 352 | elif choice == b'unresolved': |
|
354 |
return _ifail(repo, mynode, |
|
|
353 | return _ifail(repo, mynode, local, other, base, toolconf) | |
|
355 | 354 | except error.ResponseExpected: |
|
356 | 355 | ui.write(b"\n") |
|
357 |
return _ifail(repo, mynode, |
|
|
356 | return _ifail(repo, mynode, local, other, base, toolconf) | |
|
358 | 357 | |
|
359 | 358 | |
|
360 | 359 | @internaltool(b'local', nomerge) |
|
361 |
def _ilocal(repo, mynode, |
|
|
360 | def _ilocal(repo, mynode, local, other, base, toolconf): | |
|
362 | 361 | """Uses the local `p1()` version of files as the merged version.""" |
|
363 |
return 0, |
|
|
362 | return 0, local.fctx.isabsent() | |
|
364 | 363 | |
|
365 | 364 | |
|
366 | 365 | @internaltool(b'other', nomerge) |
|
367 |
def _iother(repo, mynode, |
|
|
366 | def _iother(repo, mynode, local, other, base, toolconf): | |
|
368 | 367 | """Uses the other `p2()` version of files as the merged version.""" |
|
369 |
if |
|
|
368 | if other.fctx.isabsent(): | |
|
370 | 369 | # local changed, remote deleted -- 'deleted' picked |
|
371 |
_underlyingfctxifabsent( |
|
|
370 | _underlyingfctxifabsent(local.fctx).remove() | |
|
372 | 371 | deleted = True |
|
373 | 372 | else: |
|
374 |
_underlyingfctxifabsent( |
|
|
373 | _underlyingfctxifabsent(local.fctx).write( | |
|
374 | other.fctx.data(), other.fctx.flags() | |
|
375 | ) | |
|
375 | 376 | deleted = False |
|
376 | 377 | return 0, deleted |
|
377 | 378 | |
|
378 | 379 | |
|
379 | 380 | @internaltool(b'fail', nomerge) |
|
380 |
def _ifail(repo, mynode, |
|
|
381 | def _ifail(repo, mynode, local, other, base, toolconf): | |
|
381 | 382 | """ |
|
382 | 383 | Rather than attempting to merge files that were modified on both |
|
383 | 384 | branches, it marks them as unresolved. The resolve command must be |
|
384 | 385 | used to resolve these conflicts.""" |
|
385 | 386 | # for change/delete conflicts write out the changed version, then fail |
|
386 |
if |
|
|
387 |
_underlyingfctxifabsent( |
|
|
387 | if local.fctx.isabsent(): | |
|
388 | _underlyingfctxifabsent(local.fctx).write( | |
|
389 | other.fctx.data(), other.fctx.flags() | |
|
390 | ) | |
|
388 | 391 | return 1, False |
|
389 | 392 | |
|
390 | 393 | |
@@ -399,11 +402,18 b' def _underlyingfctxifabsent(filectx):' | |||
|
399 | 402 | return filectx |
|
400 | 403 | |
|
401 | 404 | |
|
402 | def _premerge(repo, fcd, fco, fca, toolconf, files, labels=None): | |
|
405 | def _verifytext(input, ui): | |
|
406 | """verifies that text is non-binary""" | |
|
407 | if stringutil.binary(input.text()): | |
|
408 | msg = _(b"%s looks like a binary file.") % input.fctx.path() | |
|
409 | ui.warn(_(b'warning: %s\n') % msg) | |
|
410 | raise error.Abort(msg) | |
|
411 | ||
|
412 | ||
|
413 | def _premerge(repo, local, other, base, toolconf): | |
|
403 | 414 | tool, toolpath, binary, symlink, scriptfn = toolconf |
|
404 |
if symlink or |
|
|
415 | if symlink or local.fctx.isabsent() or other.fctx.isabsent(): | |
|
405 | 416 | return 1 |
|
406 | unused, unused, unused, back = files | |
|
407 | 417 | |
|
408 | 418 | ui = repo.ui |
|
409 | 419 | |
@@ -423,26 +433,28 b' def _premerge(repo, fcd, fco, fca, toolc' | |||
|
423 | 433 | |
|
424 | 434 | if premerge: |
|
425 | 435 | mode = b'merge' |
|
426 | if premerge in {b'keep-merge3', b'keep-mergediff'}: | |
|
427 | if not labels: | |
|
428 | labels = _defaultconflictlabels | |
|
429 | if len(labels) < 3: | |
|
430 | labels.append(b'base') | |
|
431 | 436 |
|
|
432 | 437 |
|
|
433 | r = simplemerge.simplemerge( | |
|
434 | ui, fcd, fca, fco, quiet=True, label=labels, mode=mode | |
|
438 | elif premerge == b'keep-merge3': | |
|
439 | mode = b'merge3' | |
|
440 | if any( | |
|
441 | stringutil.binary(input.text()) for input in (local, base, other) | |
|
442 | ): | |
|
443 | return 1 # continue merging | |
|
444 | merged_text, conflicts = simplemerge.simplemerge( | |
|
445 | local, base, other, mode=mode | |
|
435 | 446 | ) |
|
436 | if not r: | |
|
447 | if not conflicts or premerge in validkeep: | |
|
448 | # fcd.flags() already has the merged flags (done in | |
|
449 | # mergestate.resolve()) | |
|
450 | local.fctx.write(merged_text, local.fctx.flags()) | |
|
451 | if not conflicts: | |
|
437 | 452 | ui.debug(b" premerge successful\n") |
|
438 | 453 | return 0 |
|
439 | if premerge not in validkeep: | |
|
440 | # restore from backup and try again | |
|
441 | _restorebackup(fcd, back) | |
|
442 | 454 | return 1 # continue merging |
|
443 | 455 | |
|
444 | 456 | |
|
445 |
def _mergecheck(repo, mynode |
|
|
457 | def _mergecheck(repo, mynode, fcd, fco, fca, toolconf): | |
|
446 | 458 | tool, toolpath, binary, symlink, scriptfn = toolconf |
|
447 | 459 | uipathfn = scmutil.getuipathfn(repo) |
|
448 | 460 | if symlink: |
@@ -463,7 +475,7 b' def _mergecheck(repo, mynode, orig, fcd,' | |||
|
463 | 475 | return True |
|
464 | 476 | |
|
465 | 477 | |
|
466 | def _merge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, mode): | |
|
478 | def _merge(repo, local, other, base, mode): | |
|
467 | 479 | """ |
|
468 | 480 | Uses the internal non-interactive simple merge algorithm for merging |
|
469 | 481 | files. It will fail if there are any conflicts and leave markers in |
@@ -471,8 +483,20 b' def _merge(repo, mynode, orig, fcd, fco,' | |||
|
471 | 483 | of merge, unless mode equals 'union' which suppresses the markers.""" |
|
472 | 484 | ui = repo.ui |
|
473 | 485 | |
|
474 | r = simplemerge.simplemerge(ui, fcd, fca, fco, label=labels, mode=mode) | |
|
475 | return True, r, False | |
|
486 | try: | |
|
487 | _verifytext(local, ui) | |
|
488 | _verifytext(base, ui) | |
|
489 | _verifytext(other, ui) | |
|
490 | except error.Abort: | |
|
491 | return True, True, False | |
|
492 | else: | |
|
493 | merged_text, conflicts = simplemerge.simplemerge( | |
|
494 | local, base, other, mode=mode | |
|
495 | ) | |
|
496 | # fcd.flags() already has the merged flags (done in | |
|
497 | # mergestate.resolve()) | |
|
498 | local.fctx.write(merged_text, local.fctx.flags()) | |
|
499 | return True, conflicts, False | |
|
476 | 500 | |
|
477 | 501 | |
|
478 | 502 | @internaltool( |
@@ -484,14 +508,12 b' def _merge(repo, mynode, orig, fcd, fco,' | |||
|
484 | 508 | ), |
|
485 | 509 | precheck=_mergecheck, |
|
486 | 510 | ) |
|
487 |
def _iunion(repo, mynode, |
|
|
511 | def _iunion(repo, mynode, local, other, base, toolconf, backup): | |
|
488 | 512 | """ |
|
489 | 513 | Uses the internal non-interactive simple merge algorithm for merging |
|
490 | 514 | files. It will use both left and right sides for conflict regions. |
|
491 | 515 | No markers are inserted.""" |
|
492 | return _merge( | |
|
493 | repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, b'union' | |
|
494 | ) | |
|
516 | return _merge(repo, local, other, base, b'union') | |
|
495 | 517 | |
|
496 | 518 | |
|
497 | 519 | @internaltool( |
@@ -503,15 +525,13 b' def _iunion(repo, mynode, orig, fcd, fco' | |||
|
503 | 525 | ), |
|
504 | 526 | precheck=_mergecheck, |
|
505 | 527 | ) |
|
506 |
def _imerge(repo, mynode, |
|
|
528 | def _imerge(repo, mynode, local, other, base, toolconf, backup): | |
|
507 | 529 | """ |
|
508 | 530 | Uses the internal non-interactive simple merge algorithm for merging |
|
509 | 531 | files. It will fail if there are any conflicts and leave markers in |
|
510 | 532 | the partially merged file. Markers will have two sections, one for each side |
|
511 | 533 | of merge.""" |
|
512 | return _merge( | |
|
513 | repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, b'merge' | |
|
514 | ) | |
|
534 | return _merge(repo, local, other, base, b'merge') | |
|
515 | 535 | |
|
516 | 536 | |
|
517 | 537 | @internaltool( |
@@ -523,17 +543,13 b' def _imerge(repo, mynode, orig, fcd, fco' | |||
|
523 | 543 | ), |
|
524 | 544 | precheck=_mergecheck, |
|
525 | 545 | ) |
|
526 |
def _imerge3(repo, mynode, |
|
|
546 | def _imerge3(repo, mynode, local, other, base, toolconf, backup): | |
|
527 | 547 | """ |
|
528 | 548 | Uses the internal non-interactive simple merge algorithm for merging |
|
529 | 549 | files. It will fail if there are any conflicts and leave markers in |
|
530 | 550 | the partially merged file. Marker will have three sections, one from each |
|
531 | 551 | side of the merge and one for the base content.""" |
|
532 | if not labels: | |
|
533 | labels = _defaultconflictlabels | |
|
534 | if len(labels) < 3: | |
|
535 | labels.append(b'base') | |
|
536 | return _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels) | |
|
552 | return _merge(repo, local, other, base, b'merge3') | |
|
537 | 553 | |
|
538 | 554 | |
|
539 | 555 | @internaltool( |
@@ -564,62 +580,30 b' def _imerge3alwaysgood(*args, **kwargs):' | |||
|
564 | 580 | ), |
|
565 | 581 | precheck=_mergecheck, |
|
566 | 582 | ) |
|
567 | def _imerge_diff( | |
|
568 | repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None | |
|
569 | ): | |
|
583 | def _imerge_diff(repo, mynode, local, other, base, toolconf, backup): | |
|
570 | 584 | """ |
|
571 | 585 | Uses the internal non-interactive simple merge algorithm for merging |
|
572 | 586 | files. It will fail if there are any conflicts and leave markers in |
|
573 | 587 | the partially merged file. The marker will have two sections, one with the |
|
574 | 588 | content from one side of the merge, and one with a diff from the base |
|
575 | 589 | content to the content on the other side. (experimental)""" |
|
576 | if not labels: | |
|
577 | labels = _defaultconflictlabels | |
|
578 | if len(labels) < 3: | |
|
579 | labels.append(b'base') | |
|
580 | return _merge( | |
|
581 | repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, b'mergediff' | |
|
582 | ) | |
|
583 | ||
|
584 | ||
|
585 | def _imergeauto( | |
|
586 | repo, | |
|
587 | mynode, | |
|
588 | orig, | |
|
589 | fcd, | |
|
590 | fco, | |
|
591 | fca, | |
|
592 | toolconf, | |
|
593 | files, | |
|
594 | labels=None, | |
|
595 | localorother=None, | |
|
596 | ): | |
|
597 | """ | |
|
598 | Generic driver for _imergelocal and _imergeother | |
|
599 | """ | |
|
600 | assert localorother is not None | |
|
601 | r = simplemerge.simplemerge( | |
|
602 | repo.ui, fcd, fca, fco, label=labels, localorother=localorother | |
|
603 | ) | |
|
604 | return True, r | |
|
590 | return _merge(repo, local, other, base, b'mergediff') | |
|
605 | 591 | |
|
606 | 592 | |
|
607 | 593 | @internaltool(b'merge-local', mergeonly, precheck=_mergecheck) |
|
608 | def _imergelocal(*args, **kwargs): | |
|
594 | def _imergelocal(repo, mynode, local, other, base, toolconf, backup): | |
|
609 | 595 | """ |
|
610 | 596 | Like :merge, but resolve all conflicts non-interactively in favor |
|
611 | 597 | of the local `p1()` changes.""" |
|
612 | success, status = _imergeauto(localorother=b'local', *args, **kwargs) | |
|
613 | return success, status, False | |
|
598 | return _merge(repo, local, other, base, b'local') | |
|
614 | 599 | |
|
615 | 600 | |
|
616 | 601 | @internaltool(b'merge-other', mergeonly, precheck=_mergecheck) |
|
617 | def _imergeother(*args, **kwargs): | |
|
602 | def _imergeother(repo, mynode, local, other, base, toolconf, backup): | |
|
618 | 603 | """ |
|
619 | 604 | Like :merge, but resolve all conflicts non-interactively in favor |
|
620 | 605 | of the other `p2()` changes.""" |
|
621 | success, status = _imergeauto(localorother=b'other', *args, **kwargs) | |
|
622 | return success, status, False | |
|
606 | return _merge(repo, local, other, base, b'other') | |
|
623 | 607 | |
|
624 | 608 | |
|
625 | 609 | @internaltool( |
@@ -631,16 +615,16 b' def _imergeother(*args, **kwargs):' | |||
|
631 | 615 | b"tool of your choice)\n" |
|
632 | 616 | ), |
|
633 | 617 | ) |
|
634 |
def _itagmerge(repo, mynode, |
|
|
618 | def _itagmerge(repo, mynode, local, other, base, toolconf, backup): | |
|
635 | 619 | """ |
|
636 | 620 | Uses the internal tag merge algorithm (experimental). |
|
637 | 621 | """ |
|
638 |
success, status = tagmerge.merge(repo, |
|
|
622 | success, status = tagmerge.merge(repo, local.fctx, other.fctx, base.fctx) | |
|
639 | 623 | return success, status, False |
|
640 | 624 | |
|
641 | 625 | |
|
642 | 626 | @internaltool(b'dump', fullmerge, binary=True, symlink=True) |
|
643 |
def _idump(repo, mynode, |
|
|
627 | def _idump(repo, mynode, local, other, base, toolconf, backup): | |
|
644 | 628 | """ |
|
645 | 629 | Creates three versions of the files to merge, containing the |
|
646 | 630 | contents of local, other and base. These files can then be used to |
@@ -652,33 +636,31 b' def _idump(repo, mynode, orig, fcd, fco,' | |||
|
652 | 636 | This implies premerge. Therefore, files aren't dumped, if premerge |
|
653 | 637 | runs successfully. Use :forcedump to forcibly write files out. |
|
654 | 638 | """ |
|
655 |
a = _workingpath(repo, |
|
|
656 |
fd = |
|
|
639 | a = _workingpath(repo, local.fctx) | |
|
640 | fd = local.fctx.path() | |
|
657 | 641 | |
|
658 | 642 | from . import context |
|
659 | 643 | |
|
660 |
if isinstance( |
|
|
644 | if isinstance(local.fctx, context.overlayworkingfilectx): | |
|
661 | 645 | raise error.InMemoryMergeConflictsError( |
|
662 | 646 | b'in-memory merge does not support the :dump tool.' |
|
663 | 647 | ) |
|
664 | 648 | |
|
665 |
util.writefile(a + b".local", |
|
|
666 |
repo.wwrite(fd + b".other", |
|
|
667 |
repo.wwrite(fd + b".base", |
|
|
649 | util.writefile(a + b".local", local.fctx.decodeddata()) | |
|
650 | repo.wwrite(fd + b".other", other.fctx.data(), other.fctx.flags()) | |
|
651 | repo.wwrite(fd + b".base", base.fctx.data(), base.fctx.flags()) | |
|
668 | 652 | return False, 1, False |
|
669 | 653 | |
|
670 | 654 | |
|
671 | 655 | @internaltool(b'forcedump', mergeonly, binary=True, symlink=True) |
|
672 |
def _forcedump(repo, mynode, |
|
|
656 | def _forcedump(repo, mynode, local, other, base, toolconf, backup): | |
|
673 | 657 | """ |
|
674 | 658 | Creates three versions of the files as same as :dump, but omits premerge. |
|
675 | 659 | """ |
|
676 | return _idump( | |
|
677 | repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=labels | |
|
678 | ) | |
|
660 | return _idump(repo, mynode, local, other, base, toolconf, backup) | |
|
679 | 661 | |
|
680 | 662 | |
|
681 |
def _xmergeimm(repo, mynode, |
|
|
663 | def _xmergeimm(repo, mynode, local, other, base, toolconf, backup): | |
|
682 | 664 | # In-memory merge simply raises an exception on all external merge tools, |
|
683 | 665 | # for now. |
|
684 | 666 | # |
@@ -746,7 +728,10 b' def _describemerge(ui, repo, mynode, fcl' | |||
|
746 | 728 | ui.status(t.renderdefault(props)) |
|
747 | 729 | |
|
748 | 730 | |
|
749 |
def _xmerge(repo, mynode, |
|
|
731 | def _xmerge(repo, mynode, local, other, base, toolconf, backup): | |
|
732 | fcd = local.fctx | |
|
733 | fco = other.fctx | |
|
734 | fca = base.fctx | |
|
750 | 735 | tool, toolpath, binary, symlink, scriptfn = toolconf |
|
751 | 736 | uipathfn = scmutil.getuipathfn(repo) |
|
752 | 737 | if fcd.isabsent() or fco.isabsent(): |
@@ -755,20 +740,35 b' def _xmerge(repo, mynode, orig, fcd, fco' | |||
|
755 | 740 | % (tool, uipathfn(fcd.path())) |
|
756 | 741 | ) |
|
757 | 742 | return False, 1, None |
|
758 | unused, unused, unused, back = files | |
|
759 | 743 | localpath = _workingpath(repo, fcd) |
|
760 | 744 | args = _toolstr(repo.ui, tool, b"args") |
|
761 | 745 | |
|
762 | with _maketempfiles( | |
|
763 | repo, fco, fca, repo.wvfs.join(back.path()), b"$output" in args | |
|
764 | ) as temppaths: | |
|
765 | basepath, otherpath, localoutputpath = temppaths | |
|
746 | files = [ | |
|
747 | (b"base", fca.path(), fca.decodeddata()), | |
|
748 | (b"other", fco.path(), fco.decodeddata()), | |
|
749 | ] | |
|
766 | 750 |
|
|
767 | mylabel, otherlabel = labels[:2] | |
|
768 | if len(labels) >= 3: | |
|
769 | baselabel = labels[2] | |
|
751 | if b"$output" in args: | |
|
752 | # read input from backup, write to original | |
|
753 | outpath = localpath | |
|
754 | localoutputpath = backup.path() | |
|
755 | # Remove the .orig to make syntax-highlighting more likely. | |
|
756 | if localoutputpath.endswith(b'.orig'): | |
|
757 | localoutputpath, ext = os.path.splitext(localoutputpath) | |
|
758 | localdata = util.readfile(localpath) | |
|
759 | files.append((b"local", localoutputpath, localdata)) | |
|
760 | ||
|
761 | with _maketempfiles(files) as temppaths: | |
|
762 | basepath, otherpath = temppaths[:2] | |
|
763 | if len(temppaths) == 3: | |
|
764 | localpath = temppaths[2] | |
|
765 | ||
|
766 | def format_label(input): | |
|
767 | if input.label_detail: | |
|
768 | return b'%s: %s' % (input.label, input.label_detail) | |
|
770 | 769 | else: |
|
771 | baselabel = b'base' | |
|
770 | return input.label | |
|
771 | ||
|
772 | 772 | env = { |
|
773 | 773 | b'HG_FILE': fcd.path(), |
|
774 | 774 | b'HG_MY_NODE': short(mynode), |
@@ -777,24 +777,20 b' def _xmerge(repo, mynode, orig, fcd, fco' | |||
|
777 | 777 | b'HG_MY_ISLINK': b'l' in fcd.flags(), |
|
778 | 778 | b'HG_OTHER_ISLINK': b'l' in fco.flags(), |
|
779 | 779 | b'HG_BASE_ISLINK': b'l' in fca.flags(), |
|
780 |
b'HG_MY_LABEL': |
|
|
781 |
b'HG_OTHER_LABEL': o |
|
|
782 |
b'HG_BASE_LABEL': |
|
|
780 | b'HG_MY_LABEL': format_label(local), | |
|
781 | b'HG_OTHER_LABEL': format_label(other), | |
|
782 | b'HG_BASE_LABEL': format_label(base), | |
|
783 | 783 | } |
|
784 | 784 | ui = repo.ui |
|
785 | 785 | |
|
786 | if b"$output" in args: | |
|
787 | # read input from backup, write to original | |
|
788 | outpath = localpath | |
|
789 | localpath = localoutputpath | |
|
790 | 786 | replace = { |
|
791 | 787 | b'local': localpath, |
|
792 | 788 | b'base': basepath, |
|
793 | 789 | b'other': otherpath, |
|
794 | 790 | b'output': outpath, |
|
795 |
b'labellocal': |
|
|
796 |
b'labelother': o |
|
|
797 |
b'labelbase': |
|
|
791 | b'labellocal': format_label(local), | |
|
792 | b'labelother': format_label(other), | |
|
793 | b'labelbase': format_label(base), | |
|
798 | 794 | } |
|
799 | 795 | args = util.interpolate( |
|
800 | 796 | br'\$', |
@@ -846,40 +842,19 b' def _xmerge(repo, mynode, orig, fcd, fco' | |||
|
846 | 842 | return True, r, False |
|
847 | 843 | |
|
848 | 844 | |
|
849 | def _formatconflictmarker(ctx, template, label, pad): | |
|
850 |
"""Applies the given template to the ctx |
|
|
851 | ||
|
852 | Pad is the minimum width of the label prefix, so that multiple markers | |
|
853 | can have aligned templated parts. | |
|
854 | """ | |
|
845 | def _populate_label_detail(input, template): | |
|
846 | """Applies the given template to the ctx and stores it in the input.""" | |
|
847 | ctx = input.fctx.changectx() | |
|
855 | 848 | if ctx.node() is None: |
|
856 | 849 | ctx = ctx.p1() |
|
857 | 850 | |
|
858 | 851 | props = {b'ctx': ctx} |
|
859 | 852 | templateresult = template.renderdefault(props) |
|
860 | ||
|
861 | label = (b'%s:' % label).ljust(pad + 1) | |
|
862 | mark = b'%s %s' % (label, templateresult) | |
|
863 | ||
|
864 | if mark: | |
|
865 | mark = mark.splitlines()[0] # split for safety | |
|
866 | ||
|
867 | # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ') | |
|
868 | return stringutil.ellipsis(mark, 80 - 8) | |
|
853 | input.label_detail = templateresult.splitlines()[0] # split for safety | |
|
869 | 854 | |
|
870 | 855 | |
|
871 | _defaultconflictlabels = [b'local', b'other'] | |
|
872 | ||
|
873 | ||
|
874 | def _formatlabels(repo, fcd, fco, fca, labels, tool=None): | |
|
875 | """Formats the given labels using the conflict marker template. | |
|
876 | ||
|
877 | Returns a list of formatted labels. | |
|
878 | """ | |
|
879 | cd = fcd.changectx() | |
|
880 | co = fco.changectx() | |
|
881 | ca = fca.changectx() | |
|
882 | ||
|
856 | def _populate_label_details(repo, inputs, tool=None): | |
|
857 | """Populates the label details using the conflict marker template.""" | |
|
883 | 858 | ui = repo.ui |
|
884 | 859 | template = ui.config(b'command-templates', b'mergemarker') |
|
885 | 860 | if tool is not None: |
@@ -890,15 +865,8 b' def _formatlabels(repo, fcd, fco, fca, l' | |||
|
890 | 865 | ui, template, defaults=templatekw.keywords, resources=tres |
|
891 | 866 | ) |
|
892 | 867 | |
|
893 | pad = max(len(l) for l in labels) | |
|
894 | ||
|
895 | newlabels = [ | |
|
896 | _formatconflictmarker(cd, tmpl, labels[0], pad), | |
|
897 | _formatconflictmarker(co, tmpl, labels[1], pad), | |
|
898 | ] | |
|
899 | if len(labels) > 2: | |
|
900 | newlabels.append(_formatconflictmarker(ca, tmpl, labels[2], pad)) | |
|
901 | return newlabels | |
|
868 | for input in inputs: | |
|
869 | _populate_label_detail(input, tmpl) | |
|
902 | 870 | |
|
903 | 871 | |
|
904 | 872 | def partextras(labels): |
@@ -918,13 +886,7 b' def partextras(labels):' | |||
|
918 | 886 | } |
|
919 | 887 | |
|
920 | 888 | |
|
921 | def _restorebackup(fcd, back): | |
|
922 | # TODO: Add a workingfilectx.write(otherfilectx) path so we can use | |
|
923 | # util.copy here instead. | |
|
924 | fcd.write(back.data(), fcd.flags()) | |
|
925 | ||
|
926 | ||
|
927 | def _makebackup(repo, ui, wctx, fcd, premerge): | |
|
889 | def _makebackup(repo, ui, fcd): | |
|
928 | 890 | """Makes and returns a filectx-like object for ``fcd``'s backup file. |
|
929 | 891 | |
|
930 | 892 | In addition to preserving the user's pre-existing modifications to `fcd` |
@@ -932,8 +894,8 b' def _makebackup(repo, ui, wctx, fcd, pre' | |||
|
932 | 894 | merge changed anything, and determine what line endings the new file should |
|
933 | 895 | have. |
|
934 | 896 | |
|
935 |
Backups only need to be written once |
|
|
936 | content doesn't change afterwards. | |
|
897 | Backups only need to be written once since their content doesn't change | |
|
898 | afterwards. | |
|
937 | 899 | """ |
|
938 | 900 | if fcd.isabsent(): |
|
939 | 901 | return None |
@@ -941,96 +903,47 b' def _makebackup(repo, ui, wctx, fcd, pre' | |||
|
941 | 903 | # merge -> filemerge). (I suspect the fileset import is the weakest link) |
|
942 | 904 | from . import context |
|
943 | 905 | |
|
944 | back = scmutil.backuppath(ui, repo, fcd.path()) | |
|
945 | inworkingdir = back.startswith(repo.wvfs.base) and not back.startswith( | |
|
946 | repo.vfs.base | |
|
947 | ) | |
|
948 | if isinstance(fcd, context.overlayworkingfilectx) and inworkingdir: | |
|
949 | # If the backup file is to be in the working directory, and we're | |
|
950 | # merging in-memory, we must redirect the backup to the memory context | |
|
951 | # so we don't disturb the working directory. | |
|
952 | relpath = back[len(repo.wvfs.base) + 1 :] | |
|
953 | if premerge: | |
|
954 | wctx[relpath].write(fcd.data(), fcd.flags()) | |
|
955 | return wctx[relpath] | |
|
906 | if isinstance(fcd, context.overlayworkingfilectx): | |
|
907 | # If we're merging in-memory, we're free to put the backup anywhere. | |
|
908 | fd, backup = pycompat.mkstemp(b'hg-merge-backup') | |
|
909 | with os.fdopen(fd, 'wb') as f: | |
|
910 | f.write(fcd.data()) | |
|
956 | 911 | else: |
|
957 | if premerge: | |
|
958 | # Otherwise, write to wherever path the user specified the backups | |
|
959 | # should go. We still need to switch based on whether the source is | |
|
960 | # in-memory so we can use the fast path of ``util.copy`` if both are | |
|
961 | # on disk. | |
|
962 | if isinstance(fcd, context.overlayworkingfilectx): | |
|
963 | util.writefile(back, fcd.data()) | |
|
964 | else: | |
|
912 | backup = scmutil.backuppath(ui, repo, fcd.path()) | |
|
965 | 913 |
|
|
966 |
|
|
|
967 | # A arbitraryfilectx is returned, so we can run the same functions on | |
|
968 | # the backup context regardless of where it lives. | |
|
969 | return context.arbitraryfilectx(back, repo=repo) | |
|
914 | util.copyfile(a, backup) | |
|
915 | ||
|
916 | return context.arbitraryfilectx(backup, repo=repo) | |
|
970 | 917 | |
|
971 | 918 | |
|
972 | 919 | @contextlib.contextmanager |
|
973 | def _maketempfiles(repo, fco, fca, localpath, uselocalpath): | |
|
974 | """Writes out `fco` and `fca` as temporary files, and (if uselocalpath) | |
|
975 | copies `localpath` to another temporary file, so an external merge tool may | |
|
976 | use them. | |
|
920 | def _maketempfiles(files): | |
|
921 | """Creates a temporary file for each (prefix, path, data) tuple in `files`, | |
|
922 | so an external merge tool may use them. | |
|
977 | 923 | """ |
|
978 | tmproot = None | |
|
979 | tmprootprefix = repo.ui.config(b'experimental', b'mergetempdirprefix') | |
|
980 | if tmprootprefix: | |
|
981 | tmproot = pycompat.mkdtemp(prefix=tmprootprefix) | |
|
924 | tmproot = pycompat.mkdtemp(prefix=b'hgmerge-') | |
|
982 | 925 | |
|
983 | def maketempfrompath(prefix, path): | |
|
926 | def maketempfrompath(prefix, path, data): | |
|
984 | 927 | fullbase, ext = os.path.splitext(path) |
|
985 | 928 | pre = b"%s~%s" % (os.path.basename(fullbase), prefix) |
|
986 | if tmproot: | |
|
987 | 929 |
|
|
988 | 930 |
|
|
989 | 931 |
|
|
990 | f = open(name, "wb") | |
|
991 | else: | |
|
992 | fd, name = pycompat.mkstemp(prefix=pre + b'.', suffix=ext) | |
|
993 | f = os.fdopen(fd, "wb") | |
|
994 | return f, name | |
|
995 | ||
|
996 | def tempfromcontext(prefix, ctx): | |
|
997 | f, name = maketempfrompath(prefix, ctx.path()) | |
|
998 | data = repo.wwritedata(ctx.path(), ctx.data()) | |
|
999 | f.write(data) | |
|
1000 | f.close() | |
|
932 | util.writefile(name, data) | |
|
1001 | 933 | return name |
|
1002 | 934 | |
|
1003 | b = tempfromcontext(b"base", fca) | |
|
1004 | c = tempfromcontext(b"other", fco) | |
|
1005 | d = localpath | |
|
1006 | if uselocalpath: | |
|
1007 | # We start off with this being the backup filename, so remove the .orig | |
|
1008 | # to make syntax-highlighting more likely. | |
|
1009 | if d.endswith(b'.orig'): | |
|
1010 | d, _ = os.path.splitext(d) | |
|
1011 | f, d = maketempfrompath(b"local", d) | |
|
1012 | with open(localpath, b'rb') as src: | |
|
1013 | f.write(src.read()) | |
|
1014 | f.close() | |
|
1015 | ||
|
935 | temp_files = [] | |
|
936 | for prefix, path, data in files: | |
|
937 | temp_files.append(maketempfrompath(prefix, path, data)) | |
|
1016 | 938 | try: |
|
1017 |
yield |
|
|
939 | yield temp_files | |
|
1018 | 940 | finally: |
|
1019 | if tmproot: | |
|
1020 | 941 |
|
|
1021 | else: | |
|
1022 | util.unlink(b) | |
|
1023 | util.unlink(c) | |
|
1024 | # if not uselocalpath, d is the 'orig'/backup file which we | |
|
1025 | # shouldn't delete. | |
|
1026 | if d and uselocalpath: | |
|
1027 | util.unlink(d) | |
|
1028 | 942 | |
|
1029 | 943 | |
|
1030 |
def |
|
|
944 | def filemerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None): | |
|
1031 | 945 | """perform a 3-way merge in the working directory |
|
1032 | 946 | |
|
1033 | premerge = whether this is a premerge | |
|
1034 | 947 | mynode = parent node before merge |
|
1035 | 948 | orig = original local filename before merge |
|
1036 | 949 | fco = other file context |
@@ -1039,10 +952,6 b' def _filemerge(premerge, repo, wctx, myn' | |||
|
1039 | 952 | |
|
1040 | 953 | Returns whether the merge is complete, the return value of the merge, and |
|
1041 | 954 | a boolean indicating whether the file was deleted from disk.""" |
|
1042 | ||
|
1043 | if not fco.cmp(fcd): # files identical? | |
|
1044 | return True, None, False | |
|
1045 | ||
|
1046 | 955 | ui = repo.ui |
|
1047 | 956 | fd = fcd.path() |
|
1048 | 957 | uipathfn = scmutil.getuipathfn(repo) |
@@ -1098,11 +1007,23 b' def _filemerge(premerge, repo, wctx, myn' | |||
|
1098 | 1007 | |
|
1099 | 1008 | toolconf = tool, toolpath, binary, symlink, scriptfn |
|
1100 | 1009 | |
|
1010 | if not labels: | |
|
1011 | labels = [b'local', b'other'] | |
|
1012 | if len(labels) < 3: | |
|
1013 | labels.append(b'base') | |
|
1014 | local = simplemerge.MergeInput(fcd, labels[0]) | |
|
1015 | other = simplemerge.MergeInput(fco, labels[1]) | |
|
1016 | base = simplemerge.MergeInput(fca, labels[2]) | |
|
1101 | 1017 | if mergetype == nomerge: |
|
1102 | r, deleted = func(repo, mynode, orig, fcd, fco, fca, toolconf, labels) | |
|
1103 | return True, r, deleted | |
|
1018 | return func( | |
|
1019 | repo, | |
|
1020 | mynode, | |
|
1021 | local, | |
|
1022 | other, | |
|
1023 | base, | |
|
1024 | toolconf, | |
|
1025 | ) | |
|
1104 | 1026 | |
|
1105 | if premerge: | |
|
1106 | 1027 |
|
|
1107 | 1028 |
|
|
1108 | 1029 |
|
@@ -1113,17 +1034,16 b' def _filemerge(premerge, repo, wctx, myn' | |||
|
1113 | 1034 | |
|
1114 | 1035 | ui.debug(b"my %s other %s ancestor %s\n" % (fcd, fco, fca)) |
|
1115 | 1036 | |
|
1116 |
if precheck and not precheck(repo, mynode, |
|
|
1037 | if precheck and not precheck(repo, mynode, fcd, fco, fca, toolconf): | |
|
1117 | 1038 | if onfailure: |
|
1118 | 1039 | if wctx.isinmemory(): |
|
1119 | 1040 | raise error.InMemoryMergeConflictsError( |
|
1120 | 1041 | b'in-memory merge does not support merge conflicts' |
|
1121 | 1042 | ) |
|
1122 | 1043 | ui.warn(onfailure % fduipath) |
|
1123 |
return |
|
|
1044 | return 1, False | |
|
1124 | 1045 | |
|
1125 |
back = _makebackup(repo, ui, |
|
|
1126 | files = (None, None, None, back) | |
|
1046 | backup = _makebackup(repo, ui, fcd) | |
|
1127 | 1047 | r = 1 |
|
1128 | 1048 | try: |
|
1129 | 1049 | internalmarkerstyle = ui.config(b'ui', b'mergemarkers') |
@@ -1132,51 +1052,53 b' def _filemerge(premerge, repo, wctx, myn' | |||
|
1132 | 1052 | else: |
|
1133 | 1053 | markerstyle = internalmarkerstyle |
|
1134 | 1054 | |
|
1135 | if not labels: | |
|
1136 | labels = _defaultconflictlabels | |
|
1137 | formattedlabels = labels | |
|
1138 | if markerstyle != b'basic': | |
|
1139 | formattedlabels = _formatlabels( | |
|
1140 | repo, fcd, fco, fca, labels, tool=tool | |
|
1141 | ) | |
|
1142 | ||
|
1143 | if premerge and mergetype == fullmerge: | |
|
1055 | if mergetype == fullmerge: | |
|
1144 | 1056 | # conflict markers generated by premerge will use 'detailed' |
|
1145 | 1057 | # settings if either ui.mergemarkers or the tool's mergemarkers |
|
1146 | 1058 | # setting is 'detailed'. This way tools can have basic labels in |
|
1147 | 1059 | # space-constrained areas of the UI, but still get full information |
|
1148 | 1060 | # in conflict markers if premerge is 'keep' or 'keep-merge3'. |
|
1149 | premergelabels = labels | |
|
1150 | 1061 | labeltool = None |
|
1151 | 1062 | if markerstyle != b'basic': |
|
1152 | 1063 | # respect 'tool's mergemarkertemplate (which defaults to |
|
1153 | 1064 | # command-templates.mergemarker) |
|
1154 | 1065 | labeltool = tool |
|
1155 | 1066 | if internalmarkerstyle != b'basic' or markerstyle != b'basic': |
|
1156 |
|
|
|
1157 |
repo, |
|
|
1067 | _populate_label_details( | |
|
1068 | repo, [local, other, base], tool=labeltool | |
|
1158 | 1069 | ) |
|
1159 | 1070 | |
|
1160 | 1071 | r = _premerge( |
|
1161 | repo, fcd, fco, fca, toolconf, files, labels=premergelabels | |
|
1072 | repo, | |
|
1073 | local, | |
|
1074 | other, | |
|
1075 | base, | |
|
1076 | toolconf, | |
|
1162 | 1077 | ) |
|
1163 |
# |
|
|
1164 |
|
|
|
1078 | # we're done if premerge was successful (r is 0) | |
|
1079 | if not r: | |
|
1080 | return r, False | |
|
1081 | ||
|
1082 | # Reset to basic labels | |
|
1083 | local.label_detail = None | |
|
1084 | other.label_detail = None | |
|
1085 | base.label_detail = None | |
|
1086 | ||
|
1087 | if markerstyle != b'basic': | |
|
1088 | _populate_label_details(repo, [local, other, base], tool=tool) | |
|
1165 | 1089 | |
|
1166 | 1090 | needcheck, r, deleted = func( |
|
1167 | 1091 | repo, |
|
1168 | 1092 | mynode, |
|
1169 |
|
|
|
1170 |
|
|
|
1171 |
|
|
|
1172 | fca, | |
|
1093 | local, | |
|
1094 | other, | |
|
1095 | base, | |
|
1173 | 1096 | toolconf, |
|
1174 |
|
|
|
1175 | labels=formattedlabels, | |
|
1097 | backup, | |
|
1176 | 1098 | ) |
|
1177 | 1099 | |
|
1178 | 1100 | if needcheck: |
|
1179 |
r = _check(repo, r, ui, tool, fcd, |
|
|
1101 | r = _check(repo, r, ui, tool, fcd, backup) | |
|
1180 | 1102 | |
|
1181 | 1103 | if r: |
|
1182 | 1104 | if onfailure: |
@@ -1189,10 +1111,10 b' def _filemerge(premerge, repo, wctx, myn' | |||
|
1189 | 1111 | ui.warn(onfailure % fduipath) |
|
1190 | 1112 | _onfilemergefailure(ui) |
|
1191 | 1113 | |
|
1192 |
return |
|
|
1114 | return r, deleted | |
|
1193 | 1115 | finally: |
|
1194 | if not r and back is not None: | |
|
1195 | back.remove() | |
|
1116 | if not r and backup is not None: | |
|
1117 | backup.remove() | |
|
1196 | 1118 | |
|
1197 | 1119 | |
|
1198 | 1120 | def _haltmerge(): |
@@ -1225,10 +1147,9 b' def hasconflictmarkers(data):' | |||
|
1225 | 1147 | ) |
|
1226 | 1148 | |
|
1227 | 1149 | |
|
1228 |
def _check(repo, r, ui, tool, fcd, |
|
|
1150 | def _check(repo, r, ui, tool, fcd, backup): | |
|
1229 | 1151 | fd = fcd.path() |
|
1230 | 1152 | uipathfn = scmutil.getuipathfn(repo) |
|
1231 | unused, unused, unused, back = files | |
|
1232 | 1153 | |
|
1233 | 1154 | if not r and ( |
|
1234 | 1155 | _toolbool(ui, tool, b"checkconflicts") |
@@ -1255,7 +1176,7 b' def _check(repo, r, ui, tool, fcd, files' | |||
|
1255 | 1176 | or b'changed' in _toollist(ui, tool, b"check") |
|
1256 | 1177 | ) |
|
1257 | 1178 | ): |
|
1258 | if back is not None and not fcd.cmp(back): | |
|
1179 | if backup is not None and not fcd.cmp(backup): | |
|
1259 | 1180 | if ui.promptchoice( |
|
1260 | 1181 | _( |
|
1261 | 1182 | b" output file %s appears unchanged\n" |
@@ -1267,8 +1188,8 b' def _check(repo, r, ui, tool, fcd, files' | |||
|
1267 | 1188 | ): |
|
1268 | 1189 | r = 1 |
|
1269 | 1190 | |
|
1270 | if back is not None and _toolbool(ui, tool, b"fixeol"): | |
|
1271 | _matcheol(_workingpath(repo, fcd), back) | |
|
1191 | if backup is not None and _toolbool(ui, tool, b"fixeol"): | |
|
1192 | _matcheol(_workingpath(repo, fcd), backup) | |
|
1272 | 1193 | |
|
1273 | 1194 | return r |
|
1274 | 1195 | |
@@ -1277,18 +1198,6 b' def _workingpath(repo, ctx):' | |||
|
1277 | 1198 | return repo.wjoin(ctx.path()) |
|
1278 | 1199 | |
|
1279 | 1200 | |
|
1280 | def premerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None): | |
|
1281 | return _filemerge( | |
|
1282 | True, repo, wctx, mynode, orig, fcd, fco, fca, labels=labels | |
|
1283 | ) | |
|
1284 | ||
|
1285 | ||
|
1286 | def filemerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None): | |
|
1287 | return _filemerge( | |
|
1288 | False, repo, wctx, mynode, orig, fcd, fco, fca, labels=labels | |
|
1289 | ) | |
|
1290 | ||
|
1291 | ||
|
1292 | 1201 | def loadinternalmerge(ui, extname, registrarobj): |
|
1293 | 1202 | """Load internal merge tool from specified registrarobj""" |
|
1294 | 1203 | for name, func in pycompat.iteritems(registrarobj._table): |
@@ -513,13 +513,18 b' effect and style see :hg:`help color`.' | |||
|
513 | 513 | ``update.check`` |
|
514 | 514 | Determines what level of checking :hg:`update` will perform before moving |
|
515 | 515 | to a destination revision. Valid values are ``abort``, ``none``, |
|
516 |
``linear``, and ``noconflict``. |
|
|
517 | directory has uncommitted changes. ``none`` performs no checking, and may | |
|
518 | result in a merge with uncommitted changes. ``linear`` allows any update | |
|
519 | as long as it follows a straight line in the revision history, and may | |
|
520 | trigger a merge with uncommitted changes. ``noconflict`` will allow any | |
|
521 | update which would not trigger a merge with uncommitted changes, if any | |
|
522 | are present. | |
|
516 | ``linear``, and ``noconflict``. | |
|
517 | ||
|
518 | - ``abort`` always fails if the working directory has uncommitted changes. | |
|
519 | ||
|
520 | - ``none`` performs no checking, and may result in a merge with uncommitted changes. | |
|
521 | ||
|
522 | - ``linear`` allows any update as long as it follows a straight line in the | |
|
523 | revision history, and may trigger a merge with uncommitted changes. | |
|
524 | ||
|
525 | - ``noconflict`` will allow any update which would not trigger a merge with | |
|
526 | uncommitted changes, if any are present. | |
|
527 | ||
|
523 | 528 | (default: ``linear``) |
|
524 | 529 | |
|
525 | 530 | ``update.requiredest`` |
@@ -850,6 +855,24 b' Example for ``~/.hgrc``::' | |||
|
850 | 855 | # (this extension will get loaded from the file specified) |
|
851 | 856 | myfeature = ~/.hgext/myfeature.py |
|
852 | 857 | |
|
858 | If an extension fails to load, a warning will be issued, and Mercurial will | |
|
859 | proceed. To enforce that an extension must be loaded, one can set the `required` | |
|
860 | suboption in the config:: | |
|
861 | ||
|
862 | [extensions] | |
|
863 | myfeature = ~/.hgext/myfeature.py | |
|
864 | myfeature:required = yes | |
|
865 | ||
|
866 | To debug extension loading issue, one can add `--traceback` to their mercurial | |
|
867 | invocation. | |
|
868 | ||
|
869 | A default setting can we set using the special `*` extension key:: | |
|
870 | ||
|
871 | [extensions] | |
|
872 | *:required = yes | |
|
873 | myfeature = ~/.hgext/myfeature.py | |
|
874 | rebase= | |
|
875 | ||
|
853 | 876 | |
|
854 | 877 | ``format`` |
|
855 | 878 | ---------- |
@@ -921,6 +944,38 b' https://www.mercurial-scm.org/wiki/Missi' | |||
|
921 | 944 | |
|
922 | 945 | For a more comprehensive guide, see :hg:`help internals.dirstate-v2`. |
|
923 | 946 | |
|
947 | ``use-dirstate-tracked-hint`` | |
|
948 | Enable or disable the writing of "tracked key" file alongside the dirstate. | |
|
949 | (default to disabled) | |
|
950 | ||
|
951 | That "tracked-hint" can help external automations to detect changes to the | |
|
952 | set of tracked files. (i.e the result of `hg files` or `hg status -macd`) | |
|
953 | ||
|
954 | The tracked-hint is written in a new `.hg/dirstate-tracked-hint`. That file | |
|
955 | contains two lines: | |
|
956 | - the first line is the file version (currently: 1), | |
|
957 | - the second line contains the "tracked-hint". | |
|
958 | That file is written right after the dirstate is written. | |
|
959 | ||
|
960 | The tracked-hint changes whenever the set of file tracked in the dirstate | |
|
961 | changes. The general idea is: | |
|
962 | - if the hint is identical, the set of tracked file SHOULD be identical, | |
|
963 | - if the hint is different, the set of tracked file MIGHT be different. | |
|
964 | ||
|
965 | The "hint is identical" case uses `SHOULD` as the dirstate and the hint file | |
|
966 | are two distinct files and therefore that cannot be read or written to in an | |
|
967 | atomic way. If the key is identical, nothing garantees that the dirstate is | |
|
968 | not updated right after the hint file. This is considered a negligible | |
|
969 | limitation for the intended usecase. It is actually possible to prevent this | |
|
970 | race by taking the repository lock during read operations. | |
|
971 | ||
|
972 | They are two "ways" to use this feature: | |
|
973 | ||
|
974 | 1) monitoring changes to the `.hg/dirstate-tracked-hint`, if the file | |
|
975 | changes, the tracked set might have changed. | |
|
976 | ||
|
977 | 2) storing the value and comparing it to a later value. | |
|
978 | ||
|
924 | 979 | ``use-persistent-nodemap`` |
|
925 | 980 | Enable or disable the "persistent-nodemap" feature which improves |
|
926 | 981 | performance if the Rust extensions are available. |
@@ -975,7 +1030,7 b' https://www.mercurial-scm.org/wiki/Missi' | |||
|
975 | 1030 | |
|
976 | 1031 | Introduced in Mercurial 5.7. |
|
977 | 1032 | |
|
978 |
|
|
|
1033 | Enabled by default in Mercurial 6.1. | |
|
979 | 1034 | |
|
980 | 1035 | ``usestore`` |
|
981 | 1036 | Enable or disable the "store" repository format which improves |
@@ -332,95 +332,6 b' part of the response payload and not par' | |||
|
332 | 332 | after responses. In other words, the length of the response contains the |
|
333 | 333 | trailing ``\n``. |
|
334 | 334 | |
|
335 | Clients supporting version 2 of the SSH transport send a line beginning | |
|
336 | with ``upgrade`` before the ``hello`` and ``between`` commands. The line | |
|
337 | (which isn't a well-formed command line because it doesn't consist of a | |
|
338 | single command name) serves to both communicate the client's intent to | |
|
339 | switch to transport version 2 (transports are version 1 by default) as | |
|
340 | well as to advertise the client's transport-level capabilities so the | |
|
341 | server may satisfy that request immediately. | |
|
342 | ||
|
343 | The upgrade line has the form: | |
|
344 | ||
|
345 | upgrade <token> <transport capabilities> | |
|
346 | ||
|
347 | That is the literal string ``upgrade`` followed by a space, followed by | |
|
348 | a randomly generated string, followed by a space, followed by a string | |
|
349 | denoting the client's transport capabilities. | |
|
350 | ||
|
351 | The token can be anything. However, a random UUID is recommended. (Use | |
|
352 | of version 4 UUIDs is recommended because version 1 UUIDs can leak the | |
|
353 | client's MAC address.) | |
|
354 | ||
|
355 | The transport capabilities string is a URL/percent encoded string | |
|
356 | containing key-value pairs defining the client's transport-level | |
|
357 | capabilities. The following capabilities are defined: | |
|
358 | ||
|
359 | proto | |
|
360 | A comma-delimited list of transport protocol versions the client | |
|
361 | supports. e.g. ``ssh-v2``. | |
|
362 | ||
|
363 | If the server does not recognize the ``upgrade`` line, it should issue | |
|
364 | an empty response and continue processing the ``hello`` and ``between`` | |
|
365 | commands. Here is an example handshake between a version 2 aware client | |
|
366 | and a non version 2 aware server: | |
|
367 | ||
|
368 | c: upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=ssh-v2 | |
|
369 | c: hello\n | |
|
370 | c: between\n | |
|
371 | c: pairs 81\n | |
|
372 | c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 | |
|
373 | s: 0\n | |
|
374 | s: 324\n | |
|
375 | s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n | |
|
376 | s: 1\n | |
|
377 | s: \n | |
|
378 | ||
|
379 | (The initial ``0\n`` line from the server indicates an empty response to | |
|
380 | the unknown ``upgrade ..`` command/line.) | |
|
381 | ||
|
382 | If the server recognizes the ``upgrade`` line and is willing to satisfy that | |
|
383 | upgrade request, it replies to with a payload of the following form: | |
|
384 | ||
|
385 | upgraded <token> <transport name>\n | |
|
386 | ||
|
387 | This line is the literal string ``upgraded``, a space, the token that was | |
|
388 | specified by the client in its ``upgrade ...`` request line, a space, and the | |
|
389 | name of the transport protocol that was chosen by the server. The transport | |
|
390 | name MUST match one of the names the client specified in the ``proto`` field | |
|
391 | of its ``upgrade ...`` request line. | |
|
392 | ||
|
393 | If a server issues an ``upgraded`` response, it MUST also read and ignore | |
|
394 | the lines associated with the ``hello`` and ``between`` command requests | |
|
395 | that were issued by the server. It is assumed that the negotiated transport | |
|
396 | will respond with equivalent requested information following the transport | |
|
397 | handshake. | |
|
398 | ||
|
399 | All data following the ``\n`` terminating the ``upgraded`` line is the | |
|
400 | domain of the negotiated transport. It is common for the data immediately | |
|
401 | following to contain additional metadata about the state of the transport and | |
|
402 | the server. However, this isn't strictly speaking part of the transport | |
|
403 | handshake and isn't covered by this section. | |
|
404 | ||
|
405 | Here is an example handshake between a version 2 aware client and a version | |
|
406 | 2 aware server: | |
|
407 | ||
|
408 | c: upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=ssh-v2 | |
|
409 | c: hello\n | |
|
410 | c: between\n | |
|
411 | c: pairs 81\n | |
|
412 | c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 | |
|
413 | s: upgraded 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a ssh-v2\n | |
|
414 | s: <additional transport specific data> | |
|
415 | ||
|
416 | The client-issued token that is echoed in the response provides a more | |
|
417 | resilient mechanism for differentiating *banner* output from Mercurial | |
|
418 | output. In version 1, properly formatted banner output could get confused | |
|
419 | for Mercurial server output. By submitting a randomly generated token | |
|
420 | that is then present in the response, the client can look for that token | |
|
421 | in response lines and have reasonable certainty that the line did not | |
|
422 | originate from a *banner* message. | |
|
423 | ||
|
424 | 335 | SSH Version 1 Transport |
|
425 | 336 | ----------------------- |
|
426 | 337 | |
@@ -488,31 +399,6 b' If the server announces support for the ' | |||
|
488 | 399 | should issue a ``protocaps`` command after the initial handshake to annonunce |
|
489 | 400 | its own capabilities. The client capabilities are persistent. |
|
490 | 401 | |
|
491 | SSH Version 2 Transport | |
|
492 | ----------------------- | |
|
493 | ||
|
494 | **Experimental and under development** | |
|
495 | ||
|
496 | Version 2 of the SSH transport behaves identically to version 1 of the SSH | |
|
497 | transport with the exception of handshake semantics. See above for how | |
|
498 | version 2 of the SSH transport is negotiated. | |
|
499 | ||
|
500 | Immediately following the ``upgraded`` line signaling a switch to version | |
|
501 | 2 of the SSH protocol, the server automatically sends additional details | |
|
502 | about the capabilities of the remote server. This has the form: | |
|
503 | ||
|
504 | <integer length of value>\n | |
|
505 | capabilities: ...\n | |
|
506 | ||
|
507 | e.g. | |
|
508 | ||
|
509 | s: upgraded 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a ssh-v2\n | |
|
510 | s: 240\n | |
|
511 | s: capabilities: known getbundle batch ...\n | |
|
512 | ||
|
513 | Following capabilities advertisement, the peers communicate using version | |
|
514 | 1 of the SSH transport. | |
|
515 | ||
|
516 | 402 | Capabilities |
|
517 | 403 | ============ |
|
518 | 404 |
@@ -1,8 +1,10 b'' | |||
|
1 | 1 | Mercurial accepts several notations for identifying one or more files |
|
2 | 2 | at a time. |
|
3 | 3 | |
|
4 |
By default, Mercurial treats filenames |
|
|
5 | patterns. | |
|
4 | By default, Mercurial treats filenames verbatim without pattern | |
|
5 | matching, relative to the current working directory. Note that your | |
|
6 | system shell might perform pattern matching of its own before passing | |
|
7 | filenames into Mercurial. | |
|
6 | 8 | |
|
7 | 9 | Alternate pattern notations must be specified explicitly. |
|
8 | 10 |
@@ -132,13 +132,6 b' def addbranchrevs(lrepo, other, branches' | |||
|
132 | 132 | return revs, revs[0] |
|
133 | 133 | |
|
134 | 134 | |
|
135 | def parseurl(path, branches=None): | |
|
136 | '''parse url#branch, returning (url, (branch, branches))''' | |
|
137 | msg = b'parseurl(...) moved to mercurial.utils.urlutil' | |
|
138 | util.nouideprecwarn(msg, b'6.0', stacklevel=2) | |
|
139 | return urlutil.parseurl(path, branches=branches) | |
|
140 | ||
|
141 | ||
|
142 | 135 | schemes = { |
|
143 | 136 | b'bundle': bundlerepo, |
|
144 | 137 | b'union': unionrepo, |
@@ -366,17 +366,6 b' class hgweb(object):' | |||
|
366 | 366 | # replace it. |
|
367 | 367 | res.headers[b'Content-Security-Policy'] = rctx.csp |
|
368 | 368 | |
|
369 | # /api/* is reserved for various API implementations. Dispatch | |
|
370 | # accordingly. But URL paths can conflict with subrepos and virtual | |
|
371 | # repos in hgwebdir. So until we have a workaround for this, only | |
|
372 | # expose the URLs if the feature is enabled. | |
|
373 | apienabled = rctx.repo.ui.configbool(b'experimental', b'web.apiserver') | |
|
374 | if apienabled and req.dispatchparts and req.dispatchparts[0] == b'api': | |
|
375 | wireprotoserver.handlewsgiapirequest( | |
|
376 | rctx, req, res, self.check_perm | |
|
377 | ) | |
|
378 | return res.sendresponse() | |
|
379 | ||
|
380 | 369 | handled = wireprotoserver.handlewsgirequest( |
|
381 | 370 | rctx, req, res, self.check_perm |
|
382 | 371 | ) |
@@ -519,6 +519,7 b" rev = webcommand(b'rev')(changeset)" | |||
|
519 | 519 | |
|
520 | 520 | |
|
521 | 521 | def decodepath(path): |
|
522 | # type: (bytes) -> bytes | |
|
522 | 523 | """Hook for mapping a path in the repository to a path in the |
|
523 | 524 | working copy. |
|
524 | 525 | |
@@ -616,7 +617,9 b' def manifest(web):' | |||
|
616 | 617 | yield { |
|
617 | 618 | b"parity": next(parity), |
|
618 | 619 | b"path": path, |
|
620 | # pytype: disable=wrong-arg-types | |
|
619 | 621 | b"emptydirs": b"/".join(emptydirs), |
|
622 | # pytype: enable=wrong-arg-types | |
|
620 | 623 | b"basename": d, |
|
621 | 624 | } |
|
622 | 625 |
@@ -13,7 +13,6 b' import io' | |||
|
13 | 13 | import os |
|
14 | 14 | import socket |
|
15 | 15 | import struct |
|
16 | import weakref | |
|
17 | 16 | |
|
18 | 17 | from .i18n import _ |
|
19 | 18 | from .pycompat import getattr |
@@ -25,21 +24,9 b' from . import (' | |||
|
25 | 24 | statichttprepo, |
|
26 | 25 | url as urlmod, |
|
27 | 26 | util, |
|
28 | wireprotoframing, | |
|
29 | wireprototypes, | |
|
30 | 27 | wireprotov1peer, |
|
31 | wireprotov2peer, | |
|
32 | wireprotov2server, | |
|
33 | 28 | ) |
|
34 |
from . |
|
|
35 | repository, | |
|
36 | util as interfaceutil, | |
|
37 | ) | |
|
38 | from .utils import ( | |
|
39 | cborutil, | |
|
40 | stringutil, | |
|
41 | urlutil, | |
|
42 | ) | |
|
29 | from .utils import urlutil | |
|
43 | 30 | |
|
44 | 31 | httplib = util.httplib |
|
45 | 32 | urlerr = util.urlerr |
@@ -331,9 +318,7 b' class RedirectedRepoError(error.RepoErro' | |||
|
331 | 318 | self.respurl = respurl |
|
332 | 319 | |
|
333 | 320 | |
|
334 | def parsev1commandresponse( | |
|
335 | ui, baseurl, requrl, qs, resp, compressible, allowcbor=False | |
|
336 | ): | |
|
321 | def parsev1commandresponse(ui, baseurl, requrl, qs, resp, compressible): | |
|
337 | 322 | # record the url we got redirected to |
|
338 | 323 | redirected = False |
|
339 | 324 | respurl = pycompat.bytesurl(resp.geturl()) |
@@ -376,17 +361,6 b' def parsev1commandresponse(' | |||
|
376 | 361 | try: |
|
377 | 362 | subtype = proto.split(b'-', 1)[1] |
|
378 | 363 | |
|
379 | # Unless we end up supporting CBOR in the legacy wire protocol, | |
|
380 | # this should ONLY be encountered for the initial capabilities | |
|
381 | # request during handshake. | |
|
382 | if subtype == b'cbor': | |
|
383 | if allowcbor: | |
|
384 | return respurl, proto, resp | |
|
385 | else: | |
|
386 | raise error.RepoError( | |
|
387 | _(b'unexpected CBOR response from server') | |
|
388 | ) | |
|
389 | ||
|
390 | 364 | version_info = tuple([int(n) for n in subtype.split(b'.')]) |
|
391 | 365 | except ValueError: |
|
392 | 366 | raise error.RepoError( |
@@ -564,85 +538,6 b' class httppeer(wireprotov1peer.wirepeer)' | |||
|
564 | 538 | raise exception |
|
565 | 539 | |
|
566 | 540 | |
|
567 | def sendv2request( | |
|
568 | ui, opener, requestbuilder, apiurl, permission, requests, redirect | |
|
569 | ): | |
|
570 | wireprotoframing.populatestreamencoders() | |
|
571 | ||
|
572 | uiencoders = ui.configlist(b'experimental', b'httppeer.v2-encoder-order') | |
|
573 | ||
|
574 | if uiencoders: | |
|
575 | encoders = [] | |
|
576 | ||
|
577 | for encoder in uiencoders: | |
|
578 | if encoder not in wireprotoframing.STREAM_ENCODERS: | |
|
579 | ui.warn( | |
|
580 | _( | |
|
581 | b'wire protocol version 2 encoder referenced in ' | |
|
582 | b'config (%s) is not known; ignoring\n' | |
|
583 | ) | |
|
584 | % encoder | |
|
585 | ) | |
|
586 | else: | |
|
587 | encoders.append(encoder) | |
|
588 | ||
|
589 | else: | |
|
590 | encoders = wireprotoframing.STREAM_ENCODERS_ORDER | |
|
591 | ||
|
592 | reactor = wireprotoframing.clientreactor( | |
|
593 | ui, | |
|
594 | hasmultiplesend=False, | |
|
595 | buffersends=True, | |
|
596 | clientcontentencoders=encoders, | |
|
597 | ) | |
|
598 | ||
|
599 | handler = wireprotov2peer.clienthandler( | |
|
600 | ui, reactor, opener=opener, requestbuilder=requestbuilder | |
|
601 | ) | |
|
602 | ||
|
603 | url = b'%s/%s' % (apiurl, permission) | |
|
604 | ||
|
605 | if len(requests) > 1: | |
|
606 | url += b'/multirequest' | |
|
607 | else: | |
|
608 | url += b'/%s' % requests[0][0] | |
|
609 | ||
|
610 | ui.debug(b'sending %d commands\n' % len(requests)) | |
|
611 | for command, args, f in requests: | |
|
612 | ui.debug( | |
|
613 | b'sending command %s: %s\n' | |
|
614 | % (command, stringutil.pprint(args, indent=2)) | |
|
615 | ) | |
|
616 | assert not list( | |
|
617 | handler.callcommand(command, args, f, redirect=redirect) | |
|
618 | ) | |
|
619 | ||
|
620 | # TODO stream this. | |
|
621 | body = b''.join(map(bytes, handler.flushcommands())) | |
|
622 | ||
|
623 | # TODO modify user-agent to reflect v2 | |
|
624 | headers = { | |
|
625 | 'Accept': wireprotov2server.FRAMINGTYPE, | |
|
626 | 'Content-Type': wireprotov2server.FRAMINGTYPE, | |
|
627 | } | |
|
628 | ||
|
629 | req = requestbuilder(pycompat.strurl(url), body, headers) | |
|
630 | req.add_unredirected_header('Content-Length', '%d' % len(body)) | |
|
631 | ||
|
632 | try: | |
|
633 | res = opener.open(req) | |
|
634 | except urlerr.httperror as e: | |
|
635 | if e.code == 401: | |
|
636 | raise error.Abort(_(b'authorization failed')) | |
|
637 | ||
|
638 | raise | |
|
639 | except httplib.HTTPException as e: | |
|
640 | ui.traceback() | |
|
641 | raise IOError(None, e) | |
|
642 | ||
|
643 | return handler, res | |
|
644 | ||
|
645 | ||
|
646 | 541 | class queuedcommandfuture(pycompat.futures.Future): |
|
647 | 542 | """Wraps result() on command futures to trigger submission on call.""" |
|
648 | 543 | |
@@ -657,302 +552,6 b' class queuedcommandfuture(pycompat.futur' | |||
|
657 | 552 | return self.result(timeout) |
|
658 | 553 | |
|
659 | 554 | |
|
660 | @interfaceutil.implementer(repository.ipeercommandexecutor) | |
|
661 | class httpv2executor(object): | |
|
662 | def __init__( | |
|
663 | self, ui, opener, requestbuilder, apiurl, descriptor, redirect | |
|
664 | ): | |
|
665 | self._ui = ui | |
|
666 | self._opener = opener | |
|
667 | self._requestbuilder = requestbuilder | |
|
668 | self._apiurl = apiurl | |
|
669 | self._descriptor = descriptor | |
|
670 | self._redirect = redirect | |
|
671 | self._sent = False | |
|
672 | self._closed = False | |
|
673 | self._neededpermissions = set() | |
|
674 | self._calls = [] | |
|
675 | self._futures = weakref.WeakSet() | |
|
676 | self._responseexecutor = None | |
|
677 | self._responsef = None | |
|
678 | ||
|
679 | def __enter__(self): | |
|
680 | return self | |
|
681 | ||
|
682 | def __exit__(self, exctype, excvalue, exctb): | |
|
683 | self.close() | |
|
684 | ||
|
685 | def callcommand(self, command, args): | |
|
686 | if self._sent: | |
|
687 | raise error.ProgrammingError( | |
|
688 | b'callcommand() cannot be used after commands are sent' | |
|
689 | ) | |
|
690 | ||
|
691 | if self._closed: | |
|
692 | raise error.ProgrammingError( | |
|
693 | b'callcommand() cannot be used after close()' | |
|
694 | ) | |
|
695 | ||
|
696 | # The service advertises which commands are available. So if we attempt | |
|
697 | # to call an unknown command or pass an unknown argument, we can screen | |
|
698 | # for this. | |
|
699 | if command not in self._descriptor[b'commands']: | |
|
700 | raise error.ProgrammingError( | |
|
701 | b'wire protocol command %s is not available' % command | |
|
702 | ) | |
|
703 | ||
|
704 | cmdinfo = self._descriptor[b'commands'][command] | |
|
705 | unknownargs = set(args.keys()) - set(cmdinfo.get(b'args', {})) | |
|
706 | ||
|
707 | if unknownargs: | |
|
708 | raise error.ProgrammingError( | |
|
709 | b'wire protocol command %s does not accept argument: %s' | |
|
710 | % (command, b', '.join(sorted(unknownargs))) | |
|
711 | ) | |
|
712 | ||
|
713 | self._neededpermissions |= set(cmdinfo[b'permissions']) | |
|
714 | ||
|
715 | # TODO we /could/ also validate types here, since the API descriptor | |
|
716 | # includes types... | |
|
717 | ||
|
718 | f = pycompat.futures.Future() | |
|
719 | ||
|
720 | # Monkeypatch it so result() triggers sendcommands(), otherwise result() | |
|
721 | # could deadlock. | |
|
722 | f.__class__ = queuedcommandfuture | |
|
723 | f._peerexecutor = self | |
|
724 | ||
|
725 | self._futures.add(f) | |
|
726 | self._calls.append((command, args, f)) | |
|
727 | ||
|
728 | return f | |
|
729 | ||
|
730 | def sendcommands(self): | |
|
731 | if self._sent: | |
|
732 | return | |
|
733 | ||
|
734 | if not self._calls: | |
|
735 | return | |
|
736 | ||
|
737 | self._sent = True | |
|
738 | ||
|
739 | # Unhack any future types so caller sees a clean type and so we | |
|
740 | # break reference cycle. | |
|
741 | for f in self._futures: | |
|
742 | if isinstance(f, queuedcommandfuture): | |
|
743 | f.__class__ = pycompat.futures.Future | |
|
744 | f._peerexecutor = None | |
|
745 | ||
|
746 | # Mark the future as running and filter out cancelled futures. | |
|
747 | calls = [ | |
|
748 | (command, args, f) | |
|
749 | for command, args, f in self._calls | |
|
750 | if f.set_running_or_notify_cancel() | |
|
751 | ] | |
|
752 | ||
|
753 | # Clear out references, prevent improper object usage. | |
|
754 | self._calls = None | |
|
755 | ||
|
756 | if not calls: | |
|
757 | return | |
|
758 | ||
|
759 | permissions = set(self._neededpermissions) | |
|
760 | ||
|
761 | if b'push' in permissions and b'pull' in permissions: | |
|
762 | permissions.remove(b'pull') | |
|
763 | ||
|
764 | if len(permissions) > 1: | |
|
765 | raise error.RepoError( | |
|
766 | _(b'cannot make request requiring multiple permissions: %s') | |
|
767 | % _(b', ').join(sorted(permissions)) | |
|
768 | ) | |
|
769 | ||
|
770 | permission = { | |
|
771 | b'push': b'rw', | |
|
772 | b'pull': b'ro', | |
|
773 | }[permissions.pop()] | |
|
774 | ||
|
775 | handler, resp = sendv2request( | |
|
776 | self._ui, | |
|
777 | self._opener, | |
|
778 | self._requestbuilder, | |
|
779 | self._apiurl, | |
|
780 | permission, | |
|
781 | calls, | |
|
782 | self._redirect, | |
|
783 | ) | |
|
784 | ||
|
785 | # TODO we probably want to validate the HTTP code, media type, etc. | |
|
786 | ||
|
787 | self._responseexecutor = pycompat.futures.ThreadPoolExecutor(1) | |
|
788 | self._responsef = self._responseexecutor.submit( | |
|
789 | self._handleresponse, handler, resp | |
|
790 | ) | |
|
791 | ||
|
792 | def close(self): | |
|
793 | if self._closed: | |
|
794 | return | |
|
795 | ||
|
796 | self.sendcommands() | |
|
797 | ||
|
798 | self._closed = True | |
|
799 | ||
|
800 | if not self._responsef: | |
|
801 | return | |
|
802 | ||
|
803 | # TODO ^C here may not result in immediate program termination. | |
|
804 | ||
|
805 | try: | |
|
806 | self._responsef.result() | |
|
807 | finally: | |
|
808 | self._responseexecutor.shutdown(wait=True) | |
|
809 | self._responsef = None | |
|
810 | self._responseexecutor = None | |
|
811 | ||
|
812 | # If any of our futures are still in progress, mark them as | |
|
813 | # errored, otherwise a result() could wait indefinitely. | |
|
814 | for f in self._futures: | |
|
815 | if not f.done(): | |
|
816 | f.set_exception( | |
|
817 | error.ResponseError(_(b'unfulfilled command response')) | |
|
818 | ) | |
|
819 | ||
|
820 | self._futures = None | |
|
821 | ||
|
822 | def _handleresponse(self, handler, resp): | |
|
823 | # Called in a thread to read the response. | |
|
824 | ||
|
825 | while handler.readdata(resp): | |
|
826 | pass | |
|
827 | ||
|
828 | ||
|
829 | @interfaceutil.implementer(repository.ipeerv2) | |
|
830 | class httpv2peer(object): | |
|
831 | ||
|
832 | limitedarguments = False | |
|
833 | ||
|
834 | def __init__( | |
|
835 | self, ui, repourl, apipath, opener, requestbuilder, apidescriptor | |
|
836 | ): | |
|
837 | self.ui = ui | |
|
838 | self.apidescriptor = apidescriptor | |
|
839 | ||
|
840 | if repourl.endswith(b'/'): | |
|
841 | repourl = repourl[:-1] | |
|
842 | ||
|
843 | self._url = repourl | |
|
844 | self._apipath = apipath | |
|
845 | self._apiurl = b'%s/%s' % (repourl, apipath) | |
|
846 | self._opener = opener | |
|
847 | self._requestbuilder = requestbuilder | |
|
848 | ||
|
849 | self._redirect = wireprotov2peer.supportedredirects(ui, apidescriptor) | |
|
850 | ||
|
851 | # Start of ipeerconnection. | |
|
852 | ||
|
853 | def url(self): | |
|
854 | return self._url | |
|
855 | ||
|
856 | def local(self): | |
|
857 | return None | |
|
858 | ||
|
859 | def peer(self): | |
|
860 | return self | |
|
861 | ||
|
862 | def canpush(self): | |
|
863 | # TODO change once implemented. | |
|
864 | return False | |
|
865 | ||
|
866 | def close(self): | |
|
867 | self.ui.note( | |
|
868 | _( | |
|
869 | b'(sent %d HTTP requests and %d bytes; ' | |
|
870 | b'received %d bytes in responses)\n' | |
|
871 | ) | |
|
872 | % ( | |
|
873 | self._opener.requestscount, | |
|
874 | self._opener.sentbytescount, | |
|
875 | self._opener.receivedbytescount, | |
|
876 | ) | |
|
877 | ) | |
|
878 | ||
|
879 | # End of ipeerconnection. | |
|
880 | ||
|
881 | # Start of ipeercapabilities. | |
|
882 | ||
|
883 | def capable(self, name): | |
|
884 | # The capabilities used internally historically map to capabilities | |
|
885 | # advertised from the "capabilities" wire protocol command. However, | |
|
886 | # version 2 of that command works differently. | |
|
887 | ||
|
888 | # Maps to commands that are available. | |
|
889 | if name in ( | |
|
890 | b'branchmap', | |
|
891 | b'getbundle', | |
|
892 | b'known', | |
|
893 | b'lookup', | |
|
894 | b'pushkey', | |
|
895 | ): | |
|
896 | return True | |
|
897 | ||
|
898 | # Other concepts. | |
|
899 | if name in (b'bundle2',): | |
|
900 | return True | |
|
901 | ||
|
902 | # Alias command-* to presence of command of that name. | |
|
903 | if name.startswith(b'command-'): | |
|
904 | return name[len(b'command-') :] in self.apidescriptor[b'commands'] | |
|
905 | ||
|
906 | return False | |
|
907 | ||
|
908 | def requirecap(self, name, purpose): | |
|
909 | if self.capable(name): | |
|
910 | return | |
|
911 | ||
|
912 | raise error.CapabilityError( | |
|
913 | _( | |
|
914 | b'cannot %s; client or remote repository does not support the ' | |
|
915 | b'\'%s\' capability' | |
|
916 | ) | |
|
917 | % (purpose, name) | |
|
918 | ) | |
|
919 | ||
|
920 | # End of ipeercapabilities. | |
|
921 | ||
|
922 | def _call(self, name, **args): | |
|
923 | with self.commandexecutor() as e: | |
|
924 | return e.callcommand(name, args).result() | |
|
925 | ||
|
926 | def commandexecutor(self): | |
|
927 | return httpv2executor( | |
|
928 | self.ui, | |
|
929 | self._opener, | |
|
930 | self._requestbuilder, | |
|
931 | self._apiurl, | |
|
932 | self.apidescriptor, | |
|
933 | self._redirect, | |
|
934 | ) | |
|
935 | ||
|
936 | ||
|
937 | # Registry of API service names to metadata about peers that handle it. | |
|
938 | # | |
|
939 | # The following keys are meaningful: | |
|
940 | # | |
|
941 | # init | |
|
942 | # Callable receiving (ui, repourl, servicepath, opener, requestbuilder, | |
|
943 | # apidescriptor) to create a peer. | |
|
944 | # | |
|
945 | # priority | |
|
946 | # Integer priority for the service. If we could choose from multiple | |
|
947 | # services, we choose the one with the highest priority. | |
|
948 | API_PEERS = { | |
|
949 | wireprototypes.HTTP_WIREPROTO_V2: { | |
|
950 | b'init': httpv2peer, | |
|
951 | b'priority': 50, | |
|
952 | }, | |
|
953 | } | |
|
954 | ||
|
955 | ||
|
956 | 555 | def performhandshake(ui, url, opener, requestbuilder): |
|
957 | 556 | # The handshake is a request to the capabilities command. |
|
958 | 557 | |
@@ -963,28 +562,6 b' def performhandshake(ui, url, opener, re' | |||
|
963 | 562 | |
|
964 | 563 | args = {} |
|
965 | 564 | |
|
966 | # The client advertises support for newer protocols by adding an | |
|
967 | # X-HgUpgrade-* header with a list of supported APIs and an | |
|
968 | # X-HgProto-* header advertising which serializing formats it supports. | |
|
969 | # We only support the HTTP version 2 transport and CBOR responses for | |
|
970 | # now. | |
|
971 | advertisev2 = ui.configbool(b'experimental', b'httppeer.advertise-v2') | |
|
972 | ||
|
973 | if advertisev2: | |
|
974 | args[b'headers'] = { | |
|
975 | 'X-HgProto-1': 'cbor', | |
|
976 | } | |
|
977 | ||
|
978 | args[b'headers'].update( | |
|
979 | encodevalueinheaders( | |
|
980 | b' '.join(sorted(API_PEERS)), | |
|
981 | b'X-HgUpgrade', | |
|
982 | # We don't know the header limit this early. | |
|
983 | # So make it small. | |
|
984 | 1024, | |
|
985 | ) | |
|
986 | ) | |
|
987 | ||
|
988 | 565 | req, requrl, qs = makev1commandrequest( |
|
989 | 566 | ui, requestbuilder, caps, capable, url, b'capabilities', args |
|
990 | 567 | ) |
@@ -1004,7 +581,7 b' def performhandshake(ui, url, opener, re' | |||
|
1004 | 581 | # redirect that drops the query string to "just work." |
|
1005 | 582 | try: |
|
1006 | 583 | respurl, ct, resp = parsev1commandresponse( |
|
1007 |
ui, url, requrl, qs, resp, compressible=False |
|
|
584 | ui, url, requrl, qs, resp, compressible=False | |
|
1008 | 585 | ) |
|
1009 | 586 | except RedirectedRepoError as e: |
|
1010 | 587 | req, requrl, qs = makev1commandrequest( |
@@ -1012,7 +589,7 b' def performhandshake(ui, url, opener, re' | |||
|
1012 | 589 | ) |
|
1013 | 590 | resp = sendrequest(ui, opener, req) |
|
1014 | 591 | respurl, ct, resp = parsev1commandresponse( |
|
1015 |
ui, url, requrl, qs, resp, compressible=False |
|
|
592 | ui, url, requrl, qs, resp, compressible=False | |
|
1016 | 593 | ) |
|
1017 | 594 | |
|
1018 | 595 | try: |
@@ -1023,28 +600,6 b' def performhandshake(ui, url, opener, re' | |||
|
1023 | 600 | if not ct.startswith(b'application/mercurial-'): |
|
1024 | 601 | raise error.ProgrammingError(b'unexpected content-type: %s' % ct) |
|
1025 | 602 | |
|
1026 | if advertisev2: | |
|
1027 | if ct == b'application/mercurial-cbor': | |
|
1028 | try: | |
|
1029 | info = cborutil.decodeall(rawdata)[0] | |
|
1030 | except cborutil.CBORDecodeError: | |
|
1031 | raise error.Abort( | |
|
1032 | _(b'error decoding CBOR from remote server'), | |
|
1033 | hint=_( | |
|
1034 | b'try again and consider contacting ' | |
|
1035 | b'the server operator' | |
|
1036 | ), | |
|
1037 | ) | |
|
1038 | ||
|
1039 | # We got a legacy response. That's fine. | |
|
1040 | elif ct in (b'application/mercurial-0.1', b'application/mercurial-0.2'): | |
|
1041 | info = {b'v1capabilities': set(rawdata.split())} | |
|
1042 | ||
|
1043 | else: | |
|
1044 | raise error.RepoError( | |
|
1045 | _(b'unexpected response type from server: %s') % ct | |
|
1046 | ) | |
|
1047 | else: | |
|
1048 | 603 |
|
|
1049 | 604 | |
|
1050 | 605 | return respurl, info |
@@ -1073,29 +628,6 b' def makepeer(ui, path, opener=None, requ' | |||
|
1073 | 628 | |
|
1074 | 629 | respurl, info = performhandshake(ui, url, opener, requestbuilder) |
|
1075 | 630 | |
|
1076 | # Given the intersection of APIs that both we and the server support, | |
|
1077 | # sort by their advertised priority and pick the first one. | |
|
1078 | # | |
|
1079 | # TODO consider making this request-based and interface driven. For | |
|
1080 | # example, the caller could say "I want a peer that does X." It's quite | |
|
1081 | # possible that not all peers would do that. Since we know the service | |
|
1082 | # capabilities, we could filter out services not meeting the | |
|
1083 | # requirements. Possibly by consulting the interfaces defined by the | |
|
1084 | # peer type. | |
|
1085 | apipeerchoices = set(info.get(b'apis', {}).keys()) & set(API_PEERS.keys()) | |
|
1086 | ||
|
1087 | preferredchoices = sorted( | |
|
1088 | apipeerchoices, key=lambda x: API_PEERS[x][b'priority'], reverse=True | |
|
1089 | ) | |
|
1090 | ||
|
1091 | for service in preferredchoices: | |
|
1092 | apipath = b'%s/%s' % (info[b'apibase'].rstrip(b'/'), service) | |
|
1093 | ||
|
1094 | return API_PEERS[service][b'init']( | |
|
1095 | ui, respurl, apipath, opener, requestbuilder, info[b'apis'][service] | |
|
1096 | ) | |
|
1097 | ||
|
1098 | # Failed to construct an API peer. Fall back to legacy. | |
|
1099 | 631 | return httppeer( |
|
1100 | 632 | ui, path, respurl, opener, requestbuilder, info[b'v1capabilities'] |
|
1101 | 633 | ) |
@@ -66,17 +66,6 b' class idirstate(interfaceutil.Interface)' | |||
|
66 | 66 | def pathto(f, cwd=None): |
|
67 | 67 | pass |
|
68 | 68 | |
|
69 | def __getitem__(key): | |
|
70 | """Return the current state of key (a filename) in the dirstate. | |
|
71 | ||
|
72 | States are: | |
|
73 | n normal | |
|
74 | m needs merging | |
|
75 | r marked for removal | |
|
76 | a marked for addition | |
|
77 | ? not tracked | |
|
78 | """ | |
|
79 | ||
|
80 | 69 | def __contains__(key): |
|
81 | 70 | """Check if bytestring `key` is known to the dirstate.""" |
|
82 | 71 |
@@ -1278,7 +1278,7 b' class imanifeststorage(interfaceutil.Int' | |||
|
1278 | 1278 | def linkrev(rev): |
|
1279 | 1279 | """Obtain the changeset revision number a revision is linked to.""" |
|
1280 | 1280 | |
|
1281 |
def revision(node, _df=None |
|
|
1281 | def revision(node, _df=None): | |
|
1282 | 1282 | """Obtain fulltext data for a node.""" |
|
1283 | 1283 | |
|
1284 | 1284 | def rawdata(node, _df=None): |
@@ -1495,13 +1495,6 b' class ilocalrepositorymain(interfaceutil' | |||
|
1495 | 1495 | """null revision for the hash function used by the repository.""" |
|
1496 | 1496 | ) |
|
1497 | 1497 | |
|
1498 | supportedformats = interfaceutil.Attribute( | |
|
1499 | """Set of requirements that apply to stream clone. | |
|
1500 | ||
|
1501 | This is actually a class attribute and is shared among all instances. | |
|
1502 | """ | |
|
1503 | ) | |
|
1504 | ||
|
1505 | 1498 | supported = interfaceutil.Attribute( |
|
1506 | 1499 | """Set of requirements that this repo is capable of opening.""" |
|
1507 | 1500 | ) |
@@ -1794,7 +1787,7 b' class ilocalrepositorymain(interfaceutil' | |||
|
1794 | 1787 | DANGEROUS. |
|
1795 | 1788 | """ |
|
1796 | 1789 | |
|
1797 | def updatecaches(tr=None, full=False): | |
|
1790 | def updatecaches(tr=None, full=False, caches=None): | |
|
1798 | 1791 | """Warm repo caches.""" |
|
1799 | 1792 | |
|
1800 | 1793 | def invalidatecaches(): |
@@ -1,4 +1,5 b'' | |||
|
1 | 1 | # localrepo.py - read/write repository class for mercurial |
|
2 | # coding: utf-8 | |
|
2 | 3 | # |
|
3 | 4 | # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com> |
|
4 | 5 | # |
@@ -931,7 +932,7 b' def gathersupportedrequirements(ui):' | |||
|
931 | 932 | if engine.available() and engine.revlogheader(): |
|
932 | 933 | supported.add(b'exp-compression-%s' % name) |
|
933 | 934 | if engine.name() == b'zstd': |
|
934 | supported.add(b'revlog-compression-zstd') | |
|
935 | supported.add(requirementsmod.REVLOG_COMPRESSION_ZSTD) | |
|
935 | 936 | |
|
936 | 937 | return supported |
|
937 | 938 | |
@@ -1273,32 +1274,26 b' class localrepository(object):' | |||
|
1273 | 1274 | used. |
|
1274 | 1275 | """ |
|
1275 | 1276 | |
|
1276 | # obsolete experimental requirements: | |
|
1277 | # - manifestv2: An experimental new manifest format that allowed | |
|
1278 | # for stem compression of long paths. Experiment ended up not | |
|
1279 | # being successful (repository sizes went up due to worse delta | |
|
1280 | # chains), and the code was deleted in 4.6. | |
|
1281 | supportedformats = { | |
|
1277 | _basesupported = { | |
|
1278 | requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT, | |
|
1279 | requirementsmod.CHANGELOGV2_REQUIREMENT, | |
|
1280 | requirementsmod.COPIESSDC_REQUIREMENT, | |
|
1281 | requirementsmod.DIRSTATE_TRACKED_HINT_V1, | |
|
1282 | requirementsmod.DIRSTATE_V2_REQUIREMENT, | |
|
1283 | requirementsmod.DOTENCODE_REQUIREMENT, | |
|
1284 | requirementsmod.FNCACHE_REQUIREMENT, | |
|
1285 | requirementsmod.GENERALDELTA_REQUIREMENT, | |
|
1286 | requirementsmod.INTERNAL_PHASE_REQUIREMENT, | |
|
1287 | requirementsmod.NODEMAP_REQUIREMENT, | |
|
1288 | requirementsmod.RELATIVE_SHARED_REQUIREMENT, | |
|
1282 | 1289 | requirementsmod.REVLOGV1_REQUIREMENT, |
|
1283 | requirementsmod.GENERALDELTA_REQUIREMENT, | |
|
1284 | requirementsmod.TREEMANIFEST_REQUIREMENT, | |
|
1285 | requirementsmod.COPIESSDC_REQUIREMENT, | |
|
1286 | 1290 | requirementsmod.REVLOGV2_REQUIREMENT, |
|
1287 |
requirementsmod. |
|
|
1291 | requirementsmod.SHARED_REQUIREMENT, | |
|
1292 | requirementsmod.SHARESAFE_REQUIREMENT, | |
|
1293 | requirementsmod.SPARSE_REQUIREMENT, | |
|
1288 | 1294 | requirementsmod.SPARSEREVLOG_REQUIREMENT, |
|
1289 | requirementsmod.NODEMAP_REQUIREMENT, | |
|
1290 | bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT, | |
|
1291 | requirementsmod.SHARESAFE_REQUIREMENT, | |
|
1292 | requirementsmod.DIRSTATE_V2_REQUIREMENT, | |
|
1293 | } | |
|
1294 | _basesupported = supportedformats | { | |
|
1295 | 1295 | requirementsmod.STORE_REQUIREMENT, |
|
1296 |
requirementsmod.F |
|
|
1297 | requirementsmod.SHARED_REQUIREMENT, | |
|
1298 | requirementsmod.RELATIVE_SHARED_REQUIREMENT, | |
|
1299 | requirementsmod.DOTENCODE_REQUIREMENT, | |
|
1300 | requirementsmod.SPARSE_REQUIREMENT, | |
|
1301 | requirementsmod.INTERNAL_PHASE_REQUIREMENT, | |
|
1296 | requirementsmod.TREEMANIFEST_REQUIREMENT, | |
|
1302 | 1297 | } |
|
1303 | 1298 | |
|
1304 | 1299 | # list of prefix for file which can be written without 'wlock' |
@@ -1748,7 +1743,9 b' class localrepository(object):' | |||
|
1748 | 1743 | """Extension point for wrapping the dirstate per-repo.""" |
|
1749 | 1744 | sparsematchfn = lambda: sparse.matcher(self) |
|
1750 | 1745 | v2_req = requirementsmod.DIRSTATE_V2_REQUIREMENT |
|
1746 | th = requirementsmod.DIRSTATE_TRACKED_HINT_V1 | |
|
1751 | 1747 | use_dirstate_v2 = v2_req in self.requirements |
|
1748 | use_tracked_hint = th in self.requirements | |
|
1752 | 1749 | |
|
1753 | 1750 | return dirstate.dirstate( |
|
1754 | 1751 | self.vfs, |
@@ -1758,6 +1755,7 b' class localrepository(object):' | |||
|
1758 | 1755 | sparsematchfn, |
|
1759 | 1756 | self.nodeconstants, |
|
1760 | 1757 | use_dirstate_v2, |
|
1758 | use_tracked_hint=use_tracked_hint, | |
|
1761 | 1759 | ) |
|
1762 | 1760 | |
|
1763 | 1761 | def _dirstatevalidate(self, node): |
@@ -3551,6 +3549,10 b' def clone_requirements(ui, createopts, s' | |||
|
3551 | 3549 | depends on the configuration |
|
3552 | 3550 | """ |
|
3553 | 3551 | target_requirements = set() |
|
3552 | if not srcrepo.requirements: | |
|
3553 | # this is a legacy revlog "v0" repository, we cannot do anything fancy | |
|
3554 | # with it. | |
|
3555 | return target_requirements | |
|
3554 | 3556 | createopts = defaultcreateopts(ui, createopts=createopts) |
|
3555 | 3557 | for r in newreporequirements(ui, createopts): |
|
3556 | 3558 | if r in requirementsmod.WORKING_DIR_REQUIREMENTS: |
@@ -3568,16 +3570,6 b' def newreporequirements(ui, createopts):' | |||
|
3568 | 3570 | Extensions can wrap this function to specify custom requirements for |
|
3569 | 3571 | new repositories. |
|
3570 | 3572 | """ |
|
3571 | # If the repo is being created from a shared repository, we copy | |
|
3572 | # its requirements. | |
|
3573 | if b'sharedrepo' in createopts: | |
|
3574 | requirements = set(createopts[b'sharedrepo'].requirements) | |
|
3575 | if createopts.get(b'sharedrelative'): | |
|
3576 | requirements.add(requirementsmod.RELATIVE_SHARED_REQUIREMENT) | |
|
3577 | else: | |
|
3578 | requirements.add(requirementsmod.SHARED_REQUIREMENT) | |
|
3579 | ||
|
3580 | return requirements | |
|
3581 | 3573 | |
|
3582 | 3574 | if b'backend' not in createopts: |
|
3583 | 3575 | raise error.ProgrammingError( |
@@ -3663,7 +3655,7 b' def newreporequirements(ui, createopts):' | |||
|
3663 | 3655 | requirements.add(b'lfs') |
|
3664 | 3656 | |
|
3665 | 3657 | if ui.configbool(b'format', b'bookmarks-in-store'): |
|
3666 |
requirements.add( |
|
|
3658 | requirements.add(requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT) | |
|
3667 | 3659 | |
|
3668 | 3660 | if ui.configbool(b'format', b'use-persistent-nodemap'): |
|
3669 | 3661 | requirements.add(requirementsmod.NODEMAP_REQUIREMENT) |
@@ -3673,6 +3665,45 b' def newreporequirements(ui, createopts):' | |||
|
3673 | 3665 | if ui.configbool(b'format', b'use-share-safe'): |
|
3674 | 3666 | requirements.add(requirementsmod.SHARESAFE_REQUIREMENT) |
|
3675 | 3667 | |
|
3668 | # if we are creating a share-repo¹ we have to handle requirement | |
|
3669 | # differently. | |
|
3670 | # | |
|
3671 | # [1] (i.e. reusing the store from another repository, just having a | |
|
3672 | # working copy) | |
|
3673 | if b'sharedrepo' in createopts: | |
|
3674 | source_requirements = set(createopts[b'sharedrepo'].requirements) | |
|
3675 | ||
|
3676 | if requirementsmod.SHARESAFE_REQUIREMENT not in source_requirements: | |
|
3677 | # share to an old school repository, we have to copy the | |
|
3678 | # requirements and hope for the best. | |
|
3679 | requirements = source_requirements | |
|
3680 | else: | |
|
3681 | # We have control on the working copy only, so "copy" the non | |
|
3682 | # working copy part over, ignoring previous logic. | |
|
3683 | to_drop = set() | |
|
3684 | for req in requirements: | |
|
3685 | if req in requirementsmod.WORKING_DIR_REQUIREMENTS: | |
|
3686 | continue | |
|
3687 | if req in source_requirements: | |
|
3688 | continue | |
|
3689 | to_drop.add(req) | |
|
3690 | requirements -= to_drop | |
|
3691 | requirements |= source_requirements | |
|
3692 | ||
|
3693 | if createopts.get(b'sharedrelative'): | |
|
3694 | requirements.add(requirementsmod.RELATIVE_SHARED_REQUIREMENT) | |
|
3695 | else: | |
|
3696 | requirements.add(requirementsmod.SHARED_REQUIREMENT) | |
|
3697 | ||
|
3698 | if ui.configbool(b'format', b'use-dirstate-tracked-hint'): | |
|
3699 | version = ui.configint(b'format', b'use-dirstate-tracked-hint.version') | |
|
3700 | msg = _("ignoring unknown tracked key version: %d\n") | |
|
3701 | hint = _("see `hg help config.format.use-dirstate-tracked-hint-version") | |
|
3702 | if version != 1: | |
|
3703 | ui.warn(msg % version, hint=hint) | |
|
3704 | else: | |
|
3705 | requirements.add(requirementsmod.DIRSTATE_TRACKED_HINT_V1) | |
|
3706 | ||
|
3676 | 3707 | return requirements |
|
3677 | 3708 | |
|
3678 | 3709 | |
@@ -3685,7 +3716,7 b' def checkrequirementscompat(ui, requirem' | |||
|
3685 | 3716 | dropped = set() |
|
3686 | 3717 | |
|
3687 | 3718 | if requirementsmod.STORE_REQUIREMENT not in requirements: |
|
3688 |
if |
|
|
3719 | if requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT in requirements: | |
|
3689 | 3720 | ui.warn( |
|
3690 | 3721 | _( |
|
3691 | 3722 | b'ignoring enabled \'format.bookmarks-in-store\' config ' |
@@ -3693,7 +3724,7 b' def checkrequirementscompat(ui, requirem' | |||
|
3693 | 3724 | b'\'format.usestore\' config\n' |
|
3694 | 3725 | ) |
|
3695 | 3726 | ) |
|
3696 |
dropped.add( |
|
|
3727 | dropped.add(requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT) | |
|
3697 | 3728 | |
|
3698 | 3729 | if ( |
|
3699 | 3730 | requirementsmod.SHARED_REQUIREMENT in requirements |
@@ -3707,13 +3738,13 b' def checkrequirementscompat(ui, requirem' | |||
|
3707 | 3738 | ) |
|
3708 | 3739 | |
|
3709 | 3740 | if requirementsmod.SHARESAFE_REQUIREMENT in requirements: |
|
3710 | ui.warn( | |
|
3711 | _( | |
|
3741 | if ui.hasconfig(b'format', b'use-share-safe'): | |
|
3742 | msg = _( | |
|
3712 | 3743 | b"ignoring enabled 'format.use-share-safe' config because " |
|
3713 | 3744 | b"it is incompatible with disabled 'format.usestore'" |
|
3714 | 3745 | b" config\n" |
|
3715 | 3746 | ) |
|
3716 | ) | |
|
3747 | ui.warn(msg) | |
|
3717 | 3748 | dropped.add(requirementsmod.SHARESAFE_REQUIREMENT) |
|
3718 | 3749 | |
|
3719 | 3750 | return dropped |
@@ -62,9 +62,9 b' def getlimit(opts):' | |||
|
62 | 62 | try: |
|
63 | 63 | limit = int(limit) |
|
64 | 64 | except ValueError: |
|
65 |
raise error. |
|
|
65 | raise error.InputError(_(b'limit must be a positive integer')) | |
|
66 | 66 | if limit <= 0: |
|
67 |
raise error. |
|
|
67 | raise error.InputError(_(b'limit must be positive')) | |
|
68 | 68 | else: |
|
69 | 69 | limit = None |
|
70 | 70 | return limit |
@@ -831,7 +831,7 b' def _makematcher(repo, revs, wopts):' | |||
|
831 | 831 | # take the slow path. |
|
832 | 832 | found = slowpath = True |
|
833 | 833 | if not found: |
|
834 |
raise error. |
|
|
834 | raise error.StateError( | |
|
835 | 835 | _( |
|
836 | 836 | b'cannot follow file not in any of the specified ' |
|
837 | 837 | b'revisions: "%s"' |
@@ -847,7 +847,7 b' def _makematcher(repo, revs, wopts):' | |||
|
847 | 847 | slowpath = True |
|
848 | 848 | continue |
|
849 | 849 | else: |
|
850 |
raise error. |
|
|
850 | raise error.StateError( | |
|
851 | 851 | _( |
|
852 | 852 | b'cannot follow file not in parent ' |
|
853 | 853 | b'revision: "%s"' |
@@ -858,7 +858,7 b' def _makematcher(repo, revs, wopts):' | |||
|
858 | 858 | if not filelog: |
|
859 | 859 | # A file exists in wdir but not in history, which means |
|
860 | 860 | # the file isn't committed yet. |
|
861 |
raise error. |
|
|
861 | raise error.StateError( | |
|
862 | 862 | _(b'cannot follow nonexistent file: "%s"') % f |
|
863 | 863 | ) |
|
864 | 864 | else: |
@@ -1108,11 +1108,13 b' def _parselinerangeopt(repo, opts):' | |||
|
1108 | 1108 | try: |
|
1109 | 1109 | pat, linerange = pat.rsplit(b',', 1) |
|
1110 | 1110 | except ValueError: |
|
1111 | raise error.Abort(_(b'malformatted line-range pattern %s') % pat) | |
|
1111 | raise error.InputError( | |
|
1112 | _(b'malformatted line-range pattern %s') % pat | |
|
1113 | ) | |
|
1112 | 1114 | try: |
|
1113 | 1115 | fromline, toline = map(int, linerange.split(b':')) |
|
1114 | 1116 | except ValueError: |
|
1115 |
raise error. |
|
|
1117 | raise error.InputError(_(b"invalid line range for %s") % pat) | |
|
1116 | 1118 | msg = _(b"line range pattern '%s' must match exactly one file") % pat |
|
1117 | 1119 | fname = scmutil.parsefollowlinespattern(repo, None, pat, msg) |
|
1118 | 1120 | linerangebyfname.append( |
@@ -1136,7 +1138,7 b' def getlinerangerevs(repo, userrevs, opt' | |||
|
1136 | 1138 | linerangesbyrev = {} |
|
1137 | 1139 | for fname, (fromline, toline) in _parselinerangeopt(repo, opts): |
|
1138 | 1140 | if fname not in wctx: |
|
1139 |
raise error. |
|
|
1141 | raise error.StateError( | |
|
1140 | 1142 | _(b'cannot follow file not in parent revision: "%s"') % fname |
|
1141 | 1143 | ) |
|
1142 | 1144 | fctx = wctx.filectx(fname) |
@@ -1271,7 +1273,7 b' def displayrevs(ui, repo, revs, displaye' | |||
|
1271 | 1273 | def checkunsupportedgraphflags(pats, opts): |
|
1272 | 1274 | for op in [b"newest_first"]: |
|
1273 | 1275 | if op in opts and opts[op]: |
|
1274 |
raise error. |
|
|
1276 | raise error.InputError( | |
|
1275 | 1277 | _(b"-G/--graph option is incompatible with --%s") |
|
1276 | 1278 | % op.replace(b"_", b"-") |
|
1277 | 1279 | ) |
@@ -1819,8 +1819,8 b' class manifestrevlog(object):' | |||
|
1819 | 1819 | def checksize(self): |
|
1820 | 1820 | return self._revlog.checksize() |
|
1821 | 1821 | |
|
1822 |
def revision(self, node, _df=None |
|
|
1823 |
return self._revlog.revision(node, _df=_df |
|
|
1822 | def revision(self, node, _df=None): | |
|
1823 | return self._revlog.revision(node, _df=_df) | |
|
1824 | 1824 | |
|
1825 | 1825 | def rawdata(self, node, _df=None): |
|
1826 | 1826 | return self._revlog.rawdata(node, _df=_df) |
@@ -84,7 +84,7 b' class diffopts(object):' | |||
|
84 | 84 | try: |
|
85 | 85 | self.context = int(self.context) |
|
86 | 86 | except ValueError: |
|
87 |
raise error. |
|
|
87 | raise error.InputError( | |
|
88 | 88 | _(b'diff context lines count must be an integer, not %r') |
|
89 | 89 | % pycompat.bytestr(self.context) |
|
90 | 90 | ) |
@@ -41,10 +41,9 b' def _getcheckunknownconfig(repo, section' | |||
|
41 | 41 | valid = [b'abort', b'ignore', b'warn'] |
|
42 | 42 | if config not in valid: |
|
43 | 43 | validstr = b', '.join([b"'" + v + b"'" for v in valid]) |
|
44 | raise error.ConfigError( | |
|
45 | _(b"%s.%s not valid ('%s' is none of %s)") | |
|
46 | % (section, name, config, validstr) | |
|
47 | ) | |
|
44 | msg = _(b"%s.%s not valid ('%s' is none of %s)") | |
|
45 | msg %= (section, name, config, validstr) | |
|
46 | raise error.ConfigError(msg) | |
|
48 | 47 | return config |
|
49 | 48 | |
|
50 | 49 | |
@@ -337,10 +336,9 b' def _checkcollision(repo, wmf, mresult):' | |||
|
337 | 336 | for f in pmmf: |
|
338 | 337 | fold = util.normcase(f) |
|
339 | 338 | if fold in foldmap: |
|
340 | raise error.StateError( | |
|
341 | _(b"case-folding collision between %s and %s") | |
|
342 | % (f, foldmap[fold]) | |
|
343 | ) | |
|
339 | msg = _(b"case-folding collision between %s and %s") | |
|
340 | msg %= (f, foldmap[fold]) | |
|
341 | raise error.StateError(msg) | |
|
344 | 342 | foldmap[fold] = f |
|
345 | 343 | |
|
346 | 344 | # check case-folding of directories |
@@ -348,10 +346,9 b' def _checkcollision(repo, wmf, mresult):' | |||
|
348 | 346 | for fold, f in sorted(foldmap.items()): |
|
349 | 347 | if fold.startswith(foldprefix) and not f.startswith(unfoldprefix): |
|
350 | 348 | # the folded prefix matches but actual casing is different |
|
351 | raise error.StateError( | |
|
352 | _(b"case-folding collision between %s and directory of %s") | |
|
353 | % (lastfull, f) | |
|
354 | ) | |
|
349 | msg = _(b"case-folding collision between %s and directory of %s") | |
|
350 | msg %= (lastfull, f) | |
|
351 | raise error.StateError(msg) | |
|
355 | 352 | foldprefix = fold + b'/' |
|
356 | 353 | unfoldprefix = f + b'/' |
|
357 | 354 | lastfull = f |
@@ -491,7 +488,7 b' def checkpathconflicts(repo, wctx, mctx,' | |||
|
491 | 488 | mresult.addfile( |
|
492 | 489 | p, |
|
493 | 490 | mergestatemod.ACTION_PATH_CONFLICT, |
|
494 |
(pnew, |
|
|
491 | (pnew, b'r'), | |
|
495 | 492 | b'path conflict', |
|
496 | 493 | ) |
|
497 | 494 | remoteconflicts.remove(p) |
@@ -512,17 +509,6 b' def _filternarrowactions(narrowmatch, br' | |||
|
512 | 509 | Raise an exception if the merge cannot be completed because the repo is |
|
513 | 510 | narrowed. |
|
514 | 511 | """ |
|
515 | # TODO: handle with nonconflicttypes | |
|
516 | nonconflicttypes = { | |
|
517 | mergestatemod.ACTION_ADD, | |
|
518 | mergestatemod.ACTION_ADD_MODIFIED, | |
|
519 | mergestatemod.ACTION_CREATED, | |
|
520 | mergestatemod.ACTION_CREATED_MERGE, | |
|
521 | mergestatemod.ACTION_FORGET, | |
|
522 | mergestatemod.ACTION_GET, | |
|
523 | mergestatemod.ACTION_REMOVE, | |
|
524 | mergestatemod.ACTION_EXEC, | |
|
525 | } | |
|
526 | 512 | # We mutate the items in the dict during iteration, so iterate |
|
527 | 513 | # over a copy. |
|
528 | 514 | for f, action in mresult.filemap(): |
@@ -530,21 +516,25 b' def _filternarrowactions(narrowmatch, br' | |||
|
530 | 516 | pass |
|
531 | 517 | elif not branchmerge: |
|
532 | 518 | mresult.removefile(f) # just updating, ignore changes outside clone |
|
533 |
elif action[0] |
|
|
519 | elif action[0].no_op: | |
|
534 | 520 | mresult.removefile(f) # merge does not affect file |
|
535 |
elif action[0] |
|
|
536 | raise error.Abort( | |
|
537 | _( | |
|
521 | elif action[0].narrow_safe: | |
|
522 | if not f.endswith(b'/'): | |
|
523 | mresult.removefile(f) # merge won't affect on-disk files | |
|
524 | ||
|
525 | mresult.addcommitinfo( | |
|
526 | f, b'outside-narrow-merge-action', action[0].changes | |
|
527 | ) | |
|
528 | else: # TODO: handle the tree case | |
|
529 | msg = _( | |
|
538 | 530 | b'merge affects file \'%s\' outside narrow, ' |
|
539 | 531 | b'which is not yet supported' |
|
540 | 532 | ) |
|
541 | % f, | |
|
542 | hint=_(b'merging in the other direction may work'), | |
|
543 | ) | |
|
533 | hint = _(b'merging in the other direction may work') | |
|
534 | raise error.Abort(msg % f, hint=hint) | |
|
544 | 535 | else: |
|
545 | raise error.Abort( | |
|
546 | _(b'conflict in file \'%s\' is outside narrow clone') % f | |
|
547 | ) | |
|
536 | msg = _(b'conflict in file \'%s\' is outside narrow clone') | |
|
537 | raise error.StateError(msg % f) | |
|
548 | 538 | |
|
549 | 539 | |
|
550 | 540 | class mergeresult(object): |
@@ -705,7 +695,7 b' class mergeresult(object):' | |||
|
705 | 695 | mergestatemod.ACTION_PATH_CONFLICT_RESOLVE, |
|
706 | 696 | ) |
|
707 | 697 | and self._actionmapping[a] |
|
708 |
and |
|
|
698 | and not a.no_op | |
|
709 | 699 | ): |
|
710 | 700 | return True |
|
711 | 701 | |
@@ -1207,7 +1197,7 b' def calculateupdates(' | |||
|
1207 | 1197 | |
|
1208 | 1198 | for f, a in mresult1.filemap(sort=True): |
|
1209 | 1199 | m, args, msg = a |
|
1210 | repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m)) | |
|
1200 | repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m.__bytes__())) | |
|
1211 | 1201 | if f in fbids: |
|
1212 | 1202 | d = fbids[f] |
|
1213 | 1203 | if m in d: |
@@ -1228,13 +1218,15 b' def calculateupdates(' | |||
|
1228 | 1218 | repo.ui.debug(b" list of bids for %s:\n" % f) |
|
1229 | 1219 | for m, l in sorted(bids.items()): |
|
1230 | 1220 | for _f, args, msg in l: |
|
1231 | repo.ui.debug(b' %s -> %s\n' % (msg, m)) | |
|
1221 | repo.ui.debug(b' %s -> %s\n' % (msg, m.__bytes__())) | |
|
1232 | 1222 | # bids is a mapping from action method to list af actions |
|
1233 | 1223 | # Consensus? |
|
1234 | 1224 | if len(bids) == 1: # all bids are the same kind of method |
|
1235 | 1225 | m, l = list(bids.items())[0] |
|
1236 | 1226 | if all(a == l[0] for a in l[1:]): # len(bids) is > 1 |
|
1237 |
repo.ui.note( |
|
|
1227 | repo.ui.note( | |
|
1228 | _(b" %s: consensus for %s\n") % (f, m.__bytes__()) | |
|
1229 | ) | |
|
1238 | 1230 | mresult.addfile(f, *l[0]) |
|
1239 | 1231 | continue |
|
1240 | 1232 | # If keep is an option, just do it. |
@@ -1292,11 +1284,12 b' def calculateupdates(' | |||
|
1292 | 1284 | repo.ui.note(_(b' %s: multiple bids for merge action:\n') % f) |
|
1293 | 1285 | for m, l in sorted(bids.items()): |
|
1294 | 1286 | for _f, args, msg in l: |
|
1295 | repo.ui.note(b' %s -> %s\n' % (msg, m)) | |
|
1287 | repo.ui.note(b' %s -> %s\n' % (msg, m.__bytes__())) | |
|
1296 | 1288 | # Pick random action. TODO: Instead, prompt user when resolving |
|
1297 | 1289 | m, l = list(bids.items())[0] |
|
1298 | 1290 | repo.ui.warn( |
|
1299 |
_(b' %s: ambiguous merge - picked %s action\n') |
|
|
1291 | _(b' %s: ambiguous merge - picked %s action\n') | |
|
1292 | % (f, m.__bytes__()) | |
|
1300 | 1293 | ) |
|
1301 | 1294 | mresult.addfile(f, *l[0]) |
|
1302 | 1295 | continue |
@@ -1404,6 +1397,34 b' def batchget(repo, mctx, wctx, wantfiled' | |||
|
1404 | 1397 | atomictemp=atomictemp, |
|
1405 | 1398 | ) |
|
1406 | 1399 | if wantfiledata: |
|
1400 | # XXX note that there is a race window between the time we | |
|
1401 | # write the clean data into the file and we stats it. So another | |
|
1402 | # writing process meddling with the file content right after we | |
|
1403 | # wrote it could cause bad stat data to be gathered. | |
|
1404 | # | |
|
1405 | # They are 2 data we gather here | |
|
1406 | # - the mode: | |
|
1407 | # That we actually just wrote, we should not need to read | |
|
1408 | # it from disk, (except not all mode might have survived | |
|
1409 | # the disk round-trip, which is another issue: we should | |
|
1410 | # not depends on this) | |
|
1411 | # - the mtime, | |
|
1412 | # On system that support nanosecond precision, the mtime | |
|
1413 | # could be accurate enough to tell the two writes appart. | |
|
1414 | # However gathering it in a racy way make the mtime we | |
|
1415 | # gather "unreliable". | |
|
1416 | # | |
|
1417 | # (note: we get the size from the data we write, which is sane) | |
|
1418 | # | |
|
1419 | # So in theory the data returned here are fully racy, but in | |
|
1420 | # practice "it works mostly fine". | |
|
1421 | # | |
|
1422 | # Do not be surprised if you end up reading this while looking | |
|
1423 | # for the causes of some buggy status. Feel free to improve | |
|
1424 | # this in the future, but we cannot simply stop gathering | |
|
1425 | # information. Otherwise `hg status` call made after a large `hg | |
|
1426 | # update` runs would have to redo a similar amount of work to | |
|
1427 | # restore and compare all files content. | |
|
1407 | 1428 | s = wfctx.lstat() |
|
1408 | 1429 | mode = s.st_mode |
|
1409 | 1430 | mtime = timestamp.mtime_of(s) |
@@ -1495,7 +1516,8 b' def applyupdates(' | |||
|
1495 | 1516 | # mergestate so that it can be reused on commit |
|
1496 | 1517 | ms.addcommitinfo(f, op) |
|
1497 | 1518 | |
|
1498 |
num |
|
|
1519 | num_no_op = mresult.len(mergestatemod.MergeAction.NO_OP_ACTIONS) | |
|
1520 | numupdates = mresult.len() - num_no_op | |
|
1499 | 1521 | progress = repo.ui.makeprogress( |
|
1500 | 1522 | _(b'updating'), unit=_(b'files'), total=numupdates |
|
1501 | 1523 | ) |
@@ -1599,9 +1621,9 b' def applyupdates(' | |||
|
1599 | 1621 | progress.increment(item=f) |
|
1600 | 1622 | |
|
1601 | 1623 | # keep (noop, just log it) |
|
1602 | for a in mergestatemod.NO_OP_ACTIONS: | |
|
1624 | for a in mergestatemod.MergeAction.NO_OP_ACTIONS: | |
|
1603 | 1625 | for f, args, msg in mresult.getactions((a,), sort=True): |
|
1604 | repo.ui.debug(b" %s: %s -> %s\n" % (f, msg, a)) | |
|
1626 | repo.ui.debug(b" %s: %s -> %s\n" % (f, msg, a.__bytes__())) | |
|
1605 | 1627 | # no progress |
|
1606 | 1628 | |
|
1607 | 1629 | # directory rename, move local |
@@ -1690,10 +1712,8 b' def applyupdates(' | |||
|
1690 | 1712 | ) |
|
1691 | 1713 | |
|
1692 | 1714 | try: |
|
1693 | # premerge | |
|
1694 | tocomplete = [] | |
|
1695 | 1715 | for f, args, msg in mergeactions: |
|
1696 |
repo.ui.debug(b" %s: %s -> m |
|
|
1716 | repo.ui.debug(b" %s: %s -> m\n" % (f, msg)) | |
|
1697 | 1717 | ms.addcommitinfo(f, {b'merged': b'yes'}) |
|
1698 | 1718 | progress.increment(item=f) |
|
1699 | 1719 | if f == b'.hgsubstate': # subrepo states need updating |
@@ -1702,16 +1722,6 b' def applyupdates(' | |||
|
1702 | 1722 | ) |
|
1703 | 1723 | continue |
|
1704 | 1724 | wctx[f].audit() |
|
1705 | complete, r = ms.preresolve(f, wctx) | |
|
1706 | if not complete: | |
|
1707 | numupdates += 1 | |
|
1708 | tocomplete.append((f, args, msg)) | |
|
1709 | ||
|
1710 | # merge | |
|
1711 | for f, args, msg in tocomplete: | |
|
1712 | repo.ui.debug(b" %s: %s -> m (merge)\n" % (f, msg)) | |
|
1713 | ms.addcommitinfo(f, {b'merged': b'yes'}) | |
|
1714 | progress.increment(item=f, total=numupdates) | |
|
1715 | 1725 | ms.resolve(f, wctx) |
|
1716 | 1726 | |
|
1717 | 1727 | except error.InterventionRequired: |
@@ -1823,7 +1833,7 b' def _update(' | |||
|
1823 | 1833 | If false, merging with an ancestor (fast-forward) is only allowed |
|
1824 | 1834 | between different named branches. This flag is used by rebase extension |
|
1825 | 1835 | as a temporary fix and should be avoided in general. |
|
1826 |
labels = labels to use for |
|
|
1836 | labels = labels to use for local, other, and base | |
|
1827 | 1837 | mergeforce = whether the merge was run with 'merge --force' (deprecated): if |
|
1828 | 1838 | this is True, then 'force' should be True as well. |
|
1829 | 1839 | |
@@ -1875,22 +1885,11 b' def _update(' | |||
|
1875 | 1885 | # updatecheck='abort' to better suppport some of these callers. |
|
1876 | 1886 | if updatecheck is None: |
|
1877 | 1887 | updatecheck = UPDATECHECK_LINEAR |
|
1878 | if updatecheck not in ( | |
|
1879 | UPDATECHECK_NONE, | |
|
1880 | UPDATECHECK_LINEAR, | |
|
1881 | UPDATECHECK_NO_CONFLICT, | |
|
1882 | ): | |
|
1883 | raise ValueError( | |
|
1884 | r'Invalid updatecheck %r (can accept %r)' | |
|
1885 | % ( | |
|
1886 | updatecheck, | |
|
1887 | ( | |
|
1888 | UPDATECHECK_NONE, | |
|
1889 | UPDATECHECK_LINEAR, | |
|
1890 | UPDATECHECK_NO_CONFLICT, | |
|
1891 | ), | |
|
1892 | ) | |
|
1893 | ) | |
|
1888 | okay = (UPDATECHECK_NONE, UPDATECHECK_LINEAR, UPDATECHECK_NO_CONFLICT) | |
|
1889 | if updatecheck not in okay: | |
|
1890 | msg = r'Invalid updatecheck %r (can accept %r)' | |
|
1891 | msg %= (updatecheck, okay) | |
|
1892 | raise ValueError(msg) | |
|
1894 | 1893 | if wc is not None and wc.isinmemory(): |
|
1895 | 1894 | maybe_wlock = util.nullcontextmanager() |
|
1896 | 1895 | else: |
@@ -1919,29 +1918,22 b' def _update(' | |||
|
1919 | 1918 | raise error.StateError(_(b"outstanding uncommitted merge")) |
|
1920 | 1919 | ms = wc.mergestate() |
|
1921 | 1920 | if ms.unresolvedcount(): |
|
1922 | raise error.StateError( | |
|
1923 |
|
|
|
1924 | hint=_(b"use 'hg resolve' to resolve"), | |
|
1925 | ) | |
|
1921 | msg = _(b"outstanding merge conflicts") | |
|
1922 | hint = _(b"use 'hg resolve' to resolve") | |
|
1923 | raise error.StateError(msg, hint=hint) | |
|
1926 | 1924 | if branchmerge: |
|
1925 | m_a = _(b"merging with a working directory ancestor has no effect") | |
|
1927 | 1926 | if pas == [p2]: |
|
1928 | raise error.Abort( | |
|
1929 | _( | |
|
1930 | b"merging with a working directory ancestor" | |
|
1931 | b" has no effect" | |
|
1932 | ) | |
|
1933 | ) | |
|
1927 | raise error.Abort(m_a) | |
|
1934 | 1928 | elif pas == [p1]: |
|
1935 | 1929 | if not mergeancestor and wc.branch() == p2.branch(): |
|
1936 | raise error.Abort( | |
|
1937 |
|
|
|
1938 | hint=_(b"use 'hg update' or check 'hg heads'"), | |
|
1939 | ) | |
|
1930 | msg = _(b"nothing to merge") | |
|
1931 | hint = _(b"use 'hg update' or check 'hg heads'") | |
|
1932 | raise error.Abort(msg, hint=hint) | |
|
1940 | 1933 | if not force and (wc.files() or wc.deleted()): |
|
1941 | raise error.StateError( | |
|
1942 |
|
|
|
1943 | hint=_(b"use 'hg status' to list changes"), | |
|
1944 | ) | |
|
1934 | msg = _(b"uncommitted changes") | |
|
1935 | hint = _(b"use 'hg status' to list changes") | |
|
1936 | raise error.StateError(msg, hint=hint) | |
|
1945 | 1937 | if not wc.isinmemory(): |
|
1946 | 1938 | for s in sorted(wc.substate): |
|
1947 | 1939 | wc.sub(s).bailifchanged() |
@@ -2144,6 +2136,71 b' def _update(' | |||
|
2144 | 2136 | mresult.len((mergestatemod.ACTION_GET,)) if wantfiledata else 0 |
|
2145 | 2137 | ) |
|
2146 | 2138 | with repo.dirstate.parentchange(): |
|
2139 | ### Filter Filedata | |
|
2140 | # | |
|
2141 | # We gathered "cache" information for the clean file while | |
|
2142 | # updating them: mtime, size and mode. | |
|
2143 | # | |
|
2144 | # At the time this comment is written, they are various issues | |
|
2145 | # with how we gather the `mode` and `mtime` information (see | |
|
2146 | # the comment in `batchget`). | |
|
2147 | # | |
|
2148 | # We are going to smooth one of this issue here : mtime ambiguity. | |
|
2149 | # | |
|
2150 | # i.e. even if the mtime gathered during `batchget` was | |
|
2151 | # correct[1] a change happening right after it could change the | |
|
2152 | # content while keeping the same mtime[2]. | |
|
2153 | # | |
|
2154 | # When we reach the current code, the "on disk" part of the | |
|
2155 | # update operation is finished. We still assume that no other | |
|
2156 | # process raced that "on disk" part, but we want to at least | |
|
2157 | # prevent later file change to alter the content of the file | |
|
2158 | # right after the update operation. So quickly that the same | |
|
2159 | # mtime is record for the operation. | |
|
2160 | # To prevent such ambiguity to happens, we will only keep the | |
|
2161 | # "file data" for files with mtime that are stricly in the past, | |
|
2162 | # i.e. whose mtime is strictly lower than the current time. | |
|
2163 | # | |
|
2164 | # This protect us from race conditions from operation that could | |
|
2165 | # run right after this one, especially other Mercurial | |
|
2166 | # operation that could be waiting for the wlock to touch files | |
|
2167 | # content and the dirstate. | |
|
2168 | # | |
|
2169 | # In an ideal world, we could only get reliable information in | |
|
2170 | # `getfiledata` (from `getbatch`), however the current approach | |
|
2171 | # have been a successful compromise since many years. | |
|
2172 | # | |
|
2173 | # At the time this comment is written, not using any "cache" | |
|
2174 | # file data at all here would not be viable. As it would result is | |
|
2175 | # a very large amount of work (equivalent to the previous `hg | |
|
2176 | # update` during the next status after an update). | |
|
2177 | # | |
|
2178 | # [1] the current code cannot grantee that the `mtime` and | |
|
2179 | # `mode` are correct, but the result is "okay in practice". | |
|
2180 | # (see the comment in `batchget`). # | |
|
2181 | # | |
|
2182 | # [2] using nano-second precision can greatly help here because | |
|
2183 | # it makes the "different write with same mtime" issue | |
|
2184 | # virtually vanish. However, dirstate v1 cannot store such | |
|
2185 | # precision and a bunch of python-runtime, operating-system and | |
|
2186 | # filesystem does not provide use with such precision, so we | |
|
2187 | # have to operate as if it wasn't available. | |
|
2188 | if getfiledata: | |
|
2189 | ambiguous_mtime = {} | |
|
2190 | now = timestamp.get_fs_now(repo.vfs) | |
|
2191 | if now is None: | |
|
2192 | # we can't write to the FS, so we won't actually update | |
|
2193 | # the dirstate content anyway, no need to put cache | |
|
2194 | # information. | |
|
2195 | getfiledata = None | |
|
2196 | else: | |
|
2197 | now_sec = now[0] | |
|
2198 | for f, m in pycompat.iteritems(getfiledata): | |
|
2199 | if m is not None and m[2][0] >= now_sec: | |
|
2200 | ambiguous_mtime[f] = (m[0], m[1], None) | |
|
2201 | for f, m in pycompat.iteritems(ambiguous_mtime): | |
|
2202 | getfiledata[f] = m | |
|
2203 | ||
|
2147 | 2204 | repo.setparents(fp1, fp2) |
|
2148 | 2205 | mergestatemod.recordupdates( |
|
2149 | 2206 | repo, mresult.actionsdict, branchmerge, getfiledata |
@@ -2199,7 +2256,7 b' def update(ctx, updatecheck=None, wc=Non' | |||
|
2199 | 2256 | ctx.rev(), |
|
2200 | 2257 | branchmerge=False, |
|
2201 | 2258 | force=False, |
|
2202 | labels=[b'working copy', b'destination'], | |
|
2259 | labels=[b'working copy', b'destination', b'working copy parent'], | |
|
2203 | 2260 | updatecheck=updatecheck, |
|
2204 | 2261 | wc=wc, |
|
2205 | 2262 | ) |
@@ -2311,9 +2368,8 b' def graft(' | |||
|
2311 | 2368 | def back_out(ctx, parent=None, wc=None): |
|
2312 | 2369 | if parent is None: |
|
2313 | 2370 | if ctx.p2() is not None: |
|
2314 | raise error.ProgrammingError( | |
|
2315 | b"must specify parent of merge commit to back out" | |
|
2316 | ) | |
|
2371 | msg = b"must specify parent of merge commit to back out" | |
|
2372 | raise error.ProgrammingError(msg) | |
|
2317 | 2373 | parent = ctx.p1() |
|
2318 | 2374 | return _update( |
|
2319 | 2375 | ctx.repo(), |
@@ -2386,13 +2442,13 b' def purge(' | |||
|
2386 | 2442 | |
|
2387 | 2443 | if confirm: |
|
2388 | 2444 | nb_ignored = len(status.ignored) |
|
2389 | nb_unkown = len(status.unknown) | |
|
2390 | if nb_unkown and nb_ignored: | |
|
2391 | msg = _(b"permanently delete %d unkown and %d ignored files?") | |
|
2392 | msg %= (nb_unkown, nb_ignored) | |
|
2393 | elif nb_unkown: | |
|
2394 | msg = _(b"permanently delete %d unkown files?") | |
|
2395 | msg %= nb_unkown | |
|
2445 | nb_unknown = len(status.unknown) | |
|
2446 | if nb_unknown and nb_ignored: | |
|
2447 | msg = _(b"permanently delete %d unknown and %d ignored files?") | |
|
2448 | msg %= (nb_unknown, nb_ignored) | |
|
2449 | elif nb_unknown: | |
|
2450 | msg = _(b"permanently delete %d unknown files?") | |
|
2451 | msg %= nb_unknown | |
|
2396 | 2452 | elif nb_ignored: |
|
2397 | 2453 | msg = _(b"permanently delete %d ignored files?") |
|
2398 | 2454 | msg %= nb_ignored |
@@ -4,6 +4,7 b' import collections' | |||
|
4 | 4 | import errno |
|
5 | 5 | import shutil |
|
6 | 6 | import struct |
|
7 | import weakref | |
|
7 | 8 | |
|
8 | 9 | from .i18n import _ |
|
9 | 10 | from .node import ( |
@@ -97,36 +98,102 b" LEGACY_MERGE_DRIVER_STATE = b'm'" | |||
|
97 | 98 | # This record was release in 3.7 and usage was removed in 5.6 |
|
98 | 99 | LEGACY_MERGE_DRIVER_MERGE = b'D' |
|
99 | 100 | |
|
101 | CHANGE_ADDED = b'added' | |
|
102 | CHANGE_REMOVED = b'removed' | |
|
103 | CHANGE_MODIFIED = b'modified' | |
|
100 | 104 | |
|
101 | ACTION_FORGET = b'f' | |
|
102 | ACTION_REMOVE = b'r' | |
|
103 | ACTION_ADD = b'a' | |
|
104 | ACTION_GET = b'g' | |
|
105 | ACTION_PATH_CONFLICT = b'p' | |
|
106 | ACTION_PATH_CONFLICT_RESOLVE = b'pr' | |
|
107 | ACTION_ADD_MODIFIED = b'am' | |
|
108 | ACTION_CREATED = b'c' | |
|
109 | ACTION_DELETED_CHANGED = b'dc' | |
|
110 | ACTION_CHANGED_DELETED = b'cd' | |
|
111 | ACTION_MERGE = b'm' | |
|
112 | ACTION_LOCAL_DIR_RENAME_GET = b'dg' | |
|
113 | ACTION_DIR_RENAME_MOVE_LOCAL = b'dm' | |
|
114 | ACTION_KEEP = b'k' | |
|
105 | ||
|
106 | class MergeAction(object): | |
|
107 | """represent an "action" merge need to take for a given file | |
|
108 | ||
|
109 | Attributes: | |
|
110 | ||
|
111 | _short: internal representation used to identify each action | |
|
112 | ||
|
113 | no_op: True if the action does affect the file content or tracking status | |
|
114 | ||
|
115 | narrow_safe: | |
|
116 | True if the action can be safely used for a file outside of the narrow | |
|
117 | set | |
|
118 | ||
|
119 | changes: | |
|
120 | The types of changes that this actions involves. This is a work in | |
|
121 | progress and not all actions have one yet. In addition, some requires | |
|
122 | user changes and cannot be fully decided. The value currently available | |
|
123 | are: | |
|
124 | ||
|
125 | - ADDED: the files is new in both parents | |
|
126 | - REMOVED: the files existed in one parent and is getting removed | |
|
127 | - MODIFIED: the files existed in at least one parent and is getting changed | |
|
128 | """ | |
|
129 | ||
|
130 | ALL_ACTIONS = weakref.WeakSet() | |
|
131 | NO_OP_ACTIONS = weakref.WeakSet() | |
|
132 | ||
|
133 | def __init__(self, short, no_op=False, narrow_safe=False, changes=None): | |
|
134 | self._short = short | |
|
135 | self.ALL_ACTIONS.add(self) | |
|
136 | self.no_op = no_op | |
|
137 | if self.no_op: | |
|
138 | self.NO_OP_ACTIONS.add(self) | |
|
139 | self.narrow_safe = narrow_safe | |
|
140 | self.changes = changes | |
|
141 | ||
|
142 | def __hash__(self): | |
|
143 | return hash(self._short) | |
|
144 | ||
|
145 | def __repr__(self): | |
|
146 | return 'MergeAction<%s>' % self._short.decode('ascii') | |
|
147 | ||
|
148 | def __bytes__(self): | |
|
149 | return self._short | |
|
150 | ||
|
151 | def __eq__(self, other): | |
|
152 | if other is None: | |
|
153 | return False | |
|
154 | assert isinstance(other, MergeAction) | |
|
155 | return self._short == other._short | |
|
156 | ||
|
157 | def __lt__(self, other): | |
|
158 | return self._short < other._short | |
|
159 | ||
|
160 | ||
|
161 | ACTION_FORGET = MergeAction(b'f', narrow_safe=True, changes=CHANGE_REMOVED) | |
|
162 | ACTION_REMOVE = MergeAction(b'r', narrow_safe=True, changes=CHANGE_REMOVED) | |
|
163 | ACTION_ADD = MergeAction(b'a', narrow_safe=True, changes=CHANGE_ADDED) | |
|
164 | ACTION_GET = MergeAction(b'g', narrow_safe=True, changes=CHANGE_MODIFIED) | |
|
165 | ACTION_PATH_CONFLICT = MergeAction(b'p') | |
|
166 | ACTION_PATH_CONFLICT_RESOLVE = MergeAction('pr') | |
|
167 | ACTION_ADD_MODIFIED = MergeAction( | |
|
168 | b'am', narrow_safe=True, changes=CHANGE_ADDED | |
|
169 | ) # not 100% about the changes value here | |
|
170 | ACTION_CREATED = MergeAction(b'c', narrow_safe=True, changes=CHANGE_ADDED) | |
|
171 | ACTION_DELETED_CHANGED = MergeAction(b'dc') | |
|
172 | ACTION_CHANGED_DELETED = MergeAction(b'cd') | |
|
173 | ACTION_MERGE = MergeAction(b'm') | |
|
174 | ACTION_LOCAL_DIR_RENAME_GET = MergeAction(b'dg') | |
|
175 | ACTION_DIR_RENAME_MOVE_LOCAL = MergeAction(b'dm') | |
|
176 | ACTION_KEEP = MergeAction(b'k', no_op=True) | |
|
115 | 177 | # the file was absent on local side before merge and we should |
|
116 | 178 | # keep it absent (absent means file not present, it can be a result |
|
117 | 179 | # of file deletion, rename etc.) |
|
118 | ACTION_KEEP_ABSENT = b'ka' | |
|
180 | ACTION_KEEP_ABSENT = MergeAction(b'ka', no_op=True) | |
|
119 | 181 | # the file is absent on the ancestor and remote side of the merge |
|
120 | 182 | # hence this file is new and we should keep it |
|
121 | ACTION_KEEP_NEW = b'kn' | |
|
122 | ACTION_EXEC = b'e' | |
|
123 |
ACTION_CREATED_MERGE = |
|
|
183 | ACTION_KEEP_NEW = MergeAction(b'kn', no_op=True) | |
|
184 | ACTION_EXEC = MergeAction(b'e', narrow_safe=True, changes=CHANGE_MODIFIED) | |
|
185 | ACTION_CREATED_MERGE = MergeAction( | |
|
186 | b'cm', narrow_safe=True, changes=CHANGE_ADDED | |
|
187 | ) | |
|
188 | ||
|
124 | 189 | |
|
125 | # actions which are no op | |
|
126 | NO_OP_ACTIONS = ( | |
|
127 | ACTION_KEEP, | |
|
128 |
ACTION_ |
|
|
129 |
ACTION_ |
|
|
190 | # Used by concert to detect situation it does not like, not sure what the exact | |
|
191 | # criteria is | |
|
192 | CONVERT_MERGE_ACTIONS = ( | |
|
193 | ACTION_MERGE, | |
|
194 | ACTION_DIR_RENAME_MOVE_LOCAL, | |
|
195 | ACTION_CHANGED_DELETED, | |
|
196 | ACTION_DELETED_CHANGED, | |
|
130 | 197 | ) |
|
131 | 198 | |
|
132 | 199 | |
@@ -313,16 +380,15 b' class _mergestate_base(object):' | |||
|
313 | 380 | """return extras stored with the mergestate for the given filename""" |
|
314 | 381 | return self._stateextras[filename] |
|
315 | 382 | |
|
316 |
def |
|
|
317 |
"""r |
|
|
318 | Returns whether the merge was completed and the return value of merge | |
|
319 | obtained from filemerge._filemerge(). | |
|
320 | """ | |
|
383 | def resolve(self, dfile, wctx): | |
|
384 | """run merge process for dfile | |
|
385 | ||
|
386 | Returns the exit code of the merge.""" | |
|
321 | 387 | if self[dfile] in ( |
|
322 | 388 | MERGE_RECORD_RESOLVED, |
|
323 | 389 | LEGACY_RECORD_DRIVER_RESOLVED, |
|
324 | 390 | ): |
|
325 |
return |
|
|
391 | return 0 | |
|
326 | 392 | stateentry = self._state[dfile] |
|
327 | 393 | state, localkey, lfile, afile, anode, ofile, onode, flags = stateentry |
|
328 | 394 | octx = self._repo[self._other] |
@@ -341,7 +407,6 b' class _mergestate_base(object):' | |||
|
341 | 407 | fla = fca.flags() |
|
342 | 408 | if b'x' in flags + flo + fla and b'l' not in flags + flo + fla: |
|
343 | 409 | if fca.rev() == nullrev and flags != flo: |
|
344 | if preresolve: | |
|
345 | 410 |
|
|
346 | 411 |
|
|
347 | 412 |
|
@@ -351,13 +416,20 b' class _mergestate_base(object):' | |||
|
351 | 416 |
|
|
352 | 417 | elif flags == fla: |
|
353 | 418 | flags = flo |
|
354 | if preresolve: | |
|
355 | 419 |
|
|
356 | 420 |
|
|
357 | 421 |
|
|
358 | 422 |
|
|
359 | 423 |
|
|
360 | complete, merge_ret, deleted = filemerge.premerge( | |
|
424 | ||
|
425 | if not fco.cmp(fcd): # files identical? | |
|
426 | # If return value of merge is None, then there are no real conflict | |
|
427 | del self._state[dfile] | |
|
428 | self._results[dfile] = None, None | |
|
429 | self._dirty = True | |
|
430 | return None | |
|
431 | ||
|
432 | merge_ret, deleted = filemerge.filemerge( | |
|
361 | 433 |
|
|
362 | 434 |
|
|
363 | 435 |
|
@@ -367,25 +439,10 b' class _mergestate_base(object):' | |||
|
367 | 439 |
|
|
368 | 440 |
|
|
369 | 441 |
|
|
370 | else: | |
|
371 | complete, merge_ret, deleted = filemerge.filemerge( | |
|
372 | self._repo, | |
|
373 | wctx, | |
|
374 | self._local, | |
|
375 | lfile, | |
|
376 | fcd, | |
|
377 | fco, | |
|
378 | fca, | |
|
379 | labels=self._labels, | |
|
380 | ) | |
|
381 | if merge_ret is None: | |
|
382 | # If return value of merge is None, then there are no real conflict | |
|
383 | del self._state[dfile] | |
|
384 | self._dirty = True | |
|
385 | elif not merge_ret: | |
|
442 | ||
|
443 | if not merge_ret: | |
|
386 | 444 | self.mark(dfile, MERGE_RECORD_RESOLVED) |
|
387 | 445 | |
|
388 | if complete: | |
|
389 | 446 |
|
|
390 | 447 |
|
|
391 | 448 |
|
@@ -406,19 +463,7 b' class _mergestate_base(object):' | |||
|
406 | 463 |
|
|
407 | 464 |
|
|
408 | 465 | |
|
409 |
return |
|
|
410 | ||
|
411 | def preresolve(self, dfile, wctx): | |
|
412 | """run premerge process for dfile | |
|
413 | ||
|
414 | Returns whether the merge is complete, and the exit code.""" | |
|
415 | return self._resolve(True, dfile, wctx) | |
|
416 | ||
|
417 | def resolve(self, dfile, wctx): | |
|
418 | """run merge process (assuming premerge was run) for dfile | |
|
419 | ||
|
420 | Returns the exit code of the merge.""" | |
|
421 | return self._resolve(False, dfile, wctx)[1] | |
|
466 | return merge_ret | |
|
422 | 467 | |
|
423 | 468 | def counts(self): |
|
424 | 469 | """return counts for updated, merged and removed files in this |
@@ -109,6 +109,7 b' def validatepatterns(pats):' | |||
|
109 | 109 | and patterns that are loaded from sources that use the internal, |
|
110 | 110 | prefixed pattern representation (but can't necessarily be fully trusted). |
|
111 | 111 | """ |
|
112 | with util.timedcm('narrowspec.validatepatterns(pats size=%d)', len(pats)): | |
|
112 | 113 | if not isinstance(pats, set): |
|
113 | 114 | raise error.ProgrammingError( |
|
114 | 115 | b'narrow patterns should be a set; got %r' % pats |
@@ -323,7 +324,7 b' def updateworkingcopy(repo, assumeclean=' | |||
|
323 | 324 | removedmatch = matchmod.differencematcher(oldmatch, newmatch) |
|
324 | 325 | |
|
325 | 326 | ds = repo.dirstate |
|
326 | lookup, status = ds.status( | |
|
327 | lookup, status, _mtime_boundary = ds.status( | |
|
327 | 328 | removedmatch, subrepos=[], ignored=True, clean=True, unknown=True |
|
328 | 329 | ) |
|
329 | 330 | trackeddirty = status.modified + status.added |
@@ -73,10 +73,6 b' import errno' | |||
|
73 | 73 | import struct |
|
74 | 74 | |
|
75 | 75 | from .i18n import _ |
|
76 | from .node import ( | |
|
77 | bin, | |
|
78 | hex, | |
|
79 | ) | |
|
80 | 76 | from .pycompat import getattr |
|
81 | 77 | from .node import ( |
|
82 | 78 | bin, |
@@ -579,6 +575,12 b' class obsstore(object):' | |||
|
579 | 575 | return len(self._all) |
|
580 | 576 | |
|
581 | 577 | def __nonzero__(self): |
|
578 | from . import statichttprepo | |
|
579 | ||
|
580 | if isinstance(self.repo, statichttprepo.statichttprepository): | |
|
581 | # If repo is accessed via static HTTP, then we can't use os.stat() | |
|
582 | # to just peek at the file size. | |
|
583 | return len(self._data) > 1 | |
|
582 | 584 | if not self._cached('_all'): |
|
583 | 585 | try: |
|
584 | 586 | return self.svfs.stat(b'obsstore').st_size > 1 |
@@ -944,8 +946,7 b' def _computeobsoleteset(repo):' | |||
|
944 | 946 | getnode = repo.changelog.node |
|
945 | 947 | notpublic = _mutablerevs(repo) |
|
946 | 948 | isobs = repo.obsstore.successors.__contains__ |
|
947 |
|
|
|
948 | return obs | |
|
949 | return frozenset(r for r in notpublic if isobs(getnode(r))) | |
|
949 | 950 | |
|
950 | 951 | |
|
951 | 952 | @cachefor(b'orphan') |
@@ -963,14 +964,14 b' def _computeorphanset(repo):' | |||
|
963 | 964 | if p in obsolete or p in unstable: |
|
964 | 965 | unstable.add(r) |
|
965 | 966 | break |
|
966 | return unstable | |
|
967 | return frozenset(unstable) | |
|
967 | 968 | |
|
968 | 969 | |
|
969 | 970 | @cachefor(b'suspended') |
|
970 | 971 | def _computesuspendedset(repo): |
|
971 | 972 | """the set of obsolete parents with non obsolete descendants""" |
|
972 | 973 | suspended = repo.changelog.ancestors(getrevs(repo, b'orphan')) |
|
973 |
return |
|
|
974 | return frozenset(r for r in getrevs(repo, b'obsolete') if r in suspended) | |
|
974 | 975 | |
|
975 | 976 | |
|
976 | 977 | @cachefor(b'extinct') |
@@ -1002,7 +1003,7 b' def _computephasedivergentset(repo):' | |||
|
1002 | 1003 | # we have a public predecessor |
|
1003 | 1004 | bumped.add(rev) |
|
1004 | 1005 | break # Next draft! |
|
1005 | return bumped | |
|
1006 | return frozenset(bumped) | |
|
1006 | 1007 | |
|
1007 | 1008 | |
|
1008 | 1009 | @cachefor(b'contentdivergent') |
@@ -1029,7 +1030,7 b' def _computecontentdivergentset(repo):' | |||
|
1029 | 1030 | divergent.add(rev) |
|
1030 | 1031 | break |
|
1031 | 1032 | toprocess.update(obsstore.predecessors.get(prec, ())) |
|
1032 | return divergent | |
|
1033 | return frozenset(divergent) | |
|
1033 | 1034 | |
|
1034 | 1035 | |
|
1035 | 1036 | def makefoldid(relation, user): |
@@ -218,7 +218,7 b' def exclusivemarkers(repo, nodes):' | |||
|
218 | 218 | |
|
219 | 219 | or |
|
220 | 220 | |
|
221 | # (A0 rewritten as AX; AX rewritten as A1; AX is unkown locally) | |
|
221 | # (A0 rewritten as AX; AX rewritten as A1; AX is unknown locally) | |
|
222 | 222 | # |
|
223 | 223 | # <-1- A0 <-2- AX <-3- A1 # Marker "2,3" are exclusive to A1 |
|
224 | 224 |
@@ -55,6 +55,8 b' wordsplitter = re.compile(' | |||
|
55 | 55 | ) |
|
56 | 56 | |
|
57 | 57 | PatchError = error.PatchError |
|
58 | PatchParseError = error.PatchParseError | |
|
59 | PatchApplicationError = error.PatchApplicationError | |
|
58 | 60 | |
|
59 | 61 | # public functions |
|
60 | 62 | |
@@ -107,7 +109,9 b' def split(stream):' | |||
|
107 | 109 | def mimesplit(stream, cur): |
|
108 | 110 | def msgfp(m): |
|
109 | 111 | fp = stringio() |
|
112 | # pytype: disable=wrong-arg-types | |
|
110 | 113 | g = mail.Generator(fp, mangle_from_=False) |
|
114 | # pytype: enable=wrong-arg-types | |
|
111 | 115 | g.flatten(m) |
|
112 | 116 | fp.seek(0) |
|
113 | 117 | return fp |
@@ -553,7 +557,9 b' class workingbackend(fsbackend):' | |||
|
553 | 557 | if not self.repo.dirstate.get_entry(fname).any_tracked and self.exists( |
|
554 | 558 | fname |
|
555 | 559 | ): |
|
556 | raise PatchError(_(b'cannot patch %s: file is not tracked') % fname) | |
|
560 | raise PatchApplicationError( | |
|
561 | _(b'cannot patch %s: file is not tracked') % fname | |
|
562 | ) | |
|
557 | 563 | |
|
558 | 564 | def setfile(self, fname, data, mode, copysource): |
|
559 | 565 | self._checkknown(fname) |
@@ -637,7 +643,9 b' class repobackend(abstractbackend):' | |||
|
637 | 643 | |
|
638 | 644 | def _checkknown(self, fname): |
|
639 | 645 | if fname not in self.ctx: |
|
640 | raise PatchError(_(b'cannot patch %s: file is not tracked') % fname) | |
|
646 | raise PatchApplicationError( | |
|
647 | _(b'cannot patch %s: file is not tracked') % fname | |
|
648 | ) | |
|
641 | 649 | |
|
642 | 650 | def getfile(self, fname): |
|
643 | 651 | try: |
@@ -793,7 +801,7 b' class patchfile(object):' | |||
|
793 | 801 | |
|
794 | 802 | def apply(self, h): |
|
795 | 803 | if not h.complete(): |
|
796 | raise PatchError( | |
|
804 | raise PatchParseError( | |
|
797 | 805 | _(b"bad hunk #%d %s (%d %d %d %d)") |
|
798 | 806 | % (h.number, h.desc, len(h.a), h.lena, len(h.b), h.lenb) |
|
799 | 807 | ) |
@@ -1388,7 +1396,7 b' class hunk(object):' | |||
|
1388 | 1396 | def read_unified_hunk(self, lr): |
|
1389 | 1397 | m = unidesc.match(self.desc) |
|
1390 | 1398 | if not m: |
|
1391 | raise PatchError(_(b"bad hunk #%d") % self.number) | |
|
1399 | raise PatchParseError(_(b"bad hunk #%d") % self.number) | |
|
1392 | 1400 | self.starta, self.lena, self.startb, self.lenb = m.groups() |
|
1393 | 1401 | if self.lena is None: |
|
1394 | 1402 | self.lena = 1 |
@@ -1405,7 +1413,7 b' class hunk(object):' | |||
|
1405 | 1413 | lr, self.hunk, self.lena, self.lenb, self.a, self.b |
|
1406 | 1414 | ) |
|
1407 | 1415 | except error.ParseError as e: |
|
1408 | raise PatchError(_(b"bad hunk #%d: %s") % (self.number, e)) | |
|
1416 | raise PatchParseError(_(b"bad hunk #%d: %s") % (self.number, e)) | |
|
1409 | 1417 | # if we hit eof before finishing out the hunk, the last line will |
|
1410 | 1418 | # be zero length. Lets try to fix it up. |
|
1411 | 1419 | while len(self.hunk[-1]) == 0: |
@@ -1420,7 +1428,7 b' class hunk(object):' | |||
|
1420 | 1428 | self.desc = lr.readline() |
|
1421 | 1429 | m = contextdesc.match(self.desc) |
|
1422 | 1430 | if not m: |
|
1423 | raise PatchError(_(b"bad hunk #%d") % self.number) | |
|
1431 | raise PatchParseError(_(b"bad hunk #%d") % self.number) | |
|
1424 | 1432 | self.starta, aend = m.groups() |
|
1425 | 1433 | self.starta = int(self.starta) |
|
1426 | 1434 | if aend is None: |
@@ -1440,7 +1448,7 b' class hunk(object):' | |||
|
1440 | 1448 | elif l.startswith(b' '): |
|
1441 | 1449 | u = b' ' + s |
|
1442 | 1450 | else: |
|
1443 | raise PatchError( | |
|
1451 | raise PatchParseError( | |
|
1444 | 1452 | _(b"bad hunk #%d old text line %d") % (self.number, x) |
|
1445 | 1453 | ) |
|
1446 | 1454 | self.a.append(u) |
@@ -1454,7 +1462,7 b' class hunk(object):' | |||
|
1454 | 1462 | l = lr.readline() |
|
1455 | 1463 | m = contextdesc.match(l) |
|
1456 | 1464 | if not m: |
|
1457 | raise PatchError(_(b"bad hunk #%d") % self.number) | |
|
1465 | raise PatchParseError(_(b"bad hunk #%d") % self.number) | |
|
1458 | 1466 | self.startb, bend = m.groups() |
|
1459 | 1467 | self.startb = int(self.startb) |
|
1460 | 1468 | if bend is None: |
@@ -1487,7 +1495,7 b' class hunk(object):' | |||
|
1487 | 1495 | lr.push(l) |
|
1488 | 1496 | break |
|
1489 | 1497 | else: |
|
1490 | raise PatchError( | |
|
1498 | raise PatchParseError( | |
|
1491 | 1499 | _(b"bad hunk #%d old text line %d") % (self.number, x) |
|
1492 | 1500 | ) |
|
1493 | 1501 | self.b.append(s) |
@@ -1601,7 +1609,7 b' class binhunk(object):' | |||
|
1601 | 1609 | while True: |
|
1602 | 1610 | line = getline(lr, self.hunk) |
|
1603 | 1611 | if not line: |
|
1604 | raise PatchError( | |
|
1612 | raise PatchParseError( | |
|
1605 | 1613 | _(b'could not extract "%s" binary data') % self._fname |
|
1606 | 1614 | ) |
|
1607 | 1615 | if line.startswith(b'literal '): |
@@ -1622,14 +1630,14 b' class binhunk(object):' | |||
|
1622 | 1630 | try: |
|
1623 | 1631 | dec.append(util.b85decode(line[1:])[:l]) |
|
1624 | 1632 | except ValueError as e: |
|
1625 | raise PatchError( | |
|
1633 | raise PatchParseError( | |
|
1626 | 1634 | _(b'could not decode "%s" binary patch: %s') |
|
1627 | 1635 | % (self._fname, stringutil.forcebytestr(e)) |
|
1628 | 1636 | ) |
|
1629 | 1637 | line = getline(lr, self.hunk) |
|
1630 | 1638 | text = zlib.decompress(b''.join(dec)) |
|
1631 | 1639 | if len(text) != size: |
|
1632 | raise PatchError( | |
|
1640 | raise PatchParseError( | |
|
1633 | 1641 | _(b'"%s" length is %d bytes, should be %d') |
|
1634 | 1642 | % (self._fname, len(text), size) |
|
1635 | 1643 | ) |
@@ -1847,7 +1855,7 b' def parsepatch(originalchunks, maxcontex' | |||
|
1847 | 1855 | try: |
|
1848 | 1856 | p.transitions[state][newstate](p, data) |
|
1849 | 1857 | except KeyError: |
|
1850 | raise PatchError( | |
|
1858 | raise PatchParseError( | |
|
1851 | 1859 | b'unhandled transition: %s -> %s' % (state, newstate) |
|
1852 | 1860 | ) |
|
1853 | 1861 | state = newstate |
@@ -1874,7 +1882,7 b' def pathtransform(path, strip, prefix):' | |||
|
1874 | 1882 | ('a//b/', 'd/e/c') |
|
1875 | 1883 | >>> pathtransform(b'a/b/c', 3, b'') |
|
1876 | 1884 | Traceback (most recent call last): |
|
1877 | PatchError: unable to strip away 1 of 3 dirs from a/b/c | |
|
1885 | PatchApplicationError: unable to strip away 1 of 3 dirs from a/b/c | |
|
1878 | 1886 | """ |
|
1879 | 1887 | pathlen = len(path) |
|
1880 | 1888 | i = 0 |
@@ -1884,7 +1892,7 b' def pathtransform(path, strip, prefix):' | |||
|
1884 | 1892 | while count > 0: |
|
1885 | 1893 | i = path.find(b'/', i) |
|
1886 | 1894 | if i == -1: |
|
1887 | raise PatchError( | |
|
1895 | raise PatchApplicationError( | |
|
1888 | 1896 | _(b"unable to strip away %d of %d dirs from %s") |
|
1889 | 1897 | % (count, strip, path) |
|
1890 | 1898 | ) |
@@ -1947,7 +1955,7 b' def makepatchmeta(backend, afile_orig, b' | |||
|
1947 | 1955 | elif not nulla: |
|
1948 | 1956 | fname = afile |
|
1949 | 1957 | else: |
|
1950 | raise PatchError(_(b"undefined source and destination files")) | |
|
1958 | raise PatchParseError(_(b"undefined source and destination files")) | |
|
1951 | 1959 | |
|
1952 | 1960 | gp = patchmeta(fname) |
|
1953 | 1961 | if create: |
@@ -2097,7 +2105,7 b' def iterhunks(fp):' | |||
|
2097 | 2105 | gp.copy(), |
|
2098 | 2106 | ) |
|
2099 | 2107 | if not gitpatches: |
|
2100 | raise PatchError( | |
|
2108 | raise PatchParseError( | |
|
2101 | 2109 | _(b'failed to synchronize metadata for "%s"') % afile[2:] |
|
2102 | 2110 | ) |
|
2103 | 2111 | newfile = True |
@@ -2193,7 +2201,7 b' def applybindelta(binchunk, data):' | |||
|
2193 | 2201 | out += binchunk[i:offset_end] |
|
2194 | 2202 | i += cmd |
|
2195 | 2203 | else: |
|
2196 | raise PatchError(_(b'unexpected delta opcode 0')) | |
|
2204 | raise PatchApplicationError(_(b'unexpected delta opcode 0')) | |
|
2197 | 2205 | return out |
|
2198 | 2206 | |
|
2199 | 2207 | |
@@ -2270,7 +2278,7 b' def _applydiff(' | |||
|
2270 | 2278 | data, mode = store.getfile(gp.oldpath)[:2] |
|
2271 | 2279 | if data is None: |
|
2272 | 2280 | # This means that the old path does not exist |
|
2273 | raise PatchError( | |
|
2281 | raise PatchApplicationError( | |
|
2274 | 2282 | _(b"source file '%s' does not exist") % gp.oldpath |
|
2275 | 2283 | ) |
|
2276 | 2284 | if gp.mode: |
@@ -2283,7 +2291,7 b' def _applydiff(' | |||
|
2283 | 2291 | if gp.op in (b'ADD', b'RENAME', b'COPY') and backend.exists( |
|
2284 | 2292 | gp.path |
|
2285 | 2293 | ): |
|
2286 | raise PatchError( | |
|
2294 | raise PatchApplicationError( | |
|
2287 | 2295 | _( |
|
2288 | 2296 | b"cannot create %s: destination " |
|
2289 | 2297 | b"already exists" |
@@ -2365,7 +2373,7 b' def _externalpatch(ui, repo, patcher, pa' | |||
|
2365 | 2373 | scmutil.marktouched(repo, files, similarity) |
|
2366 | 2374 | code = fp.close() |
|
2367 | 2375 | if code: |
|
2368 | raise PatchError( | |
|
2376 | raise PatchApplicationError( | |
|
2369 | 2377 | _(b"patch command failed: %s") % procutil.explainexit(code) |
|
2370 | 2378 | ) |
|
2371 | 2379 | return fuzz |
@@ -2397,7 +2405,7 b' def patchbackend(' | |||
|
2397 | 2405 | files.update(backend.close()) |
|
2398 | 2406 | store.close() |
|
2399 | 2407 | if ret < 0: |
|
2400 | raise PatchError(_(b'patch failed to apply')) | |
|
2408 | raise PatchApplicationError(_(b'patch failed to apply')) | |
|
2401 | 2409 | return ret > 0 |
|
2402 | 2410 | |
|
2403 | 2411 |
@@ -79,20 +79,24 b' class pathauditor(object):' | |||
|
79 | 79 | return |
|
80 | 80 | # AIX ignores "/" at end of path, others raise EISDIR. |
|
81 | 81 | if util.endswithsep(path): |
|
82 | raise error.Abort(_(b"path ends in directory separator: %s") % path) | |
|
82 | raise error.InputError( | |
|
83 | _(b"path ends in directory separator: %s") % path | |
|
84 | ) | |
|
83 | 85 | parts = util.splitpath(path) |
|
84 | 86 | if ( |
|
85 | 87 | os.path.splitdrive(path)[0] |
|
86 | 88 | or _lowerclean(parts[0]) in (b'.hg', b'.hg.', b'') |
|
87 | 89 | or pycompat.ospardir in parts |
|
88 | 90 | ): |
|
89 | raise error.Abort(_(b"path contains illegal component: %s") % path) | |
|
91 | raise error.InputError( | |
|
92 | _(b"path contains illegal component: %s") % path | |
|
93 | ) | |
|
90 | 94 | # Windows shortname aliases |
|
91 | 95 | for p in parts: |
|
92 | 96 | if b"~" in p: |
|
93 | 97 | first, last = p.split(b"~", 1) |
|
94 | 98 | if last.isdigit() and first.upper() in [b"HG", b"HG8B6C"]: |
|
95 |
raise error. |
|
|
99 | raise error.InputError( | |
|
96 | 100 | _(b"path contains illegal component: %s") % path |
|
97 | 101 | ) |
|
98 | 102 | if b'.hg' in _lowerclean(path): |
@@ -101,7 +105,7 b' class pathauditor(object):' | |||
|
101 | 105 | if p in lparts[1:]: |
|
102 | 106 | pos = lparts.index(p) |
|
103 | 107 | base = os.path.join(*parts[:pos]) |
|
104 |
raise error. |
|
|
108 | raise error.InputError( | |
|
105 | 109 | _(b"path '%s' is inside nested repo %r") |
|
106 | 110 | % (path, pycompat.bytestr(base)) |
|
107 | 111 | ) |
@@ -104,6 +104,7 b' class DirstateItem(object):' | |||
|
104 | 104 | _mtime_ns = attr.ib() |
|
105 | 105 | _fallback_exec = attr.ib() |
|
106 | 106 | _fallback_symlink = attr.ib() |
|
107 | _mtime_second_ambiguous = attr.ib() | |
|
107 | 108 | |
|
108 | 109 | def __init__( |
|
109 | 110 | self, |
@@ -127,24 +128,27 b' class DirstateItem(object):' | |||
|
127 | 128 | self._size = None |
|
128 | 129 | self._mtime_s = None |
|
129 | 130 | self._mtime_ns = None |
|
131 | self._mtime_second_ambiguous = False | |
|
130 | 132 | if parentfiledata is None: |
|
131 | 133 | has_meaningful_mtime = False |
|
132 | 134 | has_meaningful_data = False |
|
135 | elif parentfiledata[2] is None: | |
|
136 | has_meaningful_mtime = False | |
|
133 | 137 | if has_meaningful_data: |
|
134 | 138 | self._mode = parentfiledata[0] |
|
135 | 139 | self._size = parentfiledata[1] |
|
136 | 140 | if has_meaningful_mtime: |
|
137 | self._mtime_s, self._mtime_ns = parentfiledata[2] | |
|
141 | ( | |
|
142 | self._mtime_s, | |
|
143 | self._mtime_ns, | |
|
144 | self._mtime_second_ambiguous, | |
|
145 | ) = parentfiledata[2] | |
|
138 | 146 | |
|
139 | 147 | @classmethod |
|
140 | 148 | def from_v2_data(cls, flags, size, mtime_s, mtime_ns): |
|
141 | 149 | """Build a new DirstateItem object from V2 data""" |
|
142 | 150 | has_mode_size = bool(flags & DIRSTATE_V2_HAS_MODE_AND_SIZE) |
|
143 | 151 | has_meaningful_mtime = bool(flags & DIRSTATE_V2_HAS_MTIME) |
|
144 | if flags & DIRSTATE_V2_MTIME_SECOND_AMBIGUOUS: | |
|
145 | # The current code is not able to do the more subtle comparison that the | |
|
146 | # MTIME_SECOND_AMBIGUOUS requires. So we ignore the mtime | |
|
147 | has_meaningful_mtime = False | |
|
148 | 152 | mode = None |
|
149 | 153 | |
|
150 | 154 | if flags & +DIRSTATE_V2_EXPECTED_STATE_IS_MODIFIED: |
@@ -171,13 +175,15 b' class DirstateItem(object):' | |||
|
171 | 175 | mode |= stat.S_IFLNK |
|
172 | 176 | else: |
|
173 | 177 | mode |= stat.S_IFREG |
|
178 | ||
|
179 | second_ambiguous = flags & DIRSTATE_V2_MTIME_SECOND_AMBIGUOUS | |
|
174 | 180 | return cls( |
|
175 | 181 | wc_tracked=bool(flags & DIRSTATE_V2_WDIR_TRACKED), |
|
176 | 182 | p1_tracked=bool(flags & DIRSTATE_V2_P1_TRACKED), |
|
177 | 183 | p2_info=bool(flags & DIRSTATE_V2_P2_INFO), |
|
178 | 184 | has_meaningful_data=has_mode_size, |
|
179 | 185 | has_meaningful_mtime=has_meaningful_mtime, |
|
180 | parentfiledata=(mode, size, (mtime_s, mtime_ns)), | |
|
186 | parentfiledata=(mode, size, (mtime_s, mtime_ns, second_ambiguous)), | |
|
181 | 187 | fallback_exec=fallback_exec, |
|
182 | 188 | fallback_symlink=fallback_symlink, |
|
183 | 189 | ) |
@@ -214,13 +220,13 b' class DirstateItem(object):' | |||
|
214 | 220 | wc_tracked=True, |
|
215 | 221 | p1_tracked=True, |
|
216 | 222 | has_meaningful_mtime=False, |
|
217 | parentfiledata=(mode, size, (42, 0)), | |
|
223 | parentfiledata=(mode, size, (42, 0, False)), | |
|
218 | 224 | ) |
|
219 | 225 | else: |
|
220 | 226 | return cls( |
|
221 | 227 | wc_tracked=True, |
|
222 | 228 | p1_tracked=True, |
|
223 | parentfiledata=(mode, size, (mtime, 0)), | |
|
229 | parentfiledata=(mode, size, (mtime, 0, False)), | |
|
224 | 230 | ) |
|
225 | 231 | else: |
|
226 | 232 | raise RuntimeError(b'unknown state: %s' % state) |
@@ -246,7 +252,7 b' class DirstateItem(object):' | |||
|
246 | 252 | self._p1_tracked = True |
|
247 | 253 | self._mode = mode |
|
248 | 254 | self._size = size |
|
249 | self._mtime_s, self._mtime_ns = mtime | |
|
255 | self._mtime_s, self._mtime_ns, self._mtime_second_ambiguous = mtime | |
|
250 | 256 | |
|
251 | 257 | def set_tracked(self): |
|
252 | 258 | """mark a file as tracked in the working copy |
@@ -301,10 +307,22 b' class DirstateItem(object):' | |||
|
301 | 307 | if self_sec is None: |
|
302 | 308 | return False |
|
303 | 309 | self_ns = self._mtime_ns |
|
304 | other_sec, other_ns = other_mtime | |
|
305 |
|
|
|
306 | self_ns == other_ns or self_ns == 0 or other_ns == 0 | |
|
307 | ) | |
|
310 | other_sec, other_ns, second_ambiguous = other_mtime | |
|
311 | if self_sec != other_sec: | |
|
312 | # seconds are different theses mtime are definitly not equal | |
|
313 | return False | |
|
314 | elif other_ns == 0 or self_ns == 0: | |
|
315 | # at least one side as no nano-seconds information | |
|
316 | ||
|
317 | if self._mtime_second_ambiguous: | |
|
318 | # We cannot trust the mtime in this case | |
|
319 | return False | |
|
320 | else: | |
|
321 | # the "seconds" value was reliable on its own. We are good to go. | |
|
322 | return True | |
|
323 | else: | |
|
324 | # We have nano second information, let us use them ! | |
|
325 | return self_ns == other_ns | |
|
308 | 326 | |
|
309 | 327 | @property |
|
310 | 328 | def state(self): |
@@ -463,6 +481,8 b' class DirstateItem(object):' | |||
|
463 | 481 | flags |= DIRSTATE_V2_MODE_IS_SYMLINK |
|
464 | 482 | if self._mtime_s is not None: |
|
465 | 483 | flags |= DIRSTATE_V2_HAS_MTIME |
|
484 | if self._mtime_second_ambiguous: | |
|
485 | flags |= DIRSTATE_V2_MTIME_SECOND_AMBIGUOUS | |
|
466 | 486 | |
|
467 | 487 | if self._fallback_exec is not None: |
|
468 | 488 | flags |= DIRSTATE_V2_HAS_FALLBACK_EXEC |
@@ -531,13 +551,11 b' class DirstateItem(object):' | |||
|
531 | 551 | return AMBIGUOUS_TIME |
|
532 | 552 | elif not self._p1_tracked: |
|
533 | 553 | return AMBIGUOUS_TIME |
|
554 | elif self._mtime_second_ambiguous: | |
|
555 | return AMBIGUOUS_TIME | |
|
534 | 556 | else: |
|
535 | 557 | return self._mtime_s |
|
536 | 558 | |
|
537 | def need_delay(self, now): | |
|
538 | """True if the stored mtime would be ambiguous with the current time""" | |
|
539 | return self.v1_state() == b'n' and self._mtime_s == now[0] | |
|
540 | ||
|
541 | 559 | |
|
542 | 560 | def gettype(q): |
|
543 | 561 | return int(q & 0xFFFF) |
@@ -566,18 +584,13 b' class BaseIndexObject(object):' | |||
|
566 | 584 | 0, |
|
567 | 585 | revlog_constants.COMP_MODE_INLINE, |
|
568 | 586 | revlog_constants.COMP_MODE_INLINE, |
|
587 | revlog_constants.RANK_UNKNOWN, | |
|
569 | 588 | ) |
|
570 | 589 | |
|
571 | 590 | @util.propertycache |
|
572 | 591 | def entry_size(self): |
|
573 | 592 | return self.index_format.size |
|
574 | 593 | |
|
575 | @property | |
|
576 | def nodemap(self): | |
|
577 | msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]" | |
|
578 | util.nouideprecwarn(msg, b'5.3', stacklevel=2) | |
|
579 | return self._nodemap | |
|
580 | ||
|
581 | 594 | @util.propertycache |
|
582 | 595 | def _nodemap(self): |
|
583 | 596 | nodemap = nodemaputil.NodeMap({sha1nodeconstants.nullid: nullrev}) |
@@ -629,7 +642,7 b' class BaseIndexObject(object):' | |||
|
629 | 642 | if not isinstance(i, int): |
|
630 | 643 | raise TypeError(b"expecting int indexes") |
|
631 | 644 | if i < 0 or i >= len(self): |
|
632 | raise IndexError | |
|
645 | raise IndexError(i) | |
|
633 | 646 | |
|
634 | 647 | def __getitem__(self, i): |
|
635 | 648 | if i == -1: |
@@ -653,6 +666,7 b' class BaseIndexObject(object):' | |||
|
653 | 666 | 0, |
|
654 | 667 | revlog_constants.COMP_MODE_INLINE, |
|
655 | 668 | revlog_constants.COMP_MODE_INLINE, |
|
669 | revlog_constants.RANK_UNKNOWN, | |
|
656 | 670 | ) |
|
657 | 671 | return r |
|
658 | 672 | |
@@ -785,9 +799,14 b' class InlinedIndexObject(BaseIndexObject' | |||
|
785 | 799 | return self._offsets[i] |
|
786 | 800 | |
|
787 | 801 | |
|
788 |
def parse_index2(data, inline, |
|
|
802 | def parse_index2(data, inline, format=revlog_constants.REVLOGV1): | |
|
803 | if format == revlog_constants.CHANGELOGV2: | |
|
804 | return parse_index_cl_v2(data) | |
|
789 | 805 | if not inline: |
|
790 | cls = IndexObject2 if revlogv2 else IndexObject | |
|
806 | if format == revlog_constants.REVLOGV2: | |
|
807 | cls = IndexObject2 | |
|
808 | else: | |
|
809 | cls = IndexObject | |
|
791 | 810 | return cls(data), None |
|
792 | 811 | cls = InlinedIndexObject |
|
793 | 812 | return cls(data, inline), (0, data) |
@@ -835,7 +854,7 b' class IndexObject2(IndexObject):' | |||
|
835 | 854 | entry = data[:10] |
|
836 | 855 | data_comp = data[10] & 3 |
|
837 | 856 | sidedata_comp = (data[10] & (3 << 2)) >> 2 |
|
838 | return entry + (data_comp, sidedata_comp) | |
|
857 | return entry + (data_comp, sidedata_comp, revlog_constants.RANK_UNKNOWN) | |
|
839 | 858 | |
|
840 | 859 | def _pack_entry(self, rev, entry): |
|
841 | 860 | data = entry[:10] |
@@ -860,20 +879,53 b' class IndexObject2(IndexObject):' | |||
|
860 | 879 | class IndexChangelogV2(IndexObject2): |
|
861 | 880 | index_format = revlog_constants.INDEX_ENTRY_CL_V2 |
|
862 | 881 | |
|
882 | null_item = ( | |
|
883 | IndexObject2.null_item[: revlog_constants.ENTRY_RANK] | |
|
884 | + (0,) # rank of null is 0 | |
|
885 | + IndexObject2.null_item[revlog_constants.ENTRY_RANK :] | |
|
886 | ) | |
|
887 | ||
|
863 | 888 | def _unpack_entry(self, rev, data, r=True): |
|
864 | 889 | items = self.index_format.unpack(data) |
|
865 | entry = items[:3] + (rev, rev) + items[3:8] | |
|
866 | data_comp = items[8] & 3 | |
|
867 | sidedata_comp = (items[8] >> 2) & 3 | |
|
868 | return entry + (data_comp, sidedata_comp) | |
|
890 | return ( | |
|
891 | items[revlog_constants.INDEX_ENTRY_V2_IDX_OFFSET], | |
|
892 | items[revlog_constants.INDEX_ENTRY_V2_IDX_COMPRESSED_LENGTH], | |
|
893 | items[revlog_constants.INDEX_ENTRY_V2_IDX_UNCOMPRESSED_LENGTH], | |
|
894 | rev, | |
|
895 | rev, | |
|
896 | items[revlog_constants.INDEX_ENTRY_V2_IDX_PARENT_1], | |
|
897 | items[revlog_constants.INDEX_ENTRY_V2_IDX_PARENT_2], | |
|
898 | items[revlog_constants.INDEX_ENTRY_V2_IDX_NODEID], | |
|
899 | items[revlog_constants.INDEX_ENTRY_V2_IDX_SIDEDATA_OFFSET], | |
|
900 | items[ | |
|
901 | revlog_constants.INDEX_ENTRY_V2_IDX_SIDEDATA_COMPRESSED_LENGTH | |
|
902 | ], | |
|
903 | items[revlog_constants.INDEX_ENTRY_V2_IDX_COMPRESSION_MODE] & 3, | |
|
904 | (items[revlog_constants.INDEX_ENTRY_V2_IDX_COMPRESSION_MODE] >> 2) | |
|
905 | & 3, | |
|
906 | items[revlog_constants.INDEX_ENTRY_V2_IDX_RANK], | |
|
907 | ) | |
|
869 | 908 | |
|
870 | 909 | def _pack_entry(self, rev, entry): |
|
871 | assert entry[3] == rev, entry[3] | |
|
872 | assert entry[4] == rev, entry[4] | |
|
873 | data = entry[:3] + entry[5:10] | |
|
874 | data_comp = entry[10] & 3 | |
|
875 | sidedata_comp = (entry[11] & 3) << 2 | |
|
876 | data += (data_comp | sidedata_comp,) | |
|
910 | ||
|
911 | base = entry[revlog_constants.ENTRY_DELTA_BASE] | |
|
912 | link_rev = entry[revlog_constants.ENTRY_LINK_REV] | |
|
913 | assert base == rev, (base, rev) | |
|
914 | assert link_rev == rev, (link_rev, rev) | |
|
915 | data = ( | |
|
916 | entry[revlog_constants.ENTRY_DATA_OFFSET], | |
|
917 | entry[revlog_constants.ENTRY_DATA_COMPRESSED_LENGTH], | |
|
918 | entry[revlog_constants.ENTRY_DATA_UNCOMPRESSED_LENGTH], | |
|
919 | entry[revlog_constants.ENTRY_PARENT_1], | |
|
920 | entry[revlog_constants.ENTRY_PARENT_2], | |
|
921 | entry[revlog_constants.ENTRY_NODE_ID], | |
|
922 | entry[revlog_constants.ENTRY_SIDEDATA_OFFSET], | |
|
923 | entry[revlog_constants.ENTRY_SIDEDATA_COMPRESSED_LENGTH], | |
|
924 | entry[revlog_constants.ENTRY_DATA_COMPRESSION_MODE] & 3 | |
|
925 | | (entry[revlog_constants.ENTRY_SIDEDATA_COMPRESSION_MODE] & 3) | |
|
926 | << 2, | |
|
927 | entry[revlog_constants.ENTRY_RANK], | |
|
928 | ) | |
|
877 | 929 | return self.index_format.pack(*data) |
|
878 | 930 | |
|
879 | 931 | |
@@ -903,23 +955,11 b' def parse_dirstate(dmap, copymap, st):' | |||
|
903 | 955 | return parents |
|
904 | 956 | |
|
905 | 957 | |
|
906 |
def pack_dirstate(dmap, copymap, pl |
|
|
958 | def pack_dirstate(dmap, copymap, pl): | |
|
907 | 959 | cs = stringio() |
|
908 | 960 | write = cs.write |
|
909 | 961 | write(b"".join(pl)) |
|
910 | 962 | for f, e in pycompat.iteritems(dmap): |
|
911 | if e.need_delay(now): | |
|
912 | # The file was last modified "simultaneously" with the current | |
|
913 | # write to dirstate (i.e. within the same second for file- | |
|
914 | # systems with a granularity of 1 sec). This commonly happens | |
|
915 | # for at least a couple of files on 'update'. | |
|
916 | # The user could change the file without changing its size | |
|
917 | # within the same second. Invalidate the file's mtime in | |
|
918 | # dirstate, forcing future 'status' calls to compare the | |
|
919 | # contents of the file if the size is the same. This prevents | |
|
920 | # mistakenly treating such files as clean. | |
|
921 | e.set_possibly_dirty() | |
|
922 | ||
|
923 | 963 | if f in copymap: |
|
924 | 964 | f = b"%s\0%s" % (f, copymap[f]) |
|
925 | 965 | e = _pack( |
@@ -7,11 +7,18 b'' | |||
|
7 | 7 | |
|
8 | 8 | from __future__ import absolute_import |
|
9 | 9 | |
|
10 | # obsolete experimental requirements: | |
|
11 | # - manifestv2: An experimental new manifest format that allowed | |
|
12 | # for stem compression of long paths. Experiment ended up not | |
|
13 | # being successful (repository sizes went up due to worse delta | |
|
14 | # chains), and the code was deleted in 4.6. | |
|
15 | ||
|
10 | 16 | GENERALDELTA_REQUIREMENT = b'generaldelta' |
|
11 | 17 | DOTENCODE_REQUIREMENT = b'dotencode' |
|
12 | 18 | STORE_REQUIREMENT = b'store' |
|
13 | 19 | FNCACHE_REQUIREMENT = b'fncache' |
|
14 | 20 | |
|
21 | DIRSTATE_TRACKED_HINT_V1 = b'dirstate-tracked-key-v1' | |
|
15 | 22 | DIRSTATE_V2_REQUIREMENT = b'dirstate-v2' |
|
16 | 23 | |
|
17 | 24 | # When narrowing is finalized and no longer subject to format changes, |
@@ -30,6 +37,9 b" TREEMANIFEST_REQUIREMENT = b'treemanifes" | |||
|
30 | 37 | |
|
31 | 38 | REVLOGV1_REQUIREMENT = b'revlogv1' |
|
32 | 39 | |
|
40 | # allow using ZSTD as compression engine for revlog content | |
|
41 | REVLOG_COMPRESSION_ZSTD = b'revlog-compression-zstd' | |
|
42 | ||
|
33 | 43 | # Increment the sub-version when the revlog v2 format changes to lock out old |
|
34 | 44 | # clients. |
|
35 | 45 | CHANGELOGV2_REQUIREMENT = b'exp-changelog-v2' |
@@ -66,6 +76,10 b" RELATIVE_SHARED_REQUIREMENT = b'relshare" | |||
|
66 | 76 | # `.hg/store/requires` are present. |
|
67 | 77 | SHARESAFE_REQUIREMENT = b'share-safe' |
|
68 | 78 | |
|
79 | # Bookmarks must be stored in the `store` part of the repository and will be | |
|
80 | # share accross shares | |
|
81 | BOOKMARKS_IN_STORE_REQUIREMENT = b'bookmarksinstore' | |
|
82 | ||
|
69 | 83 | # List of requirements which are working directory specific |
|
70 | 84 | # These requirements cannot be shared between repositories if they |
|
71 | 85 | # share the same store |
@@ -83,5 +97,25 b' WORKING_DIR_REQUIREMENTS = {' | |||
|
83 | 97 | SHARED_REQUIREMENT, |
|
84 | 98 | RELATIVE_SHARED_REQUIREMENT, |
|
85 | 99 | SHARESAFE_REQUIREMENT, |
|
100 | DIRSTATE_TRACKED_HINT_V1, | |
|
86 | 101 | DIRSTATE_V2_REQUIREMENT, |
|
87 | 102 | } |
|
103 | ||
|
104 | # List of requirement that impact "stream-clone" (and hardlink clone) and | |
|
105 | # cannot be changed in such cases. | |
|
106 | # | |
|
107 | # requirements not in this list are safe to be altered during stream-clone. | |
|
108 | # | |
|
109 | # note: the list is currently inherited from previous code and miss some relevant requirement while containing some irrelevant ones. | |
|
110 | STREAM_FIXED_REQUIREMENTS = { | |
|
111 | BOOKMARKS_IN_STORE_REQUIREMENT, | |
|
112 | CHANGELOGV2_REQUIREMENT, | |
|
113 | COPIESSDC_REQUIREMENT, | |
|
114 | GENERALDELTA_REQUIREMENT, | |
|
115 | INTERNAL_PHASE_REQUIREMENT, | |
|
116 | REVLOG_COMPRESSION_ZSTD, | |
|
117 | REVLOGV1_REQUIREMENT, | |
|
118 | REVLOGV2_REQUIREMENT, | |
|
119 | SPARSEREVLOG_REQUIREMENT, | |
|
120 | TREEMANIFEST_REQUIREMENT, | |
|
121 | } |
@@ -40,11 +40,13 b' from .revlogutils.constants import (' | |||
|
40 | 40 | COMP_MODE_DEFAULT, |
|
41 | 41 | COMP_MODE_INLINE, |
|
42 | 42 | COMP_MODE_PLAIN, |
|
43 | ENTRY_RANK, | |
|
43 | 44 | FEATURES_BY_VERSION, |
|
44 | 45 | FLAG_GENERALDELTA, |
|
45 | 46 | FLAG_INLINE_DATA, |
|
46 | 47 | INDEX_HEADER, |
|
47 | 48 | KIND_CHANGELOG, |
|
49 | RANK_UNKNOWN, | |
|
48 | 50 | REVLOGV0, |
|
49 | 51 | REVLOGV1, |
|
50 | 52 | REVLOGV1_FLAGS, |
@@ -101,6 +103,7 b' from .utils import (' | |||
|
101 | 103 | REVLOGV0 |
|
102 | 104 | REVLOGV1 |
|
103 | 105 | REVLOGV2 |
|
106 | CHANGELOGV2 | |
|
104 | 107 | FLAG_INLINE_DATA |
|
105 | 108 | FLAG_GENERALDELTA |
|
106 | 109 | REVLOG_DEFAULT_FLAGS |
@@ -199,16 +202,13 b' def parse_index_v1(data, inline):' | |||
|
199 | 202 | |
|
200 | 203 | def parse_index_v2(data, inline): |
|
201 | 204 | # call the C implementation to parse the index data |
|
202 |
index, cache = parsers.parse_index2(data, inline, |
|
|
205 | index, cache = parsers.parse_index2(data, inline, format=REVLOGV2) | |
|
203 | 206 | return index, cache |
|
204 | 207 | |
|
205 | 208 | |
|
206 | 209 | def parse_index_cl_v2(data, inline): |
|
207 | 210 | # call the C implementation to parse the index data |
|
208 | assert not inline | |
|
209 | from .pure.parsers import parse_index_cl_v2 | |
|
210 | ||
|
211 | index, cache = parse_index_cl_v2(data) | |
|
211 | index, cache = parsers.parse_index2(data, inline, format=CHANGELOGV2) | |
|
212 | 212 | return index, cache |
|
213 | 213 | |
|
214 | 214 | |
@@ -741,21 +741,6 b' class revlog(object):' | |||
|
741 | 741 | """iterate over all rev in this revlog (from start to stop)""" |
|
742 | 742 | return storageutil.iterrevs(len(self), start=start, stop=stop) |
|
743 | 743 | |
|
744 | @property | |
|
745 | def nodemap(self): | |
|
746 | msg = ( | |
|
747 | b"revlog.nodemap is deprecated, " | |
|
748 | b"use revlog.index.[has_node|rev|get_rev]" | |
|
749 | ) | |
|
750 | util.nouideprecwarn(msg, b'5.3', stacklevel=2) | |
|
751 | return self.index.nodemap | |
|
752 | ||
|
753 | @property | |
|
754 | def _nodecache(self): | |
|
755 | msg = b"revlog._nodecache is deprecated, use revlog.index.nodemap" | |
|
756 | util.nouideprecwarn(msg, b'5.3', stacklevel=2) | |
|
757 | return self.index.nodemap | |
|
758 | ||
|
759 | 744 | def hasnode(self, node): |
|
760 | 745 | try: |
|
761 | 746 | self.rev(node) |
@@ -870,7 +855,23 b' class revlog(object):' | |||
|
870 | 855 | if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0: |
|
871 | 856 | return self.rawsize(rev) |
|
872 | 857 | |
|
873 |
return len(self.revision(rev |
|
|
858 | return len(self.revision(rev)) | |
|
859 | ||
|
860 | def fast_rank(self, rev): | |
|
861 | """Return the rank of a revision if already known, or None otherwise. | |
|
862 | ||
|
863 | The rank of a revision is the size of the sub-graph it defines as a | |
|
864 | head. Equivalently, the rank of a revision `r` is the size of the set | |
|
865 | `ancestors(r)`, `r` included. | |
|
866 | ||
|
867 | This method returns the rank retrieved from the revlog in constant | |
|
868 | time. It makes no attempt at computing unknown values for versions of | |
|
869 | the revlog which do not persist the rank. | |
|
870 | """ | |
|
871 | rank = self.index[rev][ENTRY_RANK] | |
|
872 | if rank == RANK_UNKNOWN: | |
|
873 | return None | |
|
874 | return rank | |
|
874 | 875 | |
|
875 | 876 | def chainbase(self, rev): |
|
876 | 877 | base = self._chainbasecache.get(rev) |
@@ -1776,33 +1777,13 b' class revlog(object):' | |||
|
1776 | 1777 | |
|
1777 | 1778 | return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2)) |
|
1778 | 1779 | |
|
1779 | def _processflags(self, text, flags, operation, raw=False): | |
|
1780 | """deprecated entry point to access flag processors""" | |
|
1781 | msg = b'_processflag(...) use the specialized variant' | |
|
1782 | util.nouideprecwarn(msg, b'5.2', stacklevel=2) | |
|
1783 | if raw: | |
|
1784 | return text, flagutil.processflagsraw(self, text, flags) | |
|
1785 | elif operation == b'read': | |
|
1786 | return flagutil.processflagsread(self, text, flags) | |
|
1787 | else: # write operation | |
|
1788 | return flagutil.processflagswrite(self, text, flags) | |
|
1789 | ||
|
1790 | def revision(self, nodeorrev, _df=None, raw=False): | |
|
1780 | def revision(self, nodeorrev, _df=None): | |
|
1791 | 1781 | """return an uncompressed revision of a given node or revision |
|
1792 | 1782 | number. |
|
1793 | 1783 | |
|
1794 | 1784 | _df - an existing file handle to read from. (internal-only) |
|
1795 | raw - an optional argument specifying if the revision data is to be | |
|
1796 | treated as raw data when applying flag transforms. 'raw' should be set | |
|
1797 | to True when generating changegroups or in debug commands. | |
|
1798 | 1785 | """ |
|
1799 | if raw: | |
|
1800 | msg = ( | |
|
1801 | b'revlog.revision(..., raw=True) is deprecated, ' | |
|
1802 | b'use revlog.rawdata(...)' | |
|
1803 | ) | |
|
1804 | util.nouideprecwarn(msg, b'5.2', stacklevel=2) | |
|
1805 | return self._revisiondata(nodeorrev, _df, raw=raw) | |
|
1786 | return self._revisiondata(nodeorrev, _df) | |
|
1806 | 1787 | |
|
1807 | 1788 | def sidedata(self, nodeorrev, _df=None): |
|
1808 | 1789 | """a map of extra data related to the changeset but not part of the hash |
@@ -2479,6 +2460,19 b' class revlog(object):' | |||
|
2479 | 2460 | # than ones we manually add. |
|
2480 | 2461 | sidedata_offset = 0 |
|
2481 | 2462 | |
|
2463 | rank = RANK_UNKNOWN | |
|
2464 | if self._format_version == CHANGELOGV2: | |
|
2465 | if (p1r, p2r) == (nullrev, nullrev): | |
|
2466 | rank = 1 | |
|
2467 | elif p1r != nullrev and p2r == nullrev: | |
|
2468 | rank = 1 + self.fast_rank(p1r) | |
|
2469 | elif p1r == nullrev and p2r != nullrev: | |
|
2470 | rank = 1 + self.fast_rank(p2r) | |
|
2471 | else: # merge node | |
|
2472 | pmin, pmax = sorted((p1r, p2r)) | |
|
2473 | rank = 1 + self.fast_rank(pmax) | |
|
2474 | rank += sum(1 for _ in self.findmissingrevs([pmax], [pmin])) | |
|
2475 | ||
|
2482 | 2476 | e = revlogutils.entry( |
|
2483 | 2477 | flags=flags, |
|
2484 | 2478 | data_offset=offset, |
@@ -2493,6 +2487,7 b' class revlog(object):' | |||
|
2493 | 2487 | sidedata_offset=sidedata_offset, |
|
2494 | 2488 | sidedata_compressed_length=len(serialized_sidedata), |
|
2495 | 2489 | sidedata_compression_mode=sidedata_compression_mode, |
|
2490 | rank=rank, | |
|
2496 | 2491 | ) |
|
2497 | 2492 | |
|
2498 | 2493 | self.index.append(e) |
@@ -12,6 +12,7 b' from ..interfaces import repository' | |||
|
12 | 12 | |
|
13 | 13 | # See mercurial.revlogutils.constants for doc |
|
14 | 14 | COMP_MODE_INLINE = 2 |
|
15 | RANK_UNKNOWN = -1 | |
|
15 | 16 | |
|
16 | 17 | |
|
17 | 18 | def offset_type(offset, type): |
@@ -34,6 +35,7 b' def entry(' | |||
|
34 | 35 | sidedata_offset=0, |
|
35 | 36 | sidedata_compressed_length=0, |
|
36 | 37 | sidedata_compression_mode=COMP_MODE_INLINE, |
|
38 | rank=RANK_UNKNOWN, | |
|
37 | 39 | ): |
|
38 | 40 | """Build one entry from symbolic name |
|
39 | 41 | |
@@ -56,6 +58,7 b' def entry(' | |||
|
56 | 58 | sidedata_compressed_length, |
|
57 | 59 | data_compression_mode, |
|
58 | 60 | sidedata_compression_mode, |
|
61 | rank, | |
|
59 | 62 | ) |
|
60 | 63 | |
|
61 | 64 |
@@ -103,6 +103,17 b' ENTRY_DATA_COMPRESSION_MODE = 10' | |||
|
103 | 103 | # (see "COMP_MODE_*" constants for details) |
|
104 | 104 | ENTRY_SIDEDATA_COMPRESSION_MODE = 11 |
|
105 | 105 | |
|
106 | # [12] Revision rank: | |
|
107 | # The number of revision under this one. | |
|
108 | # | |
|
109 | # Formally this is defined as : rank(X) = len(ancestors(X) + X) | |
|
110 | # | |
|
111 | # If rank == -1; then we do not have this information available. | |
|
112 | # Only `null` has a rank of 0. | |
|
113 | ENTRY_RANK = 12 | |
|
114 | ||
|
115 | RANK_UNKNOWN = -1 | |
|
116 | ||
|
106 | 117 | ### main revlog header |
|
107 | 118 | |
|
108 | 119 | # We cannot rely on Struct.format is inconsistent for python <=3.6 versus above |
@@ -181,9 +192,20 b' assert INDEX_ENTRY_V2.size == 32 * 3, IN' | |||
|
181 | 192 | # 8 bytes: sidedata offset |
|
182 | 193 | # 4 bytes: sidedata compressed length |
|
183 | 194 | # 1 bytes: compression mode (2 lower bit are data_compression_mode) |
|
184 | # 27 bytes: Padding to align to 96 bytes (see RevlogV2Plan wiki page) | |
|
185 | INDEX_ENTRY_CL_V2 = struct.Struct(b">Qiiii20s12xQiB27x") | |
|
186 | assert INDEX_ENTRY_CL_V2.size == 32 * 3, INDEX_ENTRY_V2.size | |
|
195 | # 4 bytes: changeset rank (i.e. `len(::REV)`) | |
|
196 | # 23 bytes: Padding to align to 96 bytes (see RevlogV2Plan wiki page) | |
|
197 | INDEX_ENTRY_CL_V2 = struct.Struct(b">Qiiii20s12xQiBi23x") | |
|
198 | assert INDEX_ENTRY_CL_V2.size == 32 * 3, INDEX_ENTRY_CL_V2.size | |
|
199 | INDEX_ENTRY_V2_IDX_OFFSET = 0 | |
|
200 | INDEX_ENTRY_V2_IDX_COMPRESSED_LENGTH = 1 | |
|
201 | INDEX_ENTRY_V2_IDX_UNCOMPRESSED_LENGTH = 2 | |
|
202 | INDEX_ENTRY_V2_IDX_PARENT_1 = 3 | |
|
203 | INDEX_ENTRY_V2_IDX_PARENT_2 = 4 | |
|
204 | INDEX_ENTRY_V2_IDX_NODEID = 5 | |
|
205 | INDEX_ENTRY_V2_IDX_SIDEDATA_OFFSET = 6 | |
|
206 | INDEX_ENTRY_V2_IDX_SIDEDATA_COMPRESSED_LENGTH = 7 | |
|
207 | INDEX_ENTRY_V2_IDX_COMPRESSION_MODE = 8 | |
|
208 | INDEX_ENTRY_V2_IDX_RANK = 9 | |
|
187 | 209 | |
|
188 | 210 | # revlog index flags |
|
189 | 211 |
@@ -526,7 +526,7 b' def _textfromdelta(fh, revlog, baserev, ' | |||
|
526 | 526 | else: |
|
527 | 527 | # deltabase is rawtext before changed by flag processors, which is |
|
528 | 528 | # equivalent to non-raw text |
|
529 |
basetext = revlog.revision(baserev, _df=fh |
|
|
529 | basetext = revlog.revision(baserev, _df=fh) | |
|
530 | 530 | fulltext = mdiff.patch(basetext, delta) |
|
531 | 531 | |
|
532 | 532 | try: |
@@ -32,6 +32,7 b' REVIDX_DEFAULT_FLAGS' | |||
|
32 | 32 | REVIDX_FLAGS_ORDER |
|
33 | 33 | REVIDX_RAWTEXT_CHANGING_FLAGS |
|
34 | 34 | |
|
35 | # Keep this in sync with REVIDX_KNOWN_FLAGS in rust/hg-core/src/revlog/revlog.rs | |
|
35 | 36 | REVIDX_KNOWN_FLAGS = util.bitsfrom(REVIDX_FLAGS_ORDER) |
|
36 | 37 | |
|
37 | 38 | # Store flag processors (cf. 'addflagprocessor()' to register) |
@@ -16,6 +16,7 b' from ..node import hex' | |||
|
16 | 16 | |
|
17 | 17 | from .. import ( |
|
18 | 18 | error, |
|
19 | requirements, | |
|
19 | 20 | util, |
|
20 | 21 | ) |
|
21 | 22 | from . import docket as docket_mod |
@@ -34,6 +35,19 b' def test_race_hook_1():' | |||
|
34 | 35 | pass |
|
35 | 36 | |
|
36 | 37 | |
|
38 | def post_stream_cleanup(repo): | |
|
39 | """The stream clone might needs to remove some file if persisten nodemap | |
|
40 | was dropped while stream cloning | |
|
41 | """ | |
|
42 | if requirements.REVLOGV1_REQUIREMENT not in repo.requirements: | |
|
43 | return | |
|
44 | if requirements.NODEMAP_REQUIREMENT in repo.requirements: | |
|
45 | return | |
|
46 | unfi = repo.unfiltered() | |
|
47 | delete_nodemap(None, unfi, unfi.changelog) | |
|
48 | delete_nodemap(None, repo, unfi.manifestlog._rootstore._revlog) | |
|
49 | ||
|
50 | ||
|
37 | 51 | def persisted_data(revlog): |
|
38 | 52 | """read the nodemap for a revlog from disk""" |
|
39 | 53 | if revlog._nodemap_file is None: |
@@ -144,10 +158,12 b' def update_persistent_nodemap(revlog):' | |||
|
144 | 158 | |
|
145 | 159 | def delete_nodemap(tr, repo, revlog): |
|
146 | 160 | """Delete nodemap data on disk for a given revlog""" |
|
147 | if revlog._nodemap_file is None: | |
|
148 | msg = "calling persist nodemap on a revlog without the feature enabled" | |
|
149 | raise error.ProgrammingError(msg) | |
|
150 | repo.svfs.tryunlink(revlog._nodemap_file) | |
|
161 | prefix = revlog.radix | |
|
162 | pattern = re.compile(br"(^|/)%s(-[0-9a-f]+\.nd|\.n(\.a)?)$" % prefix) | |
|
163 | dirpath = revlog.opener.dirname(revlog._indexfile) | |
|
164 | for f in revlog.opener.listdir(dirpath): | |
|
165 | if pattern.match(f): | |
|
166 | repo.svfs.tryunlink(f) | |
|
151 | 167 | |
|
152 | 168 | |
|
153 | 169 | def persist_nodemap(tr, revlog, pending=False, force=False): |
@@ -47,12 +47,6 b' class revlogoldindex(list):' | |||
|
47 | 47 | node_id=sha1nodeconstants.nullid, |
|
48 | 48 | ) |
|
49 | 49 | |
|
50 | @property | |
|
51 | def nodemap(self): | |
|
52 | msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]" | |
|
53 | util.nouideprecwarn(msg, b'5.3', stacklevel=2) | |
|
54 | return self._nodemap | |
|
55 | ||
|
56 | 50 | @util.propertycache |
|
57 | 51 | def _nodemap(self): |
|
58 | 52 | nodemap = nodemaputil.NodeMap({sha1nodeconstants.nullid: node.nullrev}) |
@@ -180,6 +180,8 b' def callcatch(ui, func):' | |||
|
180 | 180 | ) |
|
181 | 181 | ) |
|
182 | 182 | except error.RepoError as inst: |
|
183 | if isinstance(inst, error.RepoLookupError): | |
|
184 | detailed_exit_code = 10 | |
|
183 | 185 | ui.error(_(b"abort: %s\n") % inst) |
|
184 | 186 | if inst.hint: |
|
185 | 187 | ui.error(_(b"(%s)\n") % inst.hint) |
@@ -341,13 +343,13 b' class casecollisionauditor(object):' | |||
|
341 | 343 | if fl in self._loweredfiles and f not in self._dirstate: |
|
342 | 344 | msg = _(b'possible case-folding collision for %s') % f |
|
343 | 345 | if self._abort: |
|
344 |
raise error. |
|
|
346 | raise error.StateError(msg) | |
|
345 | 347 | self._ui.warn(_(b"warning: %s\n") % msg) |
|
346 | 348 | self._loweredfiles.add(fl) |
|
347 | 349 | self._newfiles.add(f) |
|
348 | 350 | |
|
349 | 351 | |
|
350 | def filteredhash(repo, maxrev): | |
|
352 | def filteredhash(repo, maxrev, needobsolete=False): | |
|
351 | 353 | """build hash of filtered revisions in the current repoview. |
|
352 | 354 | |
|
353 | 355 | Multiple caches perform up-to-date validation by checking that the |
@@ -356,22 +358,31 b' def filteredhash(repo, maxrev):' | |||
|
356 | 358 | of revisions in the view may change without the repository tiprev and |
|
357 | 359 | tipnode changing. |
|
358 | 360 | |
|
359 |
This function hashes all the revs filtered from the view |
|
|
360 | that SHA-1 digest. | |
|
361 | This function hashes all the revs filtered from the view (and, optionally, | |
|
362 | all obsolete revs) up to maxrev and returns that SHA-1 digest. | |
|
361 | 363 | """ |
|
362 | 364 | cl = repo.changelog |
|
365 | if needobsolete: | |
|
366 | obsrevs = obsolete.getrevs(repo, b'obsolete') | |
|
367 | if not cl.filteredrevs and not obsrevs: | |
|
368 | return None | |
|
369 | key = (maxrev, hash(cl.filteredrevs), hash(obsrevs)) | |
|
370 | else: | |
|
363 | 371 | if not cl.filteredrevs: |
|
364 | 372 | return None |
|
365 | key = cl._filteredrevs_hashcache.get(maxrev) | |
|
366 | if not key: | |
|
367 | revs = sorted(r for r in cl.filteredrevs if r <= maxrev) | |
|
373 | key = maxrev | |
|
374 | obsrevs = frozenset() | |
|
375 | ||
|
376 | result = cl._filteredrevs_hashcache.get(key) | |
|
377 | if not result: | |
|
378 | revs = sorted(r for r in cl.filteredrevs | obsrevs if r <= maxrev) | |
|
368 | 379 | if revs: |
|
369 | 380 | s = hashutil.sha1() |
|
370 | 381 | for rev in revs: |
|
371 | 382 | s.update(b'%d;' % rev) |
|
372 |
|
|
|
373 |
cl._filteredrevs_hashcache[ |
|
|
374 |
return |
|
|
383 | result = s.digest() | |
|
384 | cl._filteredrevs_hashcache[key] = result | |
|
385 | return result | |
|
375 | 386 | |
|
376 | 387 | |
|
377 | 388 | def walkrepos(path, followsym=False, seen_dirs=None, recurse=False): |
@@ -2195,6 +2206,9 b' def unhidehashlikerevs(repo, specs, hidd' | |||
|
2195 | 2206 | |
|
2196 | 2207 | returns a repo object with the required changesets unhidden |
|
2197 | 2208 | """ |
|
2209 | if not specs: | |
|
2210 | return repo | |
|
2211 | ||
|
2198 | 2212 | if not repo.filtername or not repo.ui.configbool( |
|
2199 | 2213 | b'experimental', b'directaccess' |
|
2200 | 2214 | ): |
@@ -1000,7 +1000,11 b' def _rebaserestoredcommit(' | |||
|
1000 | 1000 | stats = merge.graft( |
|
1001 | 1001 | repo, |
|
1002 | 1002 | shelvectx, |
|
1003 | labels=[b'working-copy', b'shelve'], | |
|
1003 | labels=[ | |
|
1004 | b'working-copy', | |
|
1005 | b'shelved change', | |
|
1006 | b'parent of shelved change', | |
|
1007 | ], | |
|
1004 | 1008 | keepconflictparent=True, |
|
1005 | 1009 | ) |
|
1006 | 1010 | if stats.unresolvedcount: |
@@ -19,20 +19,14 b'' | |||
|
19 | 19 | from __future__ import absolute_import |
|
20 | 20 | |
|
21 | 21 | from .i18n import _ |
|
22 | from .node import nullrev | |
|
23 | 22 | from . import ( |
|
24 | 23 | error, |
|
25 | 24 | mdiff, |
|
26 | 25 | pycompat, |
|
27 | util, | |
|
28 | 26 | ) |
|
29 | 27 | from .utils import stringutil |
|
30 | 28 | |
|
31 | 29 | |
|
32 | class CantReprocessAndShowBase(Exception): | |
|
33 | pass | |
|
34 | ||
|
35 | ||
|
36 | 30 | def intersect(ra, rb): |
|
37 | 31 | """Given two ranges return the range where they intersect or None. |
|
38 | 32 | |
@@ -89,72 +83,6 b' class Merge3Text(object):' | |||
|
89 | 83 | self.a = a |
|
90 | 84 | self.b = b |
|
91 | 85 | |
|
92 | def merge_lines( | |
|
93 | self, | |
|
94 | name_a=None, | |
|
95 | name_b=None, | |
|
96 | name_base=None, | |
|
97 | start_marker=b'<<<<<<<', | |
|
98 | mid_marker=b'=======', | |
|
99 | end_marker=b'>>>>>>>', | |
|
100 | base_marker=None, | |
|
101 | localorother=None, | |
|
102 | minimize=False, | |
|
103 | ): | |
|
104 | """Return merge in cvs-like form.""" | |
|
105 | self.conflicts = False | |
|
106 | newline = b'\n' | |
|
107 | if len(self.a) > 0: | |
|
108 | if self.a[0].endswith(b'\r\n'): | |
|
109 | newline = b'\r\n' | |
|
110 | elif self.a[0].endswith(b'\r'): | |
|
111 | newline = b'\r' | |
|
112 | if name_a and start_marker: | |
|
113 | start_marker = start_marker + b' ' + name_a | |
|
114 | if name_b and end_marker: | |
|
115 | end_marker = end_marker + b' ' + name_b | |
|
116 | if name_base and base_marker: | |
|
117 | base_marker = base_marker + b' ' + name_base | |
|
118 | merge_regions = self.merge_regions() | |
|
119 | if minimize: | |
|
120 | merge_regions = self.minimize(merge_regions) | |
|
121 | for t in merge_regions: | |
|
122 | what = t[0] | |
|
123 | if what == b'unchanged': | |
|
124 | for i in range(t[1], t[2]): | |
|
125 | yield self.base[i] | |
|
126 | elif what == b'a' or what == b'same': | |
|
127 | for i in range(t[1], t[2]): | |
|
128 | yield self.a[i] | |
|
129 | elif what == b'b': | |
|
130 | for i in range(t[1], t[2]): | |
|
131 | yield self.b[i] | |
|
132 | elif what == b'conflict': | |
|
133 | if localorother == b'local': | |
|
134 | for i in range(t[3], t[4]): | |
|
135 | yield self.a[i] | |
|
136 | elif localorother == b'other': | |
|
137 | for i in range(t[5], t[6]): | |
|
138 | yield self.b[i] | |
|
139 | else: | |
|
140 | self.conflicts = True | |
|
141 | if start_marker is not None: | |
|
142 | yield start_marker + newline | |
|
143 | for i in range(t[3], t[4]): | |
|
144 | yield self.a[i] | |
|
145 | if base_marker is not None: | |
|
146 | yield base_marker + newline | |
|
147 | for i in range(t[1], t[2]): | |
|
148 | yield self.base[i] | |
|
149 | if mid_marker is not None: | |
|
150 | yield mid_marker + newline | |
|
151 | for i in range(t[5], t[6]): | |
|
152 | yield self.b[i] | |
|
153 | if end_marker is not None: | |
|
154 | yield end_marker + newline | |
|
155 | else: | |
|
156 | raise ValueError(what) | |
|
157 | ||
|
158 | 86 | def merge_groups(self): |
|
159 | 87 | """Yield sequence of line groups. Each one is a tuple: |
|
160 | 88 | |
@@ -170,7 +98,7 b' class Merge3Text(object):' | |||
|
170 | 98 | 'b', lines |
|
171 | 99 | Lines taken from b |
|
172 | 100 | |
|
173 | 'conflict', base_lines, a_lines, b_lines | |
|
101 | 'conflict', (base_lines, a_lines, b_lines) | |
|
174 | 102 | Lines from base were changed to either a or b and conflict. |
|
175 | 103 | """ |
|
176 | 104 | for t in self.merge_regions(): |
@@ -184,9 +112,11 b' class Merge3Text(object):' | |||
|
184 | 112 | elif what == b'conflict': |
|
185 | 113 | yield ( |
|
186 | 114 | what, |
|
115 | ( | |
|
187 | 116 | self.base[t[1] : t[2]], |
|
188 | 117 | self.a[t[3] : t[4]], |
|
189 | 118 | self.b[t[5] : t[6]], |
|
119 | ), | |
|
190 | 120 | ) |
|
191 | 121 | else: |
|
192 | 122 | raise ValueError(what) |
@@ -280,67 +210,6 b' class Merge3Text(object):' | |||
|
280 | 210 | ia = aend |
|
281 | 211 | ib = bend |
|
282 | 212 | |
|
283 | def minimize(self, merge_regions): | |
|
284 | """Trim conflict regions of lines where A and B sides match. | |
|
285 | ||
|
286 | Lines where both A and B have made the same changes at the beginning | |
|
287 | or the end of each merge region are eliminated from the conflict | |
|
288 | region and are instead considered the same. | |
|
289 | """ | |
|
290 | for region in merge_regions: | |
|
291 | if region[0] != b"conflict": | |
|
292 | yield region | |
|
293 | continue | |
|
294 | # pytype thinks this tuple contains only 3 things, but | |
|
295 | # that's clearly not true because this code successfully | |
|
296 | # executes. It might be wise to rework merge_regions to be | |
|
297 | # some kind of attrs type. | |
|
298 | ( | |
|
299 | issue, | |
|
300 | z1, | |
|
301 | z2, | |
|
302 | a1, | |
|
303 | a2, | |
|
304 | b1, | |
|
305 | b2, | |
|
306 | ) = region # pytype: disable=bad-unpacking | |
|
307 | alen = a2 - a1 | |
|
308 | blen = b2 - b1 | |
|
309 | ||
|
310 | # find matches at the front | |
|
311 | ii = 0 | |
|
312 | while ( | |
|
313 | ii < alen and ii < blen and self.a[a1 + ii] == self.b[b1 + ii] | |
|
314 | ): | |
|
315 | ii += 1 | |
|
316 | startmatches = ii | |
|
317 | ||
|
318 | # find matches at the end | |
|
319 | ii = 0 | |
|
320 | while ( | |
|
321 | ii < alen | |
|
322 | and ii < blen | |
|
323 | and self.a[a2 - ii - 1] == self.b[b2 - ii - 1] | |
|
324 | ): | |
|
325 | ii += 1 | |
|
326 | endmatches = ii | |
|
327 | ||
|
328 | if startmatches > 0: | |
|
329 | yield b'same', a1, a1 + startmatches | |
|
330 | ||
|
331 | yield ( | |
|
332 | b'conflict', | |
|
333 | z1, | |
|
334 | z2, | |
|
335 | a1 + startmatches, | |
|
336 | a2 - endmatches, | |
|
337 | b1 + startmatches, | |
|
338 | b2 - endmatches, | |
|
339 | ) | |
|
340 | ||
|
341 | if endmatches > 0: | |
|
342 | yield b'same', a2 - endmatches, a2 | |
|
343 | ||
|
344 | 213 | def find_sync_regions(self): |
|
345 | 214 | """Return a list of sync regions, where both descendants match the base. |
|
346 | 215 | |
@@ -403,39 +272,136 b' class Merge3Text(object):' | |||
|
403 | 272 | return sl |
|
404 | 273 | |
|
405 | 274 | |
|
406 |
def _verifytext( |
|
|
275 | def _verifytext(input): | |
|
407 | 276 | """verifies that text is non-binary (unless opts[text] is passed, |
|
408 | 277 | then we just warn)""" |
|
409 | if stringutil.binary(text): | |
|
410 | msg = _(b"%s looks like a binary file.") % path | |
|
411 | if not opts.get('quiet'): | |
|
412 | ui.warn(_(b'warning: %s\n') % msg) | |
|
413 | if not opts.get('text'): | |
|
278 | if stringutil.binary(input.text()): | |
|
279 | msg = _(b"%s looks like a binary file.") % input.fctx.path() | |
|
414 | 280 |
|
|
415 | return text | |
|
281 | ||
|
282 | ||
|
283 | def _format_labels(*inputs): | |
|
284 | pad = max(len(input.label) if input.label else 0 for input in inputs) | |
|
285 | labels = [] | |
|
286 | for input in inputs: | |
|
287 | if input.label: | |
|
288 | if input.label_detail: | |
|
289 | label = ( | |
|
290 | (input.label + b':').ljust(pad + 1) | |
|
291 | + b' ' | |
|
292 | + input.label_detail | |
|
293 | ) | |
|
294 | else: | |
|
295 | label = input.label | |
|
296 | # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ') | |
|
297 | labels.append(stringutil.ellipsis(label, 80 - 8)) | |
|
298 | else: | |
|
299 | labels.append(None) | |
|
300 | return labels | |
|
301 | ||
|
302 | ||
|
303 | def _detect_newline(m3): | |
|
304 | if len(m3.a) > 0: | |
|
305 | if m3.a[0].endswith(b'\r\n'): | |
|
306 | return b'\r\n' | |
|
307 | elif m3.a[0].endswith(b'\r'): | |
|
308 | return b'\r' | |
|
309 | return b'\n' | |
|
416 | 310 | |
|
417 | 311 | |
|
418 | def _picklabels(defaults, overrides): | |
|
419 | if len(overrides) > 3: | |
|
420 | raise error.Abort(_(b"can only specify three labels.")) | |
|
421 | result = defaults[:] | |
|
422 | for i, override in enumerate(overrides): | |
|
423 | result[i] = override | |
|
424 | return result | |
|
312 | def _minimize(a_lines, b_lines): | |
|
313 | """Trim conflict regions of lines where A and B sides match. | |
|
314 | ||
|
315 | Lines where both A and B have made the same changes at the beginning | |
|
316 | or the end of each merge region are eliminated from the conflict | |
|
317 | region and are instead considered the same. | |
|
318 | """ | |
|
319 | alen = len(a_lines) | |
|
320 | blen = len(b_lines) | |
|
321 | ||
|
322 | # find matches at the front | |
|
323 | ii = 0 | |
|
324 | while ii < alen and ii < blen and a_lines[ii] == b_lines[ii]: | |
|
325 | ii += 1 | |
|
326 | startmatches = ii | |
|
327 | ||
|
328 | # find matches at the end | |
|
329 | ii = 0 | |
|
330 | while ii < alen and ii < blen and a_lines[-ii - 1] == b_lines[-ii - 1]: | |
|
331 | ii += 1 | |
|
332 | endmatches = ii | |
|
333 | ||
|
334 | lines_before = a_lines[:startmatches] | |
|
335 | new_a_lines = a_lines[startmatches : alen - endmatches] | |
|
336 | new_b_lines = b_lines[startmatches : blen - endmatches] | |
|
337 | lines_after = a_lines[alen - endmatches :] | |
|
338 | return lines_before, new_a_lines, new_b_lines, lines_after | |
|
425 | 339 | |
|
426 | 340 | |
|
427 | def is_not_null(ctx): | |
|
428 | if not util.safehasattr(ctx, "node"): | |
|
429 | return False | |
|
430 | return ctx.rev() != nullrev | |
|
341 | def render_minimized( | |
|
342 | m3, | |
|
343 | name_a=None, | |
|
344 | name_b=None, | |
|
345 | start_marker=b'<<<<<<<', | |
|
346 | mid_marker=b'=======', | |
|
347 | end_marker=b'>>>>>>>', | |
|
348 | ): | |
|
349 | """Return merge in cvs-like form.""" | |
|
350 | newline = _detect_newline(m3) | |
|
351 | conflicts = False | |
|
352 | if name_a: | |
|
353 | start_marker = start_marker + b' ' + name_a | |
|
354 | if name_b: | |
|
355 | end_marker = end_marker + b' ' + name_b | |
|
356 | merge_groups = m3.merge_groups() | |
|
357 | lines = [] | |
|
358 | for what, group_lines in merge_groups: | |
|
359 | if what == b'conflict': | |
|
360 | conflicts = True | |
|
361 | base_lines, a_lines, b_lines = group_lines | |
|
362 | minimized = _minimize(a_lines, b_lines) | |
|
363 | lines_before, a_lines, b_lines, lines_after = minimized | |
|
364 | lines.extend(lines_before) | |
|
365 | lines.append(start_marker + newline) | |
|
366 | lines.extend(a_lines) | |
|
367 | lines.append(mid_marker + newline) | |
|
368 | lines.extend(b_lines) | |
|
369 | lines.append(end_marker + newline) | |
|
370 | lines.extend(lines_after) | |
|
371 | else: | |
|
372 | lines.extend(group_lines) | |
|
373 | return lines, conflicts | |
|
431 | 374 | |
|
432 | 375 | |
|
433 |
def _merge |
|
|
376 | def render_merge3(m3, name_a, name_b, name_base): | |
|
377 | """Render conflicts as 3-way conflict markers.""" | |
|
378 | newline = _detect_newline(m3) | |
|
379 | conflicts = False | |
|
380 | lines = [] | |
|
381 | for what, group_lines in m3.merge_groups(): | |
|
382 | if what == b'conflict': | |
|
383 | base_lines, a_lines, b_lines = group_lines | |
|
384 | conflicts = True | |
|
385 | lines.append(b'<<<<<<< ' + name_a + newline) | |
|
386 | lines.extend(a_lines) | |
|
387 | lines.append(b'||||||| ' + name_base + newline) | |
|
388 | lines.extend(base_lines) | |
|
389 | lines.append(b'=======' + newline) | |
|
390 | lines.extend(b_lines) | |
|
391 | lines.append(b'>>>>>>> ' + name_b + newline) | |
|
392 | else: | |
|
393 | lines.extend(group_lines) | |
|
394 | return lines, conflicts | |
|
395 | ||
|
396 | ||
|
397 | def render_mergediff(m3, name_a, name_b, name_base): | |
|
398 | """Render conflicts as conflict markers with one snapshot and one diff.""" | |
|
399 | newline = _detect_newline(m3) | |
|
434 | 400 | lines = [] |
|
435 | 401 | conflicts = False |
|
436 | for group in m3.merge_groups(): | |
|
437 |
if |
|
|
438 |
base_lines, a_lines, b_lines = group |
|
|
402 | for what, group_lines in m3.merge_groups(): | |
|
403 | if what == b'conflict': | |
|
404 | base_lines, a_lines, b_lines = group_lines | |
|
439 | 405 | base_text = b''.join(base_lines) |
|
440 | 406 | b_blocks = list( |
|
441 | 407 | mdiff.allblocks( |
@@ -472,33 +438,49 b' def _mergediff(m3, name_a, name_b, name_' | |||
|
472 | 438 | for line in lines2[block[2] : block[3]]: |
|
473 | 439 | yield b'+' + line |
|
474 | 440 | |
|
475 |
lines.append(b"<<<<<<< |
|
|
441 | lines.append(b"<<<<<<<" + newline) | |
|
476 | 442 | if matching_lines(a_blocks) < matching_lines(b_blocks): |
|
477 |
lines.append(b"======= |
|
|
443 | lines.append(b"======= " + name_a + newline) | |
|
478 | 444 | lines.extend(a_lines) |
|
479 |
lines.append(b"------- |
|
|
480 |
lines.append(b"+++++++ |
|
|
445 | lines.append(b"------- " + name_base + newline) | |
|
446 | lines.append(b"+++++++ " + name_b + newline) | |
|
481 | 447 | lines.extend(diff_lines(b_blocks, base_lines, b_lines)) |
|
482 | 448 | else: |
|
483 |
lines.append(b"------- |
|
|
484 |
lines.append(b"+++++++ |
|
|
449 | lines.append(b"------- " + name_base + newline) | |
|
450 | lines.append(b"+++++++ " + name_a + newline) | |
|
485 | 451 | lines.extend(diff_lines(a_blocks, base_lines, a_lines)) |
|
486 |
lines.append(b"======= |
|
|
452 | lines.append(b"======= " + name_b + newline) | |
|
487 | 453 | lines.extend(b_lines) |
|
488 |
lines.append(b">>>>>>> |
|
|
454 | lines.append(b">>>>>>>" + newline) | |
|
489 | 455 | conflicts = True |
|
490 | 456 | else: |
|
491 |
lines.extend(group |
|
|
457 | lines.extend(group_lines) | |
|
492 | 458 | return lines, conflicts |
|
493 | 459 | |
|
494 | 460 | |
|
495 | def simplemerge(ui, localctx, basectx, otherctx, **opts): | |
|
496 | """Performs the simplemerge algorithm. | |
|
461 | def _resolve(m3, sides): | |
|
462 | lines = [] | |
|
463 | for what, group_lines in m3.merge_groups(): | |
|
464 | if what == b'conflict': | |
|
465 | for side in sides: | |
|
466 | lines.extend(group_lines[side]) | |
|
467 | else: | |
|
468 | lines.extend(group_lines) | |
|
469 | return lines | |
|
470 | ||
|
497 | 471 | |
|
498 | The merged result is written into `localctx`. | |
|
499 | """ | |
|
472 | class MergeInput(object): | |
|
473 | def __init__(self, fctx, label=None, label_detail=None): | |
|
474 | self.fctx = fctx | |
|
475 | self.label = label | |
|
476 | # If the "detail" part is set, then that is rendered after the label and | |
|
477 | # separated by a ':'. The label is padded to make the ':' aligned among | |
|
478 | # all merge inputs. | |
|
479 | self.label_detail = label_detail | |
|
480 | self._text = None | |
|
500 | 481 | |
|
501 |
def |
|
|
482 | def text(self): | |
|
483 | if self._text is None: | |
|
502 | 484 | # Merges were always run in the working copy before, which means |
|
503 | 485 | # they used decoded data, if the user defined any repository |
|
504 | 486 | # filters. |
@@ -506,61 +488,45 b' def simplemerge(ui, localctx, basectx, o' | |||
|
506 | 488 | # Maintain that behavior today for BC, though perhaps in the future |
|
507 | 489 | # it'd be worth considering whether merging encoded data (what the |
|
508 | 490 | # repository usually sees) might be more useful. |
|
509 | return _verifytext(ctx.decodeddata(), ctx.path(), ui, opts) | |
|
491 | self._text = self.fctx.decodeddata() | |
|
492 | return self._text | |
|
510 | 493 | |
|
511 | mode = opts.get('mode', b'merge') | |
|
512 | name_a, name_b, name_base = None, None, None | |
|
513 | if mode != b'union': | |
|
514 | name_a, name_b, name_base = _picklabels( | |
|
515 | [localctx.path(), otherctx.path(), None], opts.get('label', []) | |
|
516 | ) | |
|
517 | 494 | |
|
518 | try: | |
|
519 | localtext = readctx(localctx) | |
|
520 | basetext = readctx(basectx) | |
|
521 | othertext = readctx(otherctx) | |
|
522 | except error.Abort: | |
|
523 | return 1 | |
|
495 | def simplemerge( | |
|
496 | local, | |
|
497 | base, | |
|
498 | other, | |
|
499 | mode=b'merge', | |
|
500 | allow_binary=False, | |
|
501 | ): | |
|
502 | """Performs the simplemerge algorithm. | |
|
524 | 503 |
|
|
525 | m3 = Merge3Text(basetext, localtext, othertext) | |
|
526 | extrakwargs = { | |
|
527 | b"localorother": opts.get("localorother", None), | |
|
528 | b'minimize': True, | |
|
529 | } | |
|
504 | The merged result is written into `localctx`. | |
|
505 | """ | |
|
506 | ||
|
507 | if not allow_binary: | |
|
508 | _verifytext(local) | |
|
509 | _verifytext(base) | |
|
510 | _verifytext(other) | |
|
511 | ||
|
512 | m3 = Merge3Text(base.text(), local.text(), other.text()) | |
|
513 | conflicts = False | |
|
530 | 514 | if mode == b'union': |
|
531 | extrakwargs[b'start_marker'] = None | |
|
532 | extrakwargs[b'mid_marker'] = None | |
|
533 | extrakwargs[b'end_marker'] = None | |
|
534 | elif name_base is not None: | |
|
535 | extrakwargs[b'base_marker'] = b'|||||||' | |
|
536 | extrakwargs[b'name_base'] = name_base | |
|
537 | extrakwargs[b'minimize'] = False | |
|
538 | ||
|
539 | if mode == b'mergediff': | |
|
540 | lines, conflicts = _mergediff(m3, name_a, name_b, name_base) | |
|
515 | lines = _resolve(m3, (1, 2)) | |
|
516 | elif mode == b'local': | |
|
517 | lines = _resolve(m3, (1,)) | |
|
518 | elif mode == b'other': | |
|
519 | lines = _resolve(m3, (2,)) | |
|
541 | 520 | else: |
|
542 | lines = list( | |
|
543 | m3.merge_lines( | |
|
544 | name_a=name_a, name_b=name_b, **pycompat.strkwargs(extrakwargs) | |
|
545 | ) | |
|
546 | ) | |
|
547 | conflicts = m3.conflicts | |
|
548 | ||
|
549 | # merge flags if necessary | |
|
550 | flags = localctx.flags() | |
|
551 | localflags = set(pycompat.iterbytestr(flags)) | |
|
552 | otherflags = set(pycompat.iterbytestr(otherctx.flags())) | |
|
553 | if is_not_null(basectx) and localflags != otherflags: | |
|
554 | baseflags = set(pycompat.iterbytestr(basectx.flags())) | |
|
555 | commonflags = localflags & otherflags | |
|
556 | addedflags = (localflags ^ otherflags) - baseflags | |
|
557 | flags = b''.join(sorted(commonflags | addedflags)) | |
|
521 | if mode == b'mergediff': | |
|
522 | labels = _format_labels(local, other, base) | |
|
523 | lines, conflicts = render_mergediff(m3, *labels) | |
|
524 | elif mode == b'merge3': | |
|
525 | labels = _format_labels(local, other, base) | |
|
526 | lines, conflicts = render_merge3(m3, *labels) | |
|
527 | else: | |
|
528 | labels = _format_labels(local, other) | |
|
529 | lines, conflicts = render_minimized(m3, *labels) | |
|
558 | 530 | |
|
559 | 531 | mergedtext = b''.join(lines) |
|
560 | if opts.get('print'): | |
|
561 | ui.fout.write(mergedtext) | |
|
562 | else: | |
|
563 | localctx.write(mergedtext, flags) | |
|
564 | ||
|
565 | if conflicts and not mode == b'union': | |
|
566 | return 1 | |
|
532 | return mergedtext, conflicts |
@@ -38,6 +38,9 b' def parseconfig(ui, raw, action):' | |||
|
38 | 38 | |
|
39 | 39 | Returns a tuple of includes, excludes, and profiles. |
|
40 | 40 | """ |
|
41 | with util.timedcm( | |
|
42 | 'sparse.parseconfig(ui, %d bytes, action=%s)', len(raw), action | |
|
43 | ): | |
|
41 | 44 | includes = set() |
|
42 | 45 | excludes = set() |
|
43 | 46 | profiles = set() |
@@ -396,7 +399,7 b' def filterupdatesactions(repo, wctx, mct' | |||
|
396 | 399 | temporaryfiles.append(file) |
|
397 | 400 | prunedactions[file] = action |
|
398 | 401 | elif branchmerge: |
|
399 | if type not in mergestatemod.NO_OP_ACTIONS: | |
|
402 | if not type.no_op: | |
|
400 | 403 | temporaryfiles.append(file) |
|
401 | 404 | prunedactions[file] = action |
|
402 | 405 | elif type == mergestatemod.ACTION_FORGET: |
@@ -600,8 +603,11 b' def _updateconfigandrefreshwdir(' | |||
|
600 | 603 | repo, includes, excludes, profiles, force=False, removing=False |
|
601 | 604 | ): |
|
602 | 605 | """Update the sparse config and working directory state.""" |
|
606 | with repo.lock(): | |
|
603 | 607 | raw = repo.vfs.tryread(b'sparse') |
|
604 |
oldincludes, oldexcludes, oldprofiles = parseconfig( |
|
|
608 | oldincludes, oldexcludes, oldprofiles = parseconfig( | |
|
609 | repo.ui, raw, b'sparse' | |
|
610 | ) | |
|
605 | 611 | |
|
606 | 612 | oldstatus = repo.status() |
|
607 | 613 | oldmatch = matcher(repo) |
@@ -701,21 +707,18 b' def importfromfiles(repo, opts, paths, f' | |||
|
701 | 707 | |
|
702 | 708 | def updateconfig( |
|
703 | 709 | repo, |
|
704 | pats, | |
|
705 | 710 | opts, |
|
706 |
include= |
|
|
707 |
exclude= |
|
|
711 | include=(), | |
|
712 | exclude=(), | |
|
708 | 713 | reset=False, |
|
709 |
delete= |
|
|
710 |
enableprofile= |
|
|
711 |
disableprofile= |
|
|
714 | delete=(), | |
|
715 | enableprofile=(), | |
|
716 | disableprofile=(), | |
|
712 | 717 | force=False, |
|
713 | 718 | usereporootpaths=False, |
|
714 | 719 | ): |
|
715 | 720 | """Perform a sparse config update. |
|
716 | 721 | |
|
717 | Only one of the actions may be performed. | |
|
718 | ||
|
719 | 722 | The new config is written out and a working directory refresh is performed. |
|
720 | 723 | """ |
|
721 | 724 | with repo.wlock(), repo.lock(), repo.dirstate.parentchange(): |
@@ -733,10 +736,13 b' def updateconfig(' | |||
|
733 | 736 | newexclude = set(oldexclude) |
|
734 | 737 | newprofiles = set(oldprofiles) |
|
735 | 738 | |
|
739 | def normalize_pats(pats): | |
|
736 | 740 | if any(os.path.isabs(pat) for pat in pats): |
|
737 | 741 | raise error.Abort(_(b'paths cannot be absolute')) |
|
738 | 742 | |
|
739 |
if |
|
|
743 | if usereporootpaths: | |
|
744 | return pats | |
|
745 | ||
|
740 | 746 | # let's treat paths as relative to cwd |
|
741 | 747 | root, cwd = repo.root, repo.getcwd() |
|
742 | 748 | abspats = [] |
@@ -749,19 +755,20 b' def updateconfig(' | |||
|
749 | 755 | abspats.append(ap) |
|
750 | 756 | else: |
|
751 | 757 | abspats.append(kindpat) |
|
752 |
|
|
|
758 | return abspats | |
|
753 | 759 | |
|
754 | if include: | |
|
755 | newinclude.update(pats) | |
|
756 | elif exclude: | |
|
757 | newexclude.update(pats) | |
|
758 | elif enableprofile: | |
|
759 | newprofiles.update(pats) | |
|
760 | elif disableprofile: | |
|
761 |
|
|
|
762 | elif delete: | |
|
763 |
|
|
|
764 | newexclude.difference_update(pats) | |
|
760 | include = normalize_pats(include) | |
|
761 | exclude = normalize_pats(exclude) | |
|
762 | delete = normalize_pats(delete) | |
|
763 | disableprofile = normalize_pats(disableprofile) | |
|
764 | enableprofile = normalize_pats(enableprofile) | |
|
765 | ||
|
766 | newinclude.difference_update(delete) | |
|
767 | newexclude.difference_update(delete) | |
|
768 | newprofiles.difference_update(disableprofile) | |
|
769 | newinclude.update(include) | |
|
770 | newprofiles.update(enableprofile) | |
|
771 | newexclude.update(exclude) | |
|
765 | 772 | |
|
766 | 773 | profilecount = len(newprofiles - oldprofiles) - len( |
|
767 | 774 | oldprofiles - newprofiles |
@@ -16,7 +16,6 b' from . import (' | |||
|
16 | 16 | error, |
|
17 | 17 | pycompat, |
|
18 | 18 | util, |
|
19 | wireprotoserver, | |
|
20 | 19 | wireprototypes, |
|
21 | 20 | wireprotov1peer, |
|
22 | 21 | wireprotov1server, |
@@ -288,10 +287,6 b' def _performhandshake(ui, stdin, stdout,' | |||
|
288 | 287 | # Generate a random token to help identify responses to version 2 |
|
289 | 288 | # upgrade request. |
|
290 | 289 | token = pycompat.sysbytes(str(uuid.uuid4())) |
|
291 | upgradecaps = [ | |
|
292 | (b'proto', wireprotoserver.SSHV2), | |
|
293 | ] | |
|
294 | upgradecaps = util.urlreq.urlencode(upgradecaps) | |
|
295 | 290 | |
|
296 | 291 | try: |
|
297 | 292 | pairsarg = b'%s-%s' % (b'0' * 40, b'0' * 40) |
@@ -302,11 +297,6 b' def _performhandshake(ui, stdin, stdout,' | |||
|
302 | 297 | pairsarg, |
|
303 | 298 | ] |
|
304 | 299 | |
|
305 | # Request upgrade to version 2 if configured. | |
|
306 | if ui.configbool(b'experimental', b'sshpeer.advertise-v2'): | |
|
307 | ui.debug(b'sending upgrade request: %s %s\n' % (token, upgradecaps)) | |
|
308 | handshake.insert(0, b'upgrade %s %s\n' % (token, upgradecaps)) | |
|
309 | ||
|
310 | 300 | if requestlog: |
|
311 | 301 | ui.debug(b'devel-peer-request: hello+between\n') |
|
312 | 302 | ui.debug(b'devel-peer-request: pairs: %d bytes\n' % len(pairsarg)) |
@@ -365,24 +355,6 b' def _performhandshake(ui, stdin, stdout,' | |||
|
365 | 355 | if l.startswith(b'capabilities:'): |
|
366 | 356 | caps.update(l[:-1].split(b':')[1].split()) |
|
367 | 357 | break |
|
368 | elif protoname == wireprotoserver.SSHV2: | |
|
369 | # We see a line with number of bytes to follow and then a value | |
|
370 | # looking like ``capabilities: *``. | |
|
371 | line = stdout.readline() | |
|
372 | try: | |
|
373 | valuelen = int(line) | |
|
374 | except ValueError: | |
|
375 | badresponse() | |
|
376 | ||
|
377 | capsline = stdout.read(valuelen) | |
|
378 | if not capsline.startswith(b'capabilities: '): | |
|
379 | badresponse() | |
|
380 | ||
|
381 | ui.debug(b'remote: %s\n' % capsline) | |
|
382 | ||
|
383 | caps.update(capsline.split(b':')[1].split()) | |
|
384 | # Trailing newline. | |
|
385 | stdout.read(1) | |
|
386 | 358 | |
|
387 | 359 | # Error if we couldn't find capabilities, this means: |
|
388 | 360 | # |
@@ -601,14 +573,6 b' class sshv1peer(wireprotov1peer.wirepeer' | |||
|
601 | 573 | self._readerr() |
|
602 | 574 | |
|
603 | 575 | |
|
604 | class sshv2peer(sshv1peer): | |
|
605 | """A peer that speakers version 2 of the transport protocol.""" | |
|
606 | ||
|
607 | # Currently version 2 is identical to version 1 post handshake. | |
|
608 | # And handshake is performed before the peer is instantiated. So | |
|
609 | # we need no custom code. | |
|
610 | ||
|
611 | ||
|
612 | 576 | def makepeer(ui, path, proc, stdin, stdout, stderr, autoreadstderr=True): |
|
613 | 577 | """Make a peer instance from existing pipes. |
|
614 | 578 | |
@@ -640,17 +604,6 b' def makepeer(ui, path, proc, stdin, stdo' | |||
|
640 | 604 | caps, |
|
641 | 605 | autoreadstderr=autoreadstderr, |
|
642 | 606 | ) |
|
643 | elif protoname == wireprototypes.SSHV2: | |
|
644 | return sshv2peer( | |
|
645 | ui, | |
|
646 | path, | |
|
647 | proc, | |
|
648 | stdin, | |
|
649 | stdout, | |
|
650 | stderr, | |
|
651 | caps, | |
|
652 | autoreadstderr=autoreadstderr, | |
|
653 | ) | |
|
654 | 607 | else: |
|
655 | 608 | _cleanuppipes(ui, stdout, stdin, stderr, warn=None) |
|
656 | 609 | raise error.RepoError( |
@@ -139,12 +139,18 b' def _hostsettings(ui, hostname):' | |||
|
139 | 139 | |
|
140 | 140 | alg, fingerprint = fingerprint.split(b':', 1) |
|
141 | 141 | fingerprint = fingerprint.replace(b':', b'').lower() |
|
142 | # pytype: disable=attribute-error | |
|
143 | # `s` is heterogeneous, but this entry is always a list of tuples | |
|
142 | 144 | s[b'certfingerprints'].append((alg, fingerprint)) |
|
145 | # pytype: enable=attribute-error | |
|
143 | 146 | |
|
144 | 147 | # Fingerprints from [hostfingerprints] are always SHA-1. |
|
145 | 148 | for fingerprint in ui.configlist(b'hostfingerprints', bhostname): |
|
146 | 149 | fingerprint = fingerprint.replace(b':', b'').lower() |
|
150 | # pytype: disable=attribute-error | |
|
151 | # `s` is heterogeneous, but this entry is always a list of tuples | |
|
147 | 152 | s[b'certfingerprints'].append((b'sha1', fingerprint)) |
|
153 | # pytype: enable=attribute-error | |
|
148 | 154 | s[b'legacyfingerprint'] = True |
|
149 | 155 | |
|
150 | 156 | # If a host cert fingerprint is defined, it is the only thing that |
@@ -22,6 +22,7 b' from . import (' | |||
|
22 | 22 | namespaces, |
|
23 | 23 | pathutil, |
|
24 | 24 | pycompat, |
|
25 | requirements as requirementsmod, | |
|
25 | 26 | url, |
|
26 | 27 | util, |
|
27 | 28 | vfs as vfsmod, |
@@ -197,6 +198,9 b' class statichttprepository(' | |||
|
197 | 198 | # we do not care about empty old-style repositories here |
|
198 | 199 | msg = _(b"'%s' does not appear to be an hg repository") % path |
|
199 | 200 | raise error.RepoError(msg) |
|
201 | if requirementsmod.SHARESAFE_REQUIREMENT in requirements: | |
|
202 | storevfs = vfsclass(self.vfs.join(b'store')) | |
|
203 | requirements |= set(storevfs.read(b'requires').splitlines()) | |
|
200 | 204 | |
|
201 | 205 | supportedrequirements = localrepo.gathersupportedrequirements(ui) |
|
202 | 206 | localrepo.ensurerequirementsrecognized( |
@@ -494,9 +494,9 b' def display(fp=None, format=3, data=None' | |||
|
494 | 494 | data = state |
|
495 | 495 | |
|
496 | 496 | if fp is None: |
|
497 | import sys | |
|
497 | from .utils import procutil | |
|
498 | 498 | |
|
499 |
fp = |
|
|
499 | fp = procutil.stdout | |
|
500 | 500 | if len(data.samples) == 0: |
|
501 | 501 | fp.write(b'No samples recorded.\n') |
|
502 | 502 | return |
@@ -516,7 +516,7 b' def display(fp=None, format=3, data=None' | |||
|
516 | 516 | elif format == DisplayFormats.Chrome: |
|
517 | 517 | write_to_chrome(data, fp, **kwargs) |
|
518 | 518 | else: |
|
519 |
raise Exception( |
|
|
519 | raise Exception("Invalid display format") | |
|
520 | 520 | |
|
521 | 521 | if format not in (DisplayFormats.Json, DisplayFormats.Chrome): |
|
522 | 522 | fp.write(b'---\n') |
@@ -625,7 +625,7 b' def display_by_method(data, fp):' | |||
|
625 | 625 | |
|
626 | 626 | def display_about_method(data, fp, function=None, **kwargs): |
|
627 | 627 | if function is None: |
|
628 |
raise Exception( |
|
|
628 | raise Exception("Invalid function") | |
|
629 | 629 | |
|
630 | 630 | filename = None |
|
631 | 631 | if b':' in function: |
@@ -1080,7 +1080,7 b' def main(argv=None):' | |||
|
1080 | 1080 | printusage() |
|
1081 | 1081 | return 0 |
|
1082 | 1082 | else: |
|
1083 |
assert False, |
|
|
1083 | assert False, "unhandled option %s" % o | |
|
1084 | 1084 | |
|
1085 | 1085 | if not path: |
|
1086 | 1086 | print('must specify --file to load') |
@@ -27,11 +27,38 b' from . import (' | |||
|
27 | 27 | store, |
|
28 | 28 | util, |
|
29 | 29 | ) |
|
30 | from .revlogutils import ( | |
|
31 | nodemap, | |
|
32 | ) | |
|
30 | 33 | from .utils import ( |
|
31 | 34 | stringutil, |
|
32 | 35 | ) |
|
33 | 36 | |
|
34 | 37 | |
|
38 | def new_stream_clone_requirements(default_requirements, streamed_requirements): | |
|
39 | """determine the final set of requirement for a new stream clone | |
|
40 | ||
|
41 | this method combine the "default" requirements that a new repository would | |
|
42 | use with the constaint we get from the stream clone content. We keep local | |
|
43 | configuration choice when possible. | |
|
44 | """ | |
|
45 | requirements = set(default_requirements) | |
|
46 | requirements -= requirementsmod.STREAM_FIXED_REQUIREMENTS | |
|
47 | requirements.update(streamed_requirements) | |
|
48 | return requirements | |
|
49 | ||
|
50 | ||
|
51 | def streamed_requirements(repo): | |
|
52 | """the set of requirement the new clone will have to support | |
|
53 | ||
|
54 | This is used for advertising the stream options and to generate the actual | |
|
55 | stream content.""" | |
|
56 | requiredformats = ( | |
|
57 | repo.requirements & requirementsmod.STREAM_FIXED_REQUIREMENTS | |
|
58 | ) | |
|
59 | return requiredformats | |
|
60 | ||
|
61 | ||
|
35 | 62 | def canperformstreamclone(pullop, bundle2=False): |
|
36 | 63 | """Whether it is possible to perform a streaming clone as part of pull. |
|
37 | 64 | |
@@ -184,17 +211,15 b' def maybeperformlegacystreamclone(pullop' | |||
|
184 | 211 | |
|
185 | 212 | with repo.lock(): |
|
186 | 213 | consumev1(repo, fp, filecount, bytecount) |
|
187 | ||
|
188 | # new requirements = old non-format requirements + | |
|
189 | # new format-related remote requirements | |
|
190 | # requirements from the streamed-in repository | |
|
191 | repo.requirements = requirements | ( | |
|
192 | repo.requirements - repo.supportedformats | |
|
214 | repo.requirements = new_stream_clone_requirements( | |
|
215 | repo.requirements, | |
|
216 | requirements, | |
|
193 | 217 | ) |
|
194 | 218 | repo.svfs.options = localrepo.resolvestorevfsoptions( |
|
195 | 219 | repo.ui, repo.requirements, repo.features |
|
196 | 220 | ) |
|
197 | 221 | scmutil.writereporequirements(repo) |
|
222 | nodemap.post_stream_cleanup(repo) | |
|
198 | 223 | |
|
199 | 224 | if rbranchmap: |
|
200 | 225 | repo._branchcaches.replace(repo, rbranchmap) |
@@ -333,7 +358,7 b' def generatebundlev1(repo, compression=b' | |||
|
333 | 358 | if compression != b'UN': |
|
334 | 359 | raise ValueError(b'we do not support the compression argument yet') |
|
335 | 360 | |
|
336 |
requirements = repo |
|
|
361 | requirements = streamed_requirements(repo) | |
|
337 | 362 | requires = b','.join(sorted(requirements)) |
|
338 | 363 | |
|
339 | 364 | def gen(): |
@@ -489,6 +514,7 b' def applybundlev1(repo, fp):' | |||
|
489 | 514 | ) |
|
490 | 515 | |
|
491 | 516 | consumev1(repo, fp, filecount, bytecount) |
|
517 | nodemap.post_stream_cleanup(repo) | |
|
492 | 518 | |
|
493 | 519 | |
|
494 | 520 | class streamcloneapplier(object): |
@@ -797,16 +823,15 b' def applybundlev2(repo, fp, filecount, f' | |||
|
797 | 823 | |
|
798 | 824 | consumev2(repo, fp, filecount, filesize) |
|
799 | 825 | |
|
800 |
|
|
|
801 | # new format-related remote requirements | |
|
802 | # requirements from the streamed-in repository | |
|
803 | repo.requirements = set(requirements) | ( | |
|
804 | repo.requirements - repo.supportedformats | |
|
826 | repo.requirements = new_stream_clone_requirements( | |
|
827 | repo.requirements, | |
|
828 | requirements, | |
|
805 | 829 | ) |
|
806 | 830 | repo.svfs.options = localrepo.resolvestorevfsoptions( |
|
807 | 831 | repo.ui, repo.requirements, repo.features |
|
808 | 832 | ) |
|
809 | 833 | scmutil.writereporequirements(repo) |
|
834 | nodemap.post_stream_cleanup(repo) | |
|
810 | 835 | |
|
811 | 836 | |
|
812 | 837 | def _copy_files(src_vfs_map, dst_vfs_map, entries, progress): |
@@ -304,6 +304,21 b' def showextras(context, mapping):' | |||
|
304 | 304 | ) |
|
305 | 305 | |
|
306 | 306 | |
|
307 | @templatekeyword(b'_fast_rank', requires={b'ctx'}) | |
|
308 | def fast_rank(context, mapping): | |
|
309 | """the rank of a changeset if cached | |
|
310 | ||
|
311 | The rank of a revision is the size of the sub-graph it defines as a head. | |
|
312 | Equivalently, the rank of a revision `r` is the size of the set | |
|
313 | `ancestors(r)`, `r` included. | |
|
314 | """ | |
|
315 | ctx = context.resource(mapping, b'ctx') | |
|
316 | rank = ctx.fast_rank() | |
|
317 | if rank is None: | |
|
318 | return None | |
|
319 | return b"%d" % rank | |
|
320 | ||
|
321 | ||
|
307 | 322 | def _getfilestatus(context, mapping, listall=False): |
|
308 | 323 | ctx = context.resource(mapping, b'ctx') |
|
309 | 324 | revcache = context.resource(mapping, b'revcache') |
@@ -25,11 +25,6 b' from .utils import stringutil' | |||
|
25 | 25 | |
|
26 | 26 | version = 2 |
|
27 | 27 | |
|
28 | # These are the file generators that should only be executed after the | |
|
29 | # finalizers are done, since they rely on the output of the finalizers (like | |
|
30 | # the changelog having been written). | |
|
31 | postfinalizegenerators = {b'bookmarks', b'dirstate'} | |
|
32 | ||
|
33 | 28 | GEN_GROUP_ALL = b'all' |
|
34 | 29 | GEN_GROUP_PRE_FINALIZE = b'prefinalize' |
|
35 | 30 | GEN_GROUP_POST_FINALIZE = b'postfinalize' |
@@ -334,7 +329,13 b' class transaction(util.transactional):' | |||
|
334 | 329 | |
|
335 | 330 | @active |
|
336 | 331 | def addfilegenerator( |
|
337 | self, genid, filenames, genfunc, order=0, location=b'' | |
|
332 | self, | |
|
333 | genid, | |
|
334 | filenames, | |
|
335 | genfunc, | |
|
336 | order=0, | |
|
337 | location=b'', | |
|
338 | post_finalize=False, | |
|
338 | 339 | ): |
|
339 | 340 | """add a function to generates some files at transaction commit |
|
340 | 341 | |
@@ -357,10 +358,14 b' class transaction(util.transactional):' | |||
|
357 | 358 | The `location` arguments may be used to indicate the files are located |
|
358 | 359 | outside of the the standard directory for transaction. It should match |
|
359 | 360 | one of the key of the `transaction.vfsmap` dictionary. |
|
361 | ||
|
362 | The `post_finalize` argument can be set to `True` for file generation | |
|
363 | that must be run after the transaction has been finalized. | |
|
360 | 364 | """ |
|
361 | 365 | # For now, we are unable to do proper backup and restore of custom vfs |
|
362 | 366 | # but for bookmarks that are handled outside this mechanism. |
|
363 |
|
|
|
367 | entry = (order, filenames, genfunc, location, post_finalize) | |
|
368 | self._filegenerators[genid] = entry | |
|
364 | 369 | |
|
365 | 370 | @active |
|
366 | 371 | def removefilegenerator(self, genid): |
@@ -380,13 +385,12 b' class transaction(util.transactional):' | |||
|
380 | 385 | |
|
381 | 386 | for id, entry in sorted(pycompat.iteritems(self._filegenerators)): |
|
382 | 387 | any = True |
|
383 | order, filenames, genfunc, location = entry | |
|
388 | order, filenames, genfunc, location, post_finalize = entry | |
|
384 | 389 | |
|
385 | 390 | # for generation at closing, check if it's before or after finalize |
|
386 |
i |
|
|
387 | if skip_post and is_post: | |
|
391 | if skip_post and post_finalize: | |
|
388 | 392 | continue |
|
389 |
elif skip_pre and not |
|
|
393 | elif skip_pre and not post_finalize: | |
|
390 | 394 | continue |
|
391 | 395 | |
|
392 | 396 | vfs = self._vfsmap[location] |
@@ -71,6 +71,7 b' class unionrevlog(revlog.revlog):' | |||
|
71 | 71 | _sds, |
|
72 | 72 | _dcm, |
|
73 | 73 | _sdcm, |
|
74 | rank, | |
|
74 | 75 | ) = rev |
|
75 | 76 | flags = _start & 0xFFFF |
|
76 | 77 | |
@@ -107,6 +108,7 b' class unionrevlog(revlog.revlog):' | |||
|
107 | 108 | 0, # sidedata size |
|
108 | 109 | revlog_constants.COMP_MODE_INLINE, |
|
109 | 110 | revlog_constants.COMP_MODE_INLINE, |
|
111 | rank, | |
|
110 | 112 | ) |
|
111 | 113 | self.index.append(e) |
|
112 | 114 | self.bundlerevs.add(n) |
@@ -42,27 +42,16 b' def upgraderepo(' | |||
|
42 | 42 | ): |
|
43 | 43 | """Upgrade a repository in place.""" |
|
44 | 44 | if optimize is None: |
|
45 |
optimize = |
|
|
45 | optimize = set() | |
|
46 | 46 | repo = repo.unfiltered() |
|
47 | 47 | |
|
48 | revlogs = set(upgrade_engine.UPGRADE_ALL_REVLOGS) | |
|
49 | specentries = ( | |
|
50 |
|
|
|
51 | (upgrade_engine.UPGRADE_MANIFEST, manifest), | |
|
52 |
|
|
|
53 | ) | |
|
54 | specified = [(y, x) for (y, x) in specentries if x is not None] | |
|
55 | if specified: | |
|
56 | # we have some limitation on revlogs to be recloned | |
|
57 | if any(x for y, x in specified): | |
|
58 | revlogs = set() | |
|
59 | for upgrade, enabled in specified: | |
|
60 | if enabled: | |
|
61 | revlogs.add(upgrade) | |
|
62 | else: | |
|
63 | # none are enabled | |
|
64 | for upgrade, __ in specified: | |
|
65 | revlogs.discard(upgrade) | |
|
48 | specified_revlogs = {} | |
|
49 | if changelog is not None: | |
|
50 | specified_revlogs[upgrade_engine.UPGRADE_CHANGELOG] = changelog | |
|
51 | if manifest is not None: | |
|
52 | specified_revlogs[upgrade_engine.UPGRADE_MANIFEST] = manifest | |
|
53 | if filelogs is not None: | |
|
54 | specified_revlogs[upgrade_engine.UPGRADE_FILELOGS] = filelogs | |
|
66 | 55 | |
|
67 | 56 | # Ensure the repository can be upgraded. |
|
68 | 57 | upgrade_actions.check_source_requirements(repo) |
@@ -96,20 +85,92 b' def upgraderepo(' | |||
|
96 | 85 | ) |
|
97 | 86 | removed_actions = upgrade_actions.find_format_downgrades(repo) |
|
98 | 87 | |
|
99 | removedreqs = repo.requirements - newreqs | |
|
100 | addedreqs = newreqs - repo.requirements | |
|
88 | # check if we need to touch revlog and if so, which ones | |
|
89 | ||
|
90 | touched_revlogs = set() | |
|
91 | overwrite_msg = _(b'warning: ignoring %14s, as upgrade is changing: %s\n') | |
|
92 | select_msg = _(b'note: selecting %s for processing to change: %s\n') | |
|
93 | msg_issued = 0 | |
|
94 | ||
|
95 | FL = upgrade_engine.UPGRADE_FILELOGS | |
|
96 | MN = upgrade_engine.UPGRADE_MANIFEST | |
|
97 | CL = upgrade_engine.UPGRADE_CHANGELOG | |
|
98 | ||
|
99 | if optimizations: | |
|
100 | if any(specified_revlogs.values()): | |
|
101 | # we have some limitation on revlogs to be recloned | |
|
102 | for rl, enabled in specified_revlogs.items(): | |
|
103 | if enabled: | |
|
104 | touched_revlogs.add(rl) | |
|
105 | else: | |
|
106 | touched_revlogs = set(upgrade_engine.UPGRADE_ALL_REVLOGS) | |
|
107 | for rl, enabled in specified_revlogs.items(): | |
|
108 | if not enabled: | |
|
109 | touched_revlogs.discard(rl) | |
|
110 | ||
|
111 | if repo.shared(): | |
|
112 | unsafe_actions = set() | |
|
113 | unsafe_actions.update(up_actions) | |
|
114 | unsafe_actions.update(removed_actions) | |
|
115 | unsafe_actions.update(optimizations) | |
|
116 | unsafe_actions = [ | |
|
117 | a for a in unsafe_actions if not a.compatible_with_share | |
|
118 | ] | |
|
119 | unsafe_actions.sort(key=lambda a: a.name) | |
|
120 | if unsafe_actions: | |
|
121 | m = _(b'cannot use these actions on a share repository: %s') | |
|
122 | h = _(b'upgrade the main repository directly') | |
|
123 | actions = b', '.join(a.name for a in unsafe_actions) | |
|
124 | m %= actions | |
|
125 | raise error.Abort(m, hint=h) | |
|
101 | 126 | |
|
102 | if revlogs != upgrade_engine.UPGRADE_ALL_REVLOGS: | |
|
103 | incompatible = upgrade_actions.RECLONES_REQUIREMENTS & ( | |
|
104 | removedreqs | addedreqs | |
|
105 | ) | |
|
106 | if incompatible: | |
|
107 | msg = _( | |
|
108 | b'ignoring revlogs selection flags, format requirements ' | |
|
109 | b'change: %s\n' | |
|
110 | ) | |
|
111 | ui.warn(msg % b', '.join(sorted(incompatible))) | |
|
112 | revlogs = upgrade_engine.UPGRADE_ALL_REVLOGS | |
|
127 | for action in sorted(up_actions + removed_actions, key=lambda a: a.name): | |
|
128 | # optimisation does not "requires anything, they just needs it. | |
|
129 | if action.type != upgrade_actions.FORMAT_VARIANT: | |
|
130 | continue | |
|
131 | ||
|
132 | if action.touches_filelogs and FL not in touched_revlogs: | |
|
133 | if FL in specified_revlogs: | |
|
134 | if not specified_revlogs[FL]: | |
|
135 | msg = overwrite_msg % (b'--no-filelogs', action.name) | |
|
136 | ui.warn(msg) | |
|
137 | msg_issued = 2 | |
|
138 | else: | |
|
139 | msg = select_msg % (b'all-filelogs', action.name) | |
|
140 | ui.status(msg) | |
|
141 | if not ui.quiet: | |
|
142 | msg_issued = 1 | |
|
143 | touched_revlogs.add(FL) | |
|
144 | ||
|
145 | if action.touches_manifests and MN not in touched_revlogs: | |
|
146 | if MN in specified_revlogs: | |
|
147 | if not specified_revlogs[MN]: | |
|
148 | msg = overwrite_msg % (b'--no-manifest', action.name) | |
|
149 | ui.warn(msg) | |
|
150 | msg_issued = 2 | |
|
151 | else: | |
|
152 | msg = select_msg % (b'all-manifestlogs', action.name) | |
|
153 | ui.status(msg) | |
|
154 | if not ui.quiet: | |
|
155 | msg_issued = 1 | |
|
156 | touched_revlogs.add(MN) | |
|
157 | ||
|
158 | if action.touches_changelog and CL not in touched_revlogs: | |
|
159 | if CL in specified_revlogs: | |
|
160 | if not specified_revlogs[CL]: | |
|
161 | msg = overwrite_msg % (b'--no-changelog', action.name) | |
|
162 | ui.warn(msg) | |
|
163 | msg_issued = True | |
|
164 | else: | |
|
165 | msg = select_msg % (b'changelog', action.name) | |
|
166 | ui.status(msg) | |
|
167 | if not ui.quiet: | |
|
168 | msg_issued = 1 | |
|
169 | touched_revlogs.add(CL) | |
|
170 | if msg_issued >= 2: | |
|
171 | ui.warn((b"\n")) | |
|
172 | elif msg_issued >= 1: | |
|
173 | ui.status((b"\n")) | |
|
113 | 174 | |
|
114 | 175 | upgrade_op = upgrade_actions.UpgradeOperation( |
|
115 | 176 | ui, |
@@ -117,7 +178,7 b' def upgraderepo(' | |||
|
117 | 178 | repo.requirements, |
|
118 | 179 | up_actions, |
|
119 | 180 | removed_actions, |
|
120 | revlogs, | |
|
181 | touched_revlogs, | |
|
121 | 182 | backup, |
|
122 | 183 | ) |
|
123 | 184 |
@@ -36,7 +36,10 b' RECLONES_REQUIREMENTS = {' | |||
|
36 | 36 | |
|
37 | 37 | |
|
38 | 38 | def preservedrequirements(repo): |
|
39 | return set() | |
|
39 | preserved = { | |
|
40 | requirements.SHARED_REQUIREMENT, | |
|
41 | } | |
|
42 | return preserved & repo.requirements | |
|
40 | 43 | |
|
41 | 44 | |
|
42 | 45 | FORMAT_VARIANT = b'deficiency' |
@@ -97,6 +100,9 b' class improvement(object):' | |||
|
97 | 100 | # Whether this improvement touches the dirstate |
|
98 | 101 | touches_dirstate = False |
|
99 | 102 | |
|
103 | # Can this action be run on a share instead of its mains repository | |
|
104 | compatible_with_share = False | |
|
105 | ||
|
100 | 106 | |
|
101 | 107 | allformatvariant = [] # type: List[Type['formatvariant']] |
|
102 | 108 | |
@@ -190,6 +196,30 b' class dirstatev2(requirementformatvarian' | |||
|
190 | 196 | touches_changelog = False |
|
191 | 197 | touches_requirements = True |
|
192 | 198 | touches_dirstate = True |
|
199 | compatible_with_share = True | |
|
200 | ||
|
201 | ||
|
202 | @registerformatvariant | |
|
203 | class dirstatetrackedkey(requirementformatvariant): | |
|
204 | name = b'tracked-hint' | |
|
205 | _requirement = requirements.DIRSTATE_TRACKED_HINT_V1 | |
|
206 | ||
|
207 | default = False | |
|
208 | ||
|
209 | description = _( | |
|
210 | b'Add a small file to help external tooling that watch the tracked set' | |
|
211 | ) | |
|
212 | ||
|
213 | upgrademessage = _( | |
|
214 | b'external tools will be informated of potential change in the tracked set' | |
|
215 | ) | |
|
216 | ||
|
217 | touches_filelogs = False | |
|
218 | touches_manifests = False | |
|
219 | touches_changelog = False | |
|
220 | touches_requirements = True | |
|
221 | touches_dirstate = True | |
|
222 | compatible_with_share = True | |
|
193 | 223 | |
|
194 | 224 | |
|
195 | 225 | @registerformatvariant |
@@ -243,7 +273,7 b' class sharesafe(requirementformatvariant' | |||
|
243 | 273 | name = b'share-safe' |
|
244 | 274 | _requirement = requirements.SHARESAFE_REQUIREMENT |
|
245 | 275 | |
|
246 |
default = |
|
|
276 | default = True | |
|
247 | 277 | |
|
248 | 278 | description = _( |
|
249 | 279 | b'old shared repositories do not share source repository ' |
@@ -899,8 +929,6 b' def blocksourcerequirements(repo):' | |||
|
899 | 929 | # This was a precursor to generaldelta and was never enabled by default. |
|
900 | 930 | # It should (hopefully) not exist in the wild. |
|
901 | 931 | b'parentdelta', |
|
902 | # Upgrade should operate on the actual store, not the shared link. | |
|
903 | requirements.SHARED_REQUIREMENT, | |
|
904 | 932 | } |
|
905 | 933 | |
|
906 | 934 | |
@@ -932,6 +960,16 b' def check_source_requirements(repo):' | |||
|
932 | 960 | m = _(b'cannot upgrade repository; unsupported source requirement: %s') |
|
933 | 961 | blockingreqs = b', '.join(sorted(blockingreqs)) |
|
934 | 962 | raise error.Abort(m % blockingreqs) |
|
963 | # Upgrade should operate on the actual store, not the shared link. | |
|
964 | ||
|
965 | bad_share = ( | |
|
966 | requirements.SHARED_REQUIREMENT in repo.requirements | |
|
967 | and requirements.SHARESAFE_REQUIREMENT not in repo.requirements | |
|
968 | ) | |
|
969 | if bad_share: | |
|
970 | m = _(b'cannot upgrade repository; share repository without share-safe') | |
|
971 | h = _(b'check :hg:`help config.format.use-share-safe`') | |
|
972 | raise error.Abort(m, hint=h) | |
|
935 | 973 | |
|
936 | 974 | |
|
937 | 975 | ### Verify the validity of the planned requirement changes #################### |
@@ -952,6 +990,7 b' def supportremovedrequirements(repo):' | |||
|
952 | 990 | requirements.REVLOGV2_REQUIREMENT, |
|
953 | 991 | requirements.CHANGELOGV2_REQUIREMENT, |
|
954 | 992 | requirements.REVLOGV1_REQUIREMENT, |
|
993 | requirements.DIRSTATE_TRACKED_HINT_V1, | |
|
955 | 994 | requirements.DIRSTATE_V2_REQUIREMENT, |
|
956 | 995 | } |
|
957 | 996 | for name in compression.compengines: |
@@ -972,18 +1011,20 b' def supporteddestrequirements(repo):' | |||
|
972 | 1011 | Extensions should monkeypatch this to add their custom requirements. |
|
973 | 1012 | """ |
|
974 | 1013 | supported = { |
|
1014 | requirements.CHANGELOGV2_REQUIREMENT, | |
|
1015 | requirements.COPIESSDC_REQUIREMENT, | |
|
1016 | requirements.DIRSTATE_TRACKED_HINT_V1, | |
|
1017 | requirements.DIRSTATE_V2_REQUIREMENT, | |
|
975 | 1018 | requirements.DOTENCODE_REQUIREMENT, |
|
976 | 1019 | requirements.FNCACHE_REQUIREMENT, |
|
977 | 1020 | requirements.GENERALDELTA_REQUIREMENT, |
|
1021 | requirements.NODEMAP_REQUIREMENT, | |
|
978 | 1022 | requirements.REVLOGV1_REQUIREMENT, # allowed in case of downgrade |
|
979 |
requirements. |
|
|
1023 | requirements.REVLOGV2_REQUIREMENT, | |
|
1024 | requirements.SHARED_REQUIREMENT, | |
|
1025 | requirements.SHARESAFE_REQUIREMENT, | |
|
980 | 1026 | requirements.SPARSEREVLOG_REQUIREMENT, |
|
981 |
requirements. |
|
|
982 | requirements.NODEMAP_REQUIREMENT, | |
|
983 | requirements.SHARESAFE_REQUIREMENT, | |
|
984 | requirements.REVLOGV2_REQUIREMENT, | |
|
985 | requirements.CHANGELOGV2_REQUIREMENT, | |
|
986 | requirements.DIRSTATE_V2_REQUIREMENT, | |
|
1027 | requirements.STORE_REQUIREMENT, | |
|
987 | 1028 | } |
|
988 | 1029 | for name in compression.compengines: |
|
989 | 1030 | engine = compression.compengines[name] |
@@ -1015,6 +1056,7 b' def allowednewrequirements(repo):' | |||
|
1015 | 1056 | requirements.REVLOGV1_REQUIREMENT, |
|
1016 | 1057 | requirements.REVLOGV2_REQUIREMENT, |
|
1017 | 1058 | requirements.CHANGELOGV2_REQUIREMENT, |
|
1059 | requirements.DIRSTATE_TRACKED_HINT_V1, | |
|
1018 | 1060 | requirements.DIRSTATE_V2_REQUIREMENT, |
|
1019 | 1061 | } |
|
1020 | 1062 | for name in compression.compengines: |
@@ -486,6 +486,15 b' def upgrade(ui, srcrepo, dstrepo, upgrad' | |||
|
486 | 486 | upgrade_dirstate(ui, srcrepo, upgrade_op, b'v2', b'v1') |
|
487 | 487 | upgrade_op.removed_actions.remove(upgrade_actions.dirstatev2) |
|
488 | 488 | |
|
489 | if upgrade_actions.dirstatetrackedkey in upgrade_op.upgrade_actions: | |
|
490 | ui.status(_(b'create dirstate-tracked-hint file\n')) | |
|
491 | upgrade_tracked_hint(ui, srcrepo, upgrade_op, add=True) | |
|
492 | upgrade_op.upgrade_actions.remove(upgrade_actions.dirstatetrackedkey) | |
|
493 | elif upgrade_actions.dirstatetrackedkey in upgrade_op.removed_actions: | |
|
494 | ui.status(_(b'remove dirstate-tracked-hint file\n')) | |
|
495 | upgrade_tracked_hint(ui, srcrepo, upgrade_op, add=False) | |
|
496 | upgrade_op.removed_actions.remove(upgrade_actions.dirstatetrackedkey) | |
|
497 | ||
|
489 | 498 | if not (upgrade_op.upgrade_actions or upgrade_op.removed_actions): |
|
490 | 499 | return |
|
491 | 500 | |
@@ -660,3 +669,15 b' def upgrade_dirstate(ui, srcrepo, upgrad' | |||
|
660 | 669 | srcrepo.dirstate.write(None) |
|
661 | 670 | |
|
662 | 671 | scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements) |
|
672 | ||
|
673 | ||
|
674 | def upgrade_tracked_hint(ui, srcrepo, upgrade_op, add): | |
|
675 | if add: | |
|
676 | srcrepo.dirstate._use_tracked_hint = True | |
|
677 | srcrepo.dirstate._dirty = True | |
|
678 | srcrepo.dirstate._dirty_tracked_set = True | |
|
679 | srcrepo.dirstate.write(None) | |
|
680 | if not add: | |
|
681 | srcrepo.dirstate.delete_tracked_hint() | |
|
682 | ||
|
683 | scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements) |
@@ -57,7 +57,6 b' from .utils import (' | |||
|
57 | 57 | hashutil, |
|
58 | 58 | procutil, |
|
59 | 59 | stringutil, |
|
60 | urlutil, | |
|
61 | 60 | ) |
|
62 | 61 | |
|
63 | 62 | if pycompat.TYPE_CHECKING: |
@@ -2991,54 +2990,6 b' def interpolate(prefix, mapping, s, fn=N' | |||
|
2991 | 2990 | return r.sub(lambda x: fn(mapping[x.group()[1:]]), s) |
|
2992 | 2991 | |
|
2993 | 2992 | |
|
2994 | def getport(*args, **kwargs): | |
|
2995 | msg = b'getport(...) moved to mercurial.utils.urlutil' | |
|
2996 | nouideprecwarn(msg, b'6.0', stacklevel=2) | |
|
2997 | return urlutil.getport(*args, **kwargs) | |
|
2998 | ||
|
2999 | ||
|
3000 | def url(*args, **kwargs): | |
|
3001 | msg = b'url(...) moved to mercurial.utils.urlutil' | |
|
3002 | nouideprecwarn(msg, b'6.0', stacklevel=2) | |
|
3003 | return urlutil.url(*args, **kwargs) | |
|
3004 | ||
|
3005 | ||
|
3006 | def hasscheme(*args, **kwargs): | |
|
3007 | msg = b'hasscheme(...) moved to mercurial.utils.urlutil' | |
|
3008 | nouideprecwarn(msg, b'6.0', stacklevel=2) | |
|
3009 | return urlutil.hasscheme(*args, **kwargs) | |
|
3010 | ||
|
3011 | ||
|
3012 | def hasdriveletter(*args, **kwargs): | |
|
3013 | msg = b'hasdriveletter(...) moved to mercurial.utils.urlutil' | |
|
3014 | nouideprecwarn(msg, b'6.0', stacklevel=2) | |
|
3015 | return urlutil.hasdriveletter(*args, **kwargs) | |
|
3016 | ||
|
3017 | ||
|
3018 | def urllocalpath(*args, **kwargs): | |
|
3019 | msg = b'urllocalpath(...) moved to mercurial.utils.urlutil' | |
|
3020 | nouideprecwarn(msg, b'6.0', stacklevel=2) | |
|
3021 | return urlutil.urllocalpath(*args, **kwargs) | |
|
3022 | ||
|
3023 | ||
|
3024 | def checksafessh(*args, **kwargs): | |
|
3025 | msg = b'checksafessh(...) moved to mercurial.utils.urlutil' | |
|
3026 | nouideprecwarn(msg, b'6.0', stacklevel=2) | |
|
3027 | return urlutil.checksafessh(*args, **kwargs) | |
|
3028 | ||
|
3029 | ||
|
3030 | def hidepassword(*args, **kwargs): | |
|
3031 | msg = b'hidepassword(...) moved to mercurial.utils.urlutil' | |
|
3032 | nouideprecwarn(msg, b'6.0', stacklevel=2) | |
|
3033 | return urlutil.hidepassword(*args, **kwargs) | |
|
3034 | ||
|
3035 | ||
|
3036 | def removeauth(*args, **kwargs): | |
|
3037 | msg = b'removeauth(...) moved to mercurial.utils.urlutil' | |
|
3038 | nouideprecwarn(msg, b'6.0', stacklevel=2) | |
|
3039 | return urlutil.removeauth(*args, **kwargs) | |
|
3040 | ||
|
3041 | ||
|
3042 | 2993 | timecount = unitcountfn( |
|
3043 | 2994 | (1, 1e3, _(b'%.0f s')), |
|
3044 | 2995 | (100, 1, _(b'%.1f s')), |
@@ -75,7 +75,9 b' class LineBufferedWrapper(object):' | |||
|
75 | 75 | return res |
|
76 | 76 | |
|
77 | 77 | |
|
78 | # pytype: disable=attribute-error | |
|
78 | 79 | io.BufferedIOBase.register(LineBufferedWrapper) |
|
80 | # pytype: enable=attribute-error | |
|
79 | 81 | |
|
80 | 82 | |
|
81 | 83 | def make_line_buffered(stream): |
@@ -114,7 +116,9 b' class WriteAllWrapper(object):' | |||
|
114 | 116 | return total_written |
|
115 | 117 | |
|
116 | 118 | |
|
119 | # pytype: disable=attribute-error | |
|
117 | 120 | io.IOBase.register(WriteAllWrapper) |
|
121 | # pytype: enable=attribute-error | |
|
118 | 122 | |
|
119 | 123 | |
|
120 | 124 | def _make_write_all(stream): |
@@ -738,6 +742,8 b' else:' | |||
|
738 | 742 | start_new_session = False |
|
739 | 743 | ensurestart = True |
|
740 | 744 | |
|
745 | stdin = None | |
|
746 | ||
|
741 | 747 | try: |
|
742 | 748 | if stdin_bytes is None: |
|
743 | 749 | stdin = subprocess.DEVNULL |
@@ -766,7 +772,8 b' else:' | |||
|
766 | 772 | record_wait(255) |
|
767 | 773 | raise |
|
768 | 774 | finally: |
|
769 | if stdin_bytes is not None: | |
|
775 | if stdin_bytes is not None and stdin is not None: | |
|
776 | assert not isinstance(stdin, int) | |
|
770 | 777 | stdin.close() |
|
771 | 778 | if not ensurestart: |
|
772 | 779 | # Even though we're not waiting on the child process, |
@@ -847,6 +854,8 b' else:' | |||
|
847 | 854 | return |
|
848 | 855 | |
|
849 | 856 | returncode = 255 |
|
857 | stdin = None | |
|
858 | ||
|
850 | 859 | try: |
|
851 | 860 | if record_wait is None: |
|
852 | 861 | # Start a new session |
@@ -889,6 +898,7 b' else:' | |||
|
889 | 898 | finally: |
|
890 | 899 | # mission accomplished, this child needs to exit and not |
|
891 | 900 | # continue the hg process here. |
|
901 | if stdin is not None: | |
|
892 | 902 | stdin.close() |
|
893 | 903 | if record_wait is None: |
|
894 | 904 | os._exit(returncode) |
@@ -112,6 +112,13 b' def filerevisioncopied(store, node):' | |||
|
112 | 112 | 2-tuple of the source filename and node. |
|
113 | 113 | """ |
|
114 | 114 | if store.parents(node)[0] != sha1nodeconstants.nullid: |
|
115 | # When creating a copy or move we set filelog parents to null, | |
|
116 | # because contents are probably unrelated and making a delta | |
|
117 | # would not be useful. | |
|
118 | # Conversely, if filelog p1 is non-null we know | |
|
119 | # there is no copy metadata. | |
|
120 | # In the presence of merges, this reasoning becomes invalid | |
|
121 | # if we reorder parents. See tests/test-issue6528.t. | |
|
115 | 122 | return False |
|
116 | 123 | |
|
117 | 124 | meta = parsemeta(store.revision(node))[0] |
@@ -264,7 +264,11 b' def prettyrepr(o):' | |||
|
264 | 264 | q1 = rs.find(b'<', p1 + 1) |
|
265 | 265 | if q1 < 0: |
|
266 | 266 | q1 = len(rs) |
|
267 | # pytype: disable=wrong-arg-count | |
|
268 | # TODO: figure out why pytype doesn't recognize the optional start | |
|
269 | # arg | |
|
267 | 270 | elif q1 > p1 + 1 and rs.startswith(b'=', q1 - 1): |
|
271 | # pytype: enable=wrong-arg-count | |
|
268 | 272 | # backtrack for ' field=<' |
|
269 | 273 | q0 = rs.rfind(b' ', p1 + 1, q1 - 1) |
|
270 | 274 | if q0 < 0: |
@@ -692,11 +696,11 b' def escapestr(s):' | |||
|
692 | 696 | s = bytes(s) |
|
693 | 697 | # call underlying function of s.encode('string_escape') directly for |
|
694 | 698 | # Python 3 compatibility |
|
695 | return codecs.escape_encode(s)[0] | |
|
699 | return codecs.escape_encode(s)[0] # pytype: disable=module-attr | |
|
696 | 700 | |
|
697 | 701 | |
|
698 | 702 | def unescapestr(s): |
|
699 | return codecs.escape_decode(s)[0] | |
|
703 | return codecs.escape_decode(s)[0] # pytype: disable=module-attr | |
|
700 | 704 | |
|
701 | 705 | |
|
702 | 706 | def forcebytestr(obj): |
@@ -18,11 +18,9 b' from . import (' | |||
|
18 | 18 | util, |
|
19 | 19 | wireprototypes, |
|
20 | 20 | wireprotov1server, |
|
21 | wireprotov2server, | |
|
22 | 21 | ) |
|
23 | 22 | from .interfaces import util as interfaceutil |
|
24 | 23 | from .utils import ( |
|
25 | cborutil, | |
|
26 | 24 | compression, |
|
27 | 25 | stringutil, |
|
28 | 26 | ) |
@@ -39,7 +37,6 b" HGTYPE2 = b'application/mercurial-0.2'" | |||
|
39 | 37 | HGERRTYPE = b'application/hg-error' |
|
40 | 38 | |
|
41 | 39 | SSHV1 = wireprototypes.SSHV1 |
|
42 | SSHV2 = wireprototypes.SSHV2 | |
|
43 | 40 | |
|
44 | 41 | |
|
45 | 42 | def decodevaluefromheaders(req, headerprefix): |
@@ -244,97 +241,6 b' def handlewsgirequest(rctx, req, res, ch' | |||
|
244 | 241 | return True |
|
245 | 242 | |
|
246 | 243 | |
|
247 | def _availableapis(repo): | |
|
248 | apis = set() | |
|
249 | ||
|
250 | # Registered APIs are made available via config options of the name of | |
|
251 | # the protocol. | |
|
252 | for k, v in API_HANDLERS.items(): | |
|
253 | section, option = v[b'config'] | |
|
254 | if repo.ui.configbool(section, option): | |
|
255 | apis.add(k) | |
|
256 | ||
|
257 | return apis | |
|
258 | ||
|
259 | ||
|
260 | def handlewsgiapirequest(rctx, req, res, checkperm): | |
|
261 | """Handle requests to /api/*.""" | |
|
262 | assert req.dispatchparts[0] == b'api' | |
|
263 | ||
|
264 | repo = rctx.repo | |
|
265 | ||
|
266 | # This whole URL space is experimental for now. But we want to | |
|
267 | # reserve the URL space. So, 404 all URLs if the feature isn't enabled. | |
|
268 | if not repo.ui.configbool(b'experimental', b'web.apiserver'): | |
|
269 | res.status = b'404 Not Found' | |
|
270 | res.headers[b'Content-Type'] = b'text/plain' | |
|
271 | res.setbodybytes(_(b'Experimental API server endpoint not enabled')) | |
|
272 | return | |
|
273 | ||
|
274 | # The URL space is /api/<protocol>/*. The structure of URLs under varies | |
|
275 | # by <protocol>. | |
|
276 | ||
|
277 | availableapis = _availableapis(repo) | |
|
278 | ||
|
279 | # Requests to /api/ list available APIs. | |
|
280 | if req.dispatchparts == [b'api']: | |
|
281 | res.status = b'200 OK' | |
|
282 | res.headers[b'Content-Type'] = b'text/plain' | |
|
283 | lines = [ | |
|
284 | _( | |
|
285 | b'APIs can be accessed at /api/<name>, where <name> can be ' | |
|
286 | b'one of the following:\n' | |
|
287 | ) | |
|
288 | ] | |
|
289 | if availableapis: | |
|
290 | lines.extend(sorted(availableapis)) | |
|
291 | else: | |
|
292 | lines.append(_(b'(no available APIs)\n')) | |
|
293 | res.setbodybytes(b'\n'.join(lines)) | |
|
294 | return | |
|
295 | ||
|
296 | proto = req.dispatchparts[1] | |
|
297 | ||
|
298 | if proto not in API_HANDLERS: | |
|
299 | res.status = b'404 Not Found' | |
|
300 | res.headers[b'Content-Type'] = b'text/plain' | |
|
301 | res.setbodybytes( | |
|
302 | _(b'Unknown API: %s\nKnown APIs: %s') | |
|
303 | % (proto, b', '.join(sorted(availableapis))) | |
|
304 | ) | |
|
305 | return | |
|
306 | ||
|
307 | if proto not in availableapis: | |
|
308 | res.status = b'404 Not Found' | |
|
309 | res.headers[b'Content-Type'] = b'text/plain' | |
|
310 | res.setbodybytes(_(b'API %s not enabled\n') % proto) | |
|
311 | return | |
|
312 | ||
|
313 | API_HANDLERS[proto][b'handler']( | |
|
314 | rctx, req, res, checkperm, req.dispatchparts[2:] | |
|
315 | ) | |
|
316 | ||
|
317 | ||
|
318 | # Maps API name to metadata so custom API can be registered. | |
|
319 | # Keys are: | |
|
320 | # | |
|
321 | # config | |
|
322 | # Config option that controls whether service is enabled. | |
|
323 | # handler | |
|
324 | # Callable receiving (rctx, req, res, checkperm, urlparts) that is called | |
|
325 | # when a request to this API is received. | |
|
326 | # apidescriptor | |
|
327 | # Callable receiving (req, repo) that is called to obtain an API | |
|
328 | # descriptor for this service. The response must be serializable to CBOR. | |
|
329 | API_HANDLERS = { | |
|
330 | wireprotov2server.HTTP_WIREPROTO_V2: { | |
|
331 | b'config': (b'experimental', b'web.api.http-v2'), | |
|
332 | b'handler': wireprotov2server.handlehttpv2request, | |
|
333 | b'apidescriptor': wireprotov2server.httpv2apidescriptor, | |
|
334 | }, | |
|
335 | } | |
|
336 | ||
|
337 | ||
|
338 | 244 | def _httpresponsetype(ui, proto, prefer_uncompressed): |
|
339 | 245 | """Determine the appropriate response type and compression settings. |
|
340 | 246 | |
@@ -371,55 +277,6 b' def _httpresponsetype(ui, proto, prefer_' | |||
|
371 | 277 | return HGTYPE, util.compengines[b'zlib'], opts |
|
372 | 278 | |
|
373 | 279 | |
|
374 | def processcapabilitieshandshake(repo, req, res, proto): | |
|
375 | """Called during a ?cmd=capabilities request. | |
|
376 | ||
|
377 | If the client is advertising support for a newer protocol, we send | |
|
378 | a CBOR response with information about available services. If no | |
|
379 | advertised services are available, we don't handle the request. | |
|
380 | """ | |
|
381 | # Fall back to old behavior unless the API server is enabled. | |
|
382 | if not repo.ui.configbool(b'experimental', b'web.apiserver'): | |
|
383 | return False | |
|
384 | ||
|
385 | clientapis = decodevaluefromheaders(req, b'X-HgUpgrade') | |
|
386 | protocaps = decodevaluefromheaders(req, b'X-HgProto') | |
|
387 | if not clientapis or not protocaps: | |
|
388 | return False | |
|
389 | ||
|
390 | # We currently only support CBOR responses. | |
|
391 | protocaps = set(protocaps.split(b' ')) | |
|
392 | if b'cbor' not in protocaps: | |
|
393 | return False | |
|
394 | ||
|
395 | descriptors = {} | |
|
396 | ||
|
397 | for api in sorted(set(clientapis.split()) & _availableapis(repo)): | |
|
398 | handler = API_HANDLERS[api] | |
|
399 | ||
|
400 | descriptorfn = handler.get(b'apidescriptor') | |
|
401 | if not descriptorfn: | |
|
402 | continue | |
|
403 | ||
|
404 | descriptors[api] = descriptorfn(req, repo) | |
|
405 | ||
|
406 | v1caps = wireprotov1server.dispatch(repo, proto, b'capabilities') | |
|
407 | assert isinstance(v1caps, wireprototypes.bytesresponse) | |
|
408 | ||
|
409 | m = { | |
|
410 | # TODO allow this to be configurable. | |
|
411 | b'apibase': b'api/', | |
|
412 | b'apis': descriptors, | |
|
413 | b'v1capabilities': v1caps.data, | |
|
414 | } | |
|
415 | ||
|
416 | res.status = b'200 OK' | |
|
417 | res.headers[b'Content-Type'] = b'application/mercurial-cbor' | |
|
418 | res.setbodybytes(b''.join(cborutil.streamencode(m))) | |
|
419 | ||
|
420 | return True | |
|
421 | ||
|
422 | ||
|
423 | 280 | def _callhttp(repo, req, res, proto, cmd): |
|
424 | 281 | # Avoid cycle involving hg module. |
|
425 | 282 | from .hgweb import common as hgwebcommon |
@@ -461,13 +318,6 b' def _callhttp(repo, req, res, proto, cmd' | |||
|
461 | 318 | |
|
462 | 319 | proto.checkperm(wireprotov1server.commands[cmd].permission) |
|
463 | 320 | |
|
464 | # Possibly handle a modern client wanting to switch protocols. | |
|
465 | if cmd == b'capabilities' and processcapabilitieshandshake( | |
|
466 | repo, req, res, proto | |
|
467 | ): | |
|
468 | ||
|
469 | return | |
|
470 | ||
|
471 | 321 | rsp = wireprotov1server.dispatch(repo, proto, cmd) |
|
472 | 322 | |
|
473 | 323 | if isinstance(rsp, bytes): |
@@ -596,17 +446,6 b' class sshv1protocolhandler(object):' | |||
|
596 | 446 | pass |
|
597 | 447 | |
|
598 | 448 | |
|
599 | class sshv2protocolhandler(sshv1protocolhandler): | |
|
600 | """Protocol handler for version 2 of the SSH protocol.""" | |
|
601 | ||
|
602 | @property | |
|
603 | def name(self): | |
|
604 | return wireprototypes.SSHV2 | |
|
605 | ||
|
606 | def addcapabilities(self, repo, caps): | |
|
607 | return caps | |
|
608 | ||
|
609 | ||
|
610 | 449 | def _runsshserver(ui, repo, fin, fout, ev): |
|
611 | 450 | # This function operates like a state machine of sorts. The following |
|
612 | 451 | # states are defined: |
@@ -616,19 +455,6 b' def _runsshserver(ui, repo, fin, fout, e' | |||
|
616 | 455 | # new lines. These commands are processed in this state, one command |
|
617 | 456 | # after the other. |
|
618 | 457 | # |
|
619 | # protov2-serving | |
|
620 | # Server is in protocol version 2 serving mode. | |
|
621 | # | |
|
622 | # upgrade-initial | |
|
623 | # The server is going to process an upgrade request. | |
|
624 | # | |
|
625 | # upgrade-v2-filter-legacy-handshake | |
|
626 | # The protocol is being upgraded to version 2. The server is expecting | |
|
627 | # the legacy handshake from version 1. | |
|
628 | # | |
|
629 | # upgrade-v2-finish | |
|
630 | # The upgrade to version 2 of the protocol is imminent. | |
|
631 | # | |
|
632 | 458 | # shutdown |
|
633 | 459 | # The server is shutting down, possibly in reaction to a client event. |
|
634 | 460 | # |
@@ -637,32 +463,9 b' def _runsshserver(ui, repo, fin, fout, e' | |||
|
637 | 463 | # protov1-serving -> shutdown |
|
638 | 464 | # When server receives an empty request or encounters another |
|
639 | 465 | # error. |
|
640 | # | |
|
641 | # protov1-serving -> upgrade-initial | |
|
642 | # An upgrade request line was seen. | |
|
643 | # | |
|
644 | # upgrade-initial -> upgrade-v2-filter-legacy-handshake | |
|
645 | # Upgrade to version 2 in progress. Server is expecting to | |
|
646 | # process a legacy handshake. | |
|
647 | # | |
|
648 | # upgrade-v2-filter-legacy-handshake -> shutdown | |
|
649 | # Client did not fulfill upgrade handshake requirements. | |
|
650 | # | |
|
651 | # upgrade-v2-filter-legacy-handshake -> upgrade-v2-finish | |
|
652 | # Client fulfilled version 2 upgrade requirements. Finishing that | |
|
653 | # upgrade. | |
|
654 | # | |
|
655 | # upgrade-v2-finish -> protov2-serving | |
|
656 | # Protocol upgrade to version 2 complete. Server can now speak protocol | |
|
657 | # version 2. | |
|
658 | # | |
|
659 | # protov2-serving -> protov1-serving | |
|
660 | # Ths happens by default since protocol version 2 is the same as | |
|
661 | # version 1 except for the handshake. | |
|
662 | 466 | |
|
663 | 467 | state = b'protov1-serving' |
|
664 | 468 | proto = sshv1protocolhandler(ui, fin, fout) |
|
665 | protoswitched = False | |
|
666 | 469 | |
|
667 | 470 | while not ev.is_set(): |
|
668 | 471 | if state == b'protov1-serving': |
@@ -674,21 +477,6 b' def _runsshserver(ui, repo, fin, fout, e' | |||
|
674 | 477 | state = b'shutdown' |
|
675 | 478 | continue |
|
676 | 479 | |
|
677 | # It looks like a protocol upgrade request. Transition state to | |
|
678 | # handle it. | |
|
679 | if request.startswith(b'upgrade '): | |
|
680 | if protoswitched: | |
|
681 | _sshv1respondooberror( | |
|
682 | fout, | |
|
683 | ui.ferr, | |
|
684 | b'cannot upgrade protocols multiple times', | |
|
685 | ) | |
|
686 | state = b'shutdown' | |
|
687 | continue | |
|
688 | ||
|
689 | state = b'upgrade-initial' | |
|
690 | continue | |
|
691 | ||
|
692 | 480 | available = wireprotov1server.commands.commandavailable( |
|
693 | 481 | request, proto |
|
694 | 482 | ) |
@@ -724,108 +512,6 b' def _runsshserver(ui, repo, fin, fout, e' | |||
|
724 | 512 | b'wire protocol command: %s' % rsp |
|
725 | 513 | ) |
|
726 | 514 | |
|
727 | # For now, protocol version 2 serving just goes back to version 1. | |
|
728 | elif state == b'protov2-serving': | |
|
729 | state = b'protov1-serving' | |
|
730 | continue | |
|
731 | ||
|
732 | elif state == b'upgrade-initial': | |
|
733 | # We should never transition into this state if we've switched | |
|
734 | # protocols. | |
|
735 | assert not protoswitched | |
|
736 | assert proto.name == wireprototypes.SSHV1 | |
|
737 | ||
|
738 | # Expected: upgrade <token> <capabilities> | |
|
739 | # If we get something else, the request is malformed. It could be | |
|
740 | # from a future client that has altered the upgrade line content. | |
|
741 | # We treat this as an unknown command. | |
|
742 | try: | |
|
743 | token, caps = request.split(b' ')[1:] | |
|
744 | except ValueError: | |
|
745 | _sshv1respondbytes(fout, b'') | |
|
746 | state = b'protov1-serving' | |
|
747 | continue | |
|
748 | ||
|
749 | # Send empty response if we don't support upgrading protocols. | |
|
750 | if not ui.configbool(b'experimental', b'sshserver.support-v2'): | |
|
751 | _sshv1respondbytes(fout, b'') | |
|
752 | state = b'protov1-serving' | |
|
753 | continue | |
|
754 | ||
|
755 | try: | |
|
756 | caps = urlreq.parseqs(caps) | |
|
757 | except ValueError: | |
|
758 | _sshv1respondbytes(fout, b'') | |
|
759 | state = b'protov1-serving' | |
|
760 | continue | |
|
761 | ||
|
762 | # We don't see an upgrade request to protocol version 2. Ignore | |
|
763 | # the upgrade request. | |
|
764 | wantedprotos = caps.get(b'proto', [b''])[0] | |
|
765 | if SSHV2 not in wantedprotos: | |
|
766 | _sshv1respondbytes(fout, b'') | |
|
767 | state = b'protov1-serving' | |
|
768 | continue | |
|
769 | ||
|
770 | # It looks like we can honor this upgrade request to protocol 2. | |
|
771 | # Filter the rest of the handshake protocol request lines. | |
|
772 | state = b'upgrade-v2-filter-legacy-handshake' | |
|
773 | continue | |
|
774 | ||
|
775 | elif state == b'upgrade-v2-filter-legacy-handshake': | |
|
776 | # Client should have sent legacy handshake after an ``upgrade`` | |
|
777 | # request. Expected lines: | |
|
778 | # | |
|
779 | # hello | |
|
780 | # between | |
|
781 | # pairs 81 | |
|
782 | # 0000...-0000... | |
|
783 | ||
|
784 | ok = True | |
|
785 | for line in (b'hello', b'between', b'pairs 81'): | |
|
786 | request = fin.readline()[:-1] | |
|
787 | ||
|
788 | if request != line: | |
|
789 | _sshv1respondooberror( | |
|
790 | fout, | |
|
791 | ui.ferr, | |
|
792 | b'malformed handshake protocol: missing %s' % line, | |
|
793 | ) | |
|
794 | ok = False | |
|
795 | state = b'shutdown' | |
|
796 | break | |
|
797 | ||
|
798 | if not ok: | |
|
799 | continue | |
|
800 | ||
|
801 | request = fin.read(81) | |
|
802 | if request != b'%s-%s' % (b'0' * 40, b'0' * 40): | |
|
803 | _sshv1respondooberror( | |
|
804 | fout, | |
|
805 | ui.ferr, | |
|
806 | b'malformed handshake protocol: ' | |
|
807 | b'missing between argument value', | |
|
808 | ) | |
|
809 | state = b'shutdown' | |
|
810 | continue | |
|
811 | ||
|
812 | state = b'upgrade-v2-finish' | |
|
813 | continue | |
|
814 | ||
|
815 | elif state == b'upgrade-v2-finish': | |
|
816 | # Send the upgrade response. | |
|
817 | fout.write(b'upgraded %s %s\n' % (token, SSHV2)) | |
|
818 | servercaps = wireprotov1server.capabilities(repo, proto) | |
|
819 | rsp = b'capabilities: %s' % servercaps.data | |
|
820 | fout.write(b'%d\n%s\n' % (len(rsp), rsp)) | |
|
821 | fout.flush() | |
|
822 | ||
|
823 | proto = sshv2protocolhandler(ui, fin, fout) | |
|
824 | protoswitched = True | |
|
825 | ||
|
826 | state = b'protov2-serving' | |
|
827 | continue | |
|
828 | ||
|
829 | 515 | elif state == b'shutdown': |
|
830 | 516 | break |
|
831 | 517 |
@@ -21,10 +21,6 b' from .utils import compression' | |||
|
21 | 21 | |
|
22 | 22 | # Names of the SSH protocol implementations. |
|
23 | 23 | SSHV1 = b'ssh-v1' |
|
24 | # These are advertised over the wire. Increment the counters at the end | |
|
25 | # to reflect BC breakages. | |
|
26 | SSHV2 = b'exp-ssh-v2-0003' | |
|
27 | HTTP_WIREPROTO_V2 = b'exp-http-v2-0003' | |
|
28 | 24 | |
|
29 | 25 | NARROWCAP = b'exp-narrow-1' |
|
30 | 26 | ELLIPSESCAP1 = b'exp-ellipses-1' |
@@ -37,19 +33,10 b' TRANSPORTS = {' | |||
|
37 | 33 | b'transport': b'ssh', |
|
38 | 34 | b'version': 1, |
|
39 | 35 | }, |
|
40 | SSHV2: { | |
|
41 | b'transport': b'ssh', | |
|
42 | # TODO mark as version 2 once all commands are implemented. | |
|
43 | b'version': 1, | |
|
44 | }, | |
|
45 | 36 | b'http-v1': { |
|
46 | 37 | b'transport': b'http', |
|
47 | 38 | b'version': 1, |
|
48 | 39 | }, |
|
49 | HTTP_WIREPROTO_V2: { | |
|
50 | b'transport': b'http', | |
|
51 | b'version': 2, | |
|
52 | }, | |
|
53 | 40 | } |
|
54 | 41 | |
|
55 | 42 |
@@ -147,12 +147,6 b' def wireprotocommand(name, args=None, pe' | |||
|
147 | 147 | k for k, v in wireprototypes.TRANSPORTS.items() if v[b'version'] == 1 |
|
148 | 148 | } |
|
149 | 149 | |
|
150 | # Because SSHv2 is a mirror of SSHv1, we allow "batch" commands through to | |
|
151 | # SSHv2. | |
|
152 | # TODO undo this hack when SSH is using the unified frame protocol. | |
|
153 | if name == b'batch': | |
|
154 | transports.add(wireprototypes.SSHV2) | |
|
155 | ||
|
156 | 150 | if permission not in (b'push', b'pull'): |
|
157 | 151 | raise error.ProgrammingError( |
|
158 | 152 | b'invalid wire protocol permission; ' |
@@ -306,7 +300,7 b' def _capabilities(repo, proto):' | |||
|
306 | 300 | if streamclone.allowservergeneration(repo): |
|
307 | 301 | if repo.ui.configbool(b'server', b'preferuncompressed'): |
|
308 | 302 | caps.append(b'stream-preferred') |
|
309 |
requiredformats = repo |
|
|
303 | requiredformats = streamclone.streamed_requirements(repo) | |
|
310 | 304 | # if our local revlogs are just revlogv1, add 'stream' cap |
|
311 | 305 | if not requiredformats - {requirementsmod.REVLOGV1_REQUIREMENT}: |
|
312 | 306 | caps.append(b'stream') |
@@ -4,16 +4,42 b'' | |||
|
4 | 4 | == Default Format Change == |
|
5 | 5 | |
|
6 | 6 | These changes affects newly created repositories (or new clone) done with |
|
7 |
Mercurial |
|
|
7 | Mercurial 6.1. | |
|
8 | ||
|
9 | The `share-safe` format variant is now enabled by default. It makes | |
|
10 | configuration and requirements more consistent across repository and their | |
|
11 | shares. This introduces a behavior change as shares from a repository using the | |
|
12 | new format will also use their main repository's configuration. | |
|
13 | ||
|
14 | See `hg help config.format.use-share-safe` for details about the feature and | |
|
15 | the available options for auto-upgrading existing shares. | |
|
8 | 16 | |
|
9 | 17 | |
|
10 | 18 | == New Experimental Features == |
|
11 | 19 | |
|
12 | 20 | == Bug Fixes == |
|
13 | 21 | |
|
22 | The `--no-check` and `--no-merge` now properly overwrite the behavior from `commands.update.check`. | |
|
14 | 23 | |
|
15 | 24 | == Backwards Compatibility Changes == |
|
16 | 25 | |
|
26 | The remotefilelog extension now requires an appropiate excludepattern | |
|
27 | for subrepositories. | |
|
28 | ||
|
29 | The labels passed to merge tools have changed slightly. Merge tools can get | |
|
30 | labels passed to them if you include `$labellocal`, `$labelbase`, and/or | |
|
31 | `$labelother` in the `merge-tool.<tool name>.args` configuration. These labels | |
|
32 | used to have some space-padding, and truncation to fit within 72 columns. Both | |
|
33 | the padding and the truncation has been removed. | |
|
34 | ||
|
35 | Some of the text in labels passed to merge tools has changed. For example, | |
|
36 | in conflicts while running `hg histedit`, the labels used to be "local", | |
|
37 | "base", and "histedit". They are now "already edited", | |
|
38 | "parent of current change", and "current change", respectively. | |
|
39 | ||
|
40 | The use of `share-safe`, means shares (of new repositories) will also use their | |
|
41 | main repository's configuration see the `Default Format Change` section | |
|
42 | for details. | |
|
17 | 43 | |
|
18 | 44 | == Internal API Changes == |
|
19 | 45 |
@@ -1,5 +1,7 b'' | |||
|
1 | 1 | # This file is automatically @generated by Cargo. |
|
2 | 2 | # It is not intended for manual editing. |
|
3 | version = 3 | |
|
4 | ||
|
3 | 5 | [[package]] |
|
4 | 6 | name = "adler" |
|
5 | 7 | version = "0.2.3" |
@@ -314,21 +316,19 b' dependencies = [' | |||
|
314 | 316 | |
|
315 | 317 | [[package]] |
|
316 | 318 | name = "format-bytes" |
|
317 |
version = "0. |
|
|
319 | version = "0.3.0" | |
|
318 | 320 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
319 | checksum = "1c4e89040c7fd7b4e6ba2820ac705a45def8a0c098ec78d170ae88f1ef1d5762" | |
|
321 | checksum = "48942366ef93975da38e175ac9e10068c6fc08ca9e85930d4f098f4d5b14c2fd" | |
|
320 | 322 | dependencies = [ |
|
321 | 323 | "format-bytes-macros", |
|
322 | "proc-macro-hack", | |
|
323 | 324 | ] |
|
324 | 325 | |
|
325 | 326 | [[package]] |
|
326 | 327 | name = "format-bytes-macros" |
|
327 |
version = "0. |
|
|
328 | version = "0.4.0" | |
|
328 | 329 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
329 | checksum = "b05089e341a0460449e2210c3bf7b61597860b07f0deae58da38dbed0a4c6b6d" | |
|
330 | checksum = "203aadebefcc73d12038296c228eabf830f99cba991b0032adf20e9fa6ce7e4f" | |
|
330 | 331 | dependencies = [ |
|
331 | "proc-macro-hack", | |
|
332 | 332 | "proc-macro2", |
|
333 | 333 | "quote", |
|
334 | 334 | "syn", |
@@ -356,6 +356,17 b' dependencies = [' | |||
|
356 | 356 | ] |
|
357 | 357 | |
|
358 | 358 | [[package]] |
|
359 | name = "getrandom" | |
|
360 | version = "0.2.4" | |
|
361 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
|
362 | checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" | |
|
363 | dependencies = [ | |
|
364 | "cfg-if 1.0.0", | |
|
365 | "libc", | |
|
366 | "wasi 0.10.0+wasi-snapshot-preview1", | |
|
367 | ] | |
|
368 | ||
|
369 | [[package]] | |
|
359 | 370 | name = "glob" |
|
360 | 371 | version = "0.3.0" |
|
361 | 372 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -371,6 +382,12 b' dependencies = [' | |||
|
371 | 382 | ] |
|
372 | 383 | |
|
373 | 384 | [[package]] |
|
385 | name = "hex" | |
|
386 | version = "0.4.3" | |
|
387 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
|
388 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" | |
|
389 | ||
|
390 | [[package]] | |
|
374 | 391 | name = "hg-core" |
|
375 | 392 | version = "0.1.0" |
|
376 | 393 | dependencies = [ |
@@ -391,7 +408,7 b' dependencies = [' | |||
|
391 | 408 | "memmap2", |
|
392 | 409 | "micro-timer", |
|
393 | 410 | "pretty_assertions", |
|
394 | "rand", | |
|
411 | "rand 0.8.4", | |
|
395 | 412 | "rand_distr", |
|
396 | 413 | "rand_pcg", |
|
397 | 414 | "rayon", |
@@ -415,6 +432,7 b' dependencies = [' | |||
|
415 | 432 | "libc", |
|
416 | 433 | "log", |
|
417 | 434 | "stable_deref_trait", |
|
435 | "vcsgraph", | |
|
418 | 436 | ] |
|
419 | 437 | |
|
420 | 438 | [[package]] |
@@ -442,7 +460,7 b' source = "registry+https://github.com/ru' | |||
|
442 | 460 | checksum = "3ca8957e71f04a205cb162508f9326aea04676c8dfd0711220190d6b83664f3f" |
|
443 | 461 | dependencies = [ |
|
444 | 462 | "bitmaps", |
|
445 | "rand_core", | |
|
463 | "rand_core 0.5.1", | |
|
446 | 464 | "rand_xoshiro", |
|
447 | 465 | "sized-chunks", |
|
448 | 466 | "typenum", |
@@ -480,6 +498,12 b' source = "registry+https://github.com/ru' | |||
|
480 | 498 | checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" |
|
481 | 499 | |
|
482 | 500 | [[package]] |
|
501 | name = "libm" | |
|
502 | version = "0.2.1" | |
|
503 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
|
504 | checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" | |
|
505 | ||
|
506 | [[package]] | |
|
483 | 507 | name = "libz-sys" |
|
484 | 508 | version = "1.1.2" |
|
485 | 509 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -579,6 +603,7 b' source = "registry+https://github.com/ru' | |||
|
579 | 603 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" |
|
580 | 604 | dependencies = [ |
|
581 | 605 | "autocfg", |
|
606 | "libm", | |
|
582 | 607 | ] |
|
583 | 608 | |
|
584 | 609 | [[package]] |
@@ -637,12 +662,6 b' dependencies = [' | |||
|
637 | 662 | ] |
|
638 | 663 | |
|
639 | 664 | [[package]] |
|
640 | name = "proc-macro-hack" | |
|
641 | version = "0.5.19" | |
|
642 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
|
643 | checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" | |
|
644 | ||
|
645 | [[package]] | |
|
646 | 665 | name = "proc-macro2" |
|
647 | 666 | version = "1.0.24" |
|
648 | 667 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -692,11 +711,23 b' version = "0.7.3"' | |||
|
692 | 711 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
693 | 712 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" |
|
694 | 713 | dependencies = [ |
|
695 | "getrandom", | |
|
714 | "getrandom 0.1.15", | |
|
696 | 715 | "libc", |
|
697 | "rand_chacha", | |
|
698 | "rand_core", | |
|
699 | "rand_hc", | |
|
716 | "rand_chacha 0.2.2", | |
|
717 | "rand_core 0.5.1", | |
|
718 | "rand_hc 0.2.0", | |
|
719 | ] | |
|
720 | ||
|
721 | [[package]] | |
|
722 | name = "rand" | |
|
723 | version = "0.8.4" | |
|
724 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
|
725 | checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" | |
|
726 | dependencies = [ | |
|
727 | "libc", | |
|
728 | "rand_chacha 0.3.1", | |
|
729 | "rand_core 0.6.3", | |
|
730 | "rand_hc 0.3.1", | |
|
700 | 731 | ] |
|
701 | 732 | |
|
702 | 733 | [[package]] |
@@ -706,7 +737,17 b' source = "registry+https://github.com/ru' | |||
|
706 | 737 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" |
|
707 | 738 | dependencies = [ |
|
708 | 739 | "ppv-lite86", |
|
709 | "rand_core", | |
|
740 | "rand_core 0.5.1", | |
|
741 | ] | |
|
742 | ||
|
743 | [[package]] | |
|
744 | name = "rand_chacha" | |
|
745 | version = "0.3.1" | |
|
746 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
|
747 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" | |
|
748 | dependencies = [ | |
|
749 | "ppv-lite86", | |
|
750 | "rand_core 0.6.3", | |
|
710 | 751 | ] |
|
711 | 752 | |
|
712 | 753 | [[package]] |
@@ -715,16 +756,26 b' version = "0.5.1"' | |||
|
715 | 756 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
716 | 757 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" |
|
717 | 758 | dependencies = [ |
|
718 | "getrandom", | |
|
759 | "getrandom 0.1.15", | |
|
760 | ] | |
|
761 | ||
|
762 | [[package]] | |
|
763 | name = "rand_core" | |
|
764 | version = "0.6.3" | |
|
765 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
|
766 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" | |
|
767 | dependencies = [ | |
|
768 | "getrandom 0.2.4", | |
|
719 | 769 | ] |
|
720 | 770 | |
|
721 | 771 | [[package]] |
|
722 | 772 | name = "rand_distr" |
|
723 |
version = "0. |
|
|
773 | version = "0.4.2" | |
|
724 | 774 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
725 | checksum = "96977acbdd3a6576fb1d27391900035bf3863d4a16422973a409b488cf29ffb2" | |
|
775 | checksum = "964d548f8e7d12e102ef183a0de7e98180c9f8729f555897a857b96e48122d2f" | |
|
726 | 776 | dependencies = [ |
|
727 | "rand", | |
|
777 | "num-traits", | |
|
778 | "rand 0.8.4", | |
|
728 | 779 | ] |
|
729 | 780 | |
|
730 | 781 | [[package]] |
@@ -733,16 +784,25 b' version = "0.2.0"' | |||
|
733 | 784 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
734 | 785 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" |
|
735 | 786 | dependencies = [ |
|
736 | "rand_core", | |
|
787 | "rand_core 0.5.1", | |
|
788 | ] | |
|
789 | ||
|
790 | [[package]] | |
|
791 | name = "rand_hc" | |
|
792 | version = "0.3.1" | |
|
793 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
|
794 | checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" | |
|
795 | dependencies = [ | |
|
796 | "rand_core 0.6.3", | |
|
737 | 797 | ] |
|
738 | 798 | |
|
739 | 799 | [[package]] |
|
740 | 800 | name = "rand_pcg" |
|
741 |
version = "0. |
|
|
801 | version = "0.3.1" | |
|
742 | 802 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
743 | checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" | |
|
803 | checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" | |
|
744 | 804 | dependencies = [ |
|
745 | "rand_core", | |
|
805 | "rand_core 0.6.3", | |
|
746 | 806 | ] |
|
747 | 807 | |
|
748 | 808 | [[package]] |
@@ -751,7 +811,7 b' version = "0.4.0"' | |||
|
751 | 811 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
752 | 812 | checksum = "a9fcdd2e881d02f1d9390ae47ad8e5696a9e4be7b547a1da2afbc61973217004" |
|
753 | 813 | dependencies = [ |
|
754 | "rand_core", | |
|
814 | "rand_core 0.5.1", | |
|
755 | 815 | ] |
|
756 | 816 | |
|
757 | 817 | [[package]] |
@@ -816,6 +876,7 b' dependencies = [' | |||
|
816 | 876 | name = "rhg" |
|
817 | 877 | version = "0.1.0" |
|
818 | 878 | dependencies = [ |
|
879 | "atty", | |
|
819 | 880 | "chrono", |
|
820 | 881 | "clap", |
|
821 | 882 | "derive_more", |
@@ -905,7 +966,7 b' checksum = "7a6e24d9338a0a5be79593e2fa15' | |||
|
905 | 966 | dependencies = [ |
|
906 | 967 | "cfg-if 0.1.10", |
|
907 | 968 | "libc", |
|
908 | "rand", | |
|
969 | "rand 0.7.3", | |
|
909 | 970 | "redox_syscall", |
|
910 | 971 | "remove_dir_all", |
|
911 | 972 | "winapi", |
@@ -956,7 +1017,7 b' source = "registry+https://github.com/ru' | |||
|
956 | 1017 | checksum = "04f8ab788026715fa63b31960869617cba39117e520eb415b0139543e325ab59" |
|
957 | 1018 | dependencies = [ |
|
958 | 1019 | "cfg-if 0.1.10", |
|
959 | "rand", | |
|
1020 | "rand 0.7.3", | |
|
960 | 1021 | "static_assertions", |
|
961 | 1022 | ] |
|
962 | 1023 | |
@@ -995,6 +1056,17 b' source = "registry+https://github.com/ru' | |||
|
995 | 1056 | checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb" |
|
996 | 1057 | |
|
997 | 1058 | [[package]] |
|
1059 | name = "vcsgraph" | |
|
1060 | version = "0.2.0" | |
|
1061 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
|
1062 | checksum = "4cb68c231e2575f7503a7c19213875f9d4ec2e84e963a56ce3de4b6bee351ef7" | |
|
1063 | dependencies = [ | |
|
1064 | "hex", | |
|
1065 | "rand 0.7.3", | |
|
1066 | "sha-1", | |
|
1067 | ] | |
|
1068 | ||
|
1069 | [[package]] | |
|
998 | 1070 | name = "vec_map" |
|
999 | 1071 | version = "0.8.2" |
|
1000 | 1072 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -18,9 +18,9 b' im-rc = "15.0.*"' | |||
|
18 | 18 | itertools = "0.9" |
|
19 | 19 | lazy_static = "1.4.0" |
|
20 | 20 | libc = "0.2" |
|
21 |
rand = "0. |
|
|
22 |
rand_pcg = "0. |
|
|
23 |
rand_distr = "0. |
|
|
21 | rand = "0.8.4" | |
|
22 | rand_pcg = "0.3.1" | |
|
23 | rand_distr = "0.4.2" | |
|
24 | 24 | rayon = "1.3.0" |
|
25 | 25 | regex = "1.3.9" |
|
26 | 26 | sha-1 = "0.9.6" |
@@ -33,7 +33,7 b' micro-timer = "0.3.0"' | |||
|
33 | 33 | log = "0.4.8" |
|
34 | 34 | memmap2 = {version = "0.4", features = ["stable_deref_trait"]} |
|
35 | 35 | zstd = "0.5.3" |
|
36 |
format-bytes = "0. |
|
|
36 | format-bytes = "0.3.0" | |
|
37 | 37 | |
|
38 | 38 | # We don't use the `miniz-oxide` backend to not change rhg benchmarks and until |
|
39 | 39 | # we have a clearer view of which backend is the fastest. |
@@ -26,15 +26,6 b' pub struct AncestorsIterator<G: Graph> {' | |||
|
26 | 26 | stoprev: Revision, |
|
27 | 27 | } |
|
28 | 28 | |
|
29 | /// Lazy ancestors set, backed by AncestorsIterator | |
|
30 | pub struct LazyAncestors<G: Graph + Clone> { | |
|
31 | graph: G, | |
|
32 | containsiter: AncestorsIterator<G>, | |
|
33 | initrevs: Vec<Revision>, | |
|
34 | stoprev: Revision, | |
|
35 | inclusive: bool, | |
|
36 | } | |
|
37 | ||
|
38 | 29 | pub struct MissingAncestors<G: Graph> { |
|
39 | 30 | graph: G, |
|
40 | 31 | bases: HashSet<Revision>, |
@@ -165,49 +156,6 b' impl<G: Graph> Iterator for AncestorsIte' | |||
|
165 | 156 | } |
|
166 | 157 | } |
|
167 | 158 | |
|
168 | impl<G: Graph + Clone> LazyAncestors<G> { | |
|
169 | pub fn new( | |
|
170 | graph: G, | |
|
171 | initrevs: impl IntoIterator<Item = Revision>, | |
|
172 | stoprev: Revision, | |
|
173 | inclusive: bool, | |
|
174 | ) -> Result<Self, GraphError> { | |
|
175 | let v: Vec<Revision> = initrevs.into_iter().collect(); | |
|
176 | Ok(LazyAncestors { | |
|
177 | graph: graph.clone(), | |
|
178 | containsiter: AncestorsIterator::new( | |
|
179 | graph, | |
|
180 | v.iter().cloned(), | |
|
181 | stoprev, | |
|
182 | inclusive, | |
|
183 | )?, | |
|
184 | initrevs: v, | |
|
185 | stoprev, | |
|
186 | inclusive, | |
|
187 | }) | |
|
188 | } | |
|
189 | ||
|
190 | pub fn contains(&mut self, rev: Revision) -> Result<bool, GraphError> { | |
|
191 | self.containsiter.contains(rev) | |
|
192 | } | |
|
193 | ||
|
194 | pub fn is_empty(&self) -> bool { | |
|
195 | self.containsiter.is_empty() | |
|
196 | } | |
|
197 | ||
|
198 | pub fn iter(&self) -> AncestorsIterator<G> { | |
|
199 | // the arguments being the same as for self.containsiter, we know | |
|
200 | // for sure that AncestorsIterator constructor can't fail | |
|
201 | AncestorsIterator::new( | |
|
202 | self.graph.clone(), | |
|
203 | self.initrevs.iter().cloned(), | |
|
204 | self.stoprev, | |
|
205 | self.inclusive, | |
|
206 | ) | |
|
207 | .unwrap() | |
|
208 | } | |
|
209 | } | |
|
210 | ||
|
211 | 159 | impl<G: Graph> MissingAncestors<G> { |
|
212 | 160 | pub fn new(graph: G, bases: impl IntoIterator<Item = Revision>) -> Self { |
|
213 | 161 | let mut created = MissingAncestors { |
@@ -550,39 +498,6 b' mod tests {' | |||
|
550 | 498 | } |
|
551 | 499 | |
|
552 | 500 | #[test] |
|
553 | fn test_lazy_iter_contains() { | |
|
554 | let mut lazy = | |
|
555 | LazyAncestors::new(SampleGraph, vec![11, 13], 0, false).unwrap(); | |
|
556 | ||
|
557 | let revs: Vec<Revision> = lazy.iter().map(|r| r.unwrap()).collect(); | |
|
558 | // compare with iterator tests on the same initial revisions | |
|
559 | assert_eq!(revs, vec![8, 7, 4, 3, 2, 1, 0]); | |
|
560 | ||
|
561 | // contains() results are correct, unaffected by the fact that | |
|
562 | // we consumed entirely an iterator out of lazy | |
|
563 | assert_eq!(lazy.contains(2), Ok(true)); | |
|
564 | assert_eq!(lazy.contains(9), Ok(false)); | |
|
565 | } | |
|
566 | ||
|
567 | #[test] | |
|
568 | fn test_lazy_contains_iter() { | |
|
569 | let mut lazy = | |
|
570 | LazyAncestors::new(SampleGraph, vec![11, 13], 0, false).unwrap(); // reminder: [8, 7, 4, 3, 2, 1, 0] | |
|
571 | ||
|
572 | assert_eq!(lazy.contains(2), Ok(true)); | |
|
573 | assert_eq!(lazy.contains(6), Ok(false)); | |
|
574 | ||
|
575 | // after consumption of 2 by the inner iterator, results stay | |
|
576 | // consistent | |
|
577 | assert_eq!(lazy.contains(2), Ok(true)); | |
|
578 | assert_eq!(lazy.contains(5), Ok(false)); | |
|
579 | ||
|
580 | // iter() still gives us a fresh iterator | |
|
581 | let revs: Vec<Revision> = lazy.iter().map(|r| r.unwrap()).collect(); | |
|
582 | assert_eq!(revs, vec![8, 7, 4, 3, 2, 1, 0]); | |
|
583 | } | |
|
584 | ||
|
585 | #[test] | |
|
586 | 501 | /// Test constructor, add/get bases and heads |
|
587 | 502 | fn test_missing_bases() -> Result<(), GraphError> { |
|
588 | 503 | let mut missing_ancestors = |
@@ -13,4 +13,4 b' mod config;' | |||
|
13 | 13 | mod layer; |
|
14 | 14 | mod values; |
|
15 | 15 | pub use config::{Config, ConfigSource, ConfigValueParseError}; |
|
16 | pub use layer::{ConfigError, ConfigParseError}; | |
|
16 | pub use layer::{ConfigError, ConfigOrigin, ConfigParseError}; |
@@ -84,6 +84,11 b' impl fmt::Display for ConfigValueParseEr' | |||
|
84 | 84 | } |
|
85 | 85 | |
|
86 | 86 | impl Config { |
|
87 | /// The configuration to use when printing configuration-loading errors | |
|
88 | pub fn empty() -> Self { | |
|
89 | Self { layers: Vec::new() } | |
|
90 | } | |
|
91 | ||
|
87 | 92 | /// Load system and user configuration from various files. |
|
88 | 93 | /// |
|
89 | 94 | /// This is also affected by some environment variables. |
@@ -133,13 +138,19 b' impl Config {' | |||
|
133 | 138 | Ok(config) |
|
134 | 139 | } |
|
135 | 140 | |
|
136 |
pub fn load_cli_args |
|
|
141 | pub fn load_cli_args( | |
|
137 | 142 | &mut self, |
|
138 | 143 | cli_config_args: impl IntoIterator<Item = impl AsRef<[u8]>>, |
|
144 | color_arg: Option<Vec<u8>>, | |
|
139 | 145 | ) -> Result<(), ConfigError> { |
|
140 | 146 | if let Some(layer) = ConfigLayer::parse_cli_args(cli_config_args)? { |
|
141 | 147 | self.layers.push(layer) |
|
142 | 148 | } |
|
149 | if let Some(arg) = color_arg { | |
|
150 | let mut layer = ConfigLayer::new(ConfigOrigin::CommandLineColor); | |
|
151 | layer.add(b"ui"[..].into(), b"color"[..].into(), arg, None); | |
|
152 | self.layers.push(layer) | |
|
153 | } | |
|
143 | 154 | Ok(()) |
|
144 | 155 | } |
|
145 | 156 | |
@@ -361,6 +372,15 b' impl Config {' | |||
|
361 | 372 | Ok(self.get_option(section, item)?.unwrap_or(false)) |
|
362 | 373 | } |
|
363 | 374 | |
|
375 | /// Returns `true` if the extension is enabled, `false` otherwise | |
|
376 | pub fn is_extension_enabled(&self, extension: &[u8]) -> bool { | |
|
377 | let value = self.get(b"extensions", extension); | |
|
378 | match value { | |
|
379 | Some(c) => !c.starts_with(b"!"), | |
|
380 | None => false, | |
|
381 | } | |
|
382 | } | |
|
383 | ||
|
364 | 384 | /// If there is an `item` value in `section`, parse and return a list of |
|
365 | 385 | /// byte strings. |
|
366 | 386 | pub fn get_list( |
@@ -377,6 +397,16 b' impl Config {' | |||
|
377 | 397 | .map(|(_, value)| value.bytes.as_ref()) |
|
378 | 398 | } |
|
379 | 399 | |
|
400 | /// Returns the raw value bytes of the first one found, or `None`. | |
|
401 | pub fn get_with_origin( | |
|
402 | &self, | |
|
403 | section: &[u8], | |
|
404 | item: &[u8], | |
|
405 | ) -> Option<(&[u8], &ConfigOrigin)> { | |
|
406 | self.get_inner(section, item) | |
|
407 | .map(|(layer, value)| (value.bytes.as_ref(), &layer.origin)) | |
|
408 | } | |
|
409 | ||
|
380 | 410 | /// Returns the layer and the value of the first one found, or `None`. |
|
381 | 411 | fn get_inner( |
|
382 | 412 | &self, |
@@ -402,6 +432,66 b' impl Config {' | |||
|
402 | 432 | .collect() |
|
403 | 433 | } |
|
404 | 434 | |
|
435 | /// Returns whether any key is defined in the given section | |
|
436 | pub fn has_non_empty_section(&self, section: &[u8]) -> bool { | |
|
437 | self.layers | |
|
438 | .iter() | |
|
439 | .any(|layer| layer.has_non_empty_section(section)) | |
|
440 | } | |
|
441 | ||
|
442 | /// Yields (key, value) pairs for everything in the given section | |
|
443 | pub fn iter_section<'a>( | |
|
444 | &'a self, | |
|
445 | section: &'a [u8], | |
|
446 | ) -> impl Iterator<Item = (&[u8], &[u8])> + 'a { | |
|
447 | // TODO: Use `Iterator`’s `.peekable()` when its `peek_mut` is | |
|
448 | // available: | |
|
449 | // https://doc.rust-lang.org/nightly/std/iter/struct.Peekable.html#method.peek_mut | |
|
450 | struct Peekable<I: Iterator> { | |
|
451 | iter: I, | |
|
452 | /// Remember a peeked value, even if it was None. | |
|
453 | peeked: Option<Option<I::Item>>, | |
|
454 | } | |
|
455 | ||
|
456 | impl<I: Iterator> Peekable<I> { | |
|
457 | fn new(iter: I) -> Self { | |
|
458 | Self { iter, peeked: None } | |
|
459 | } | |
|
460 | ||
|
461 | fn next(&mut self) { | |
|
462 | self.peeked = None | |
|
463 | } | |
|
464 | ||
|
465 | fn peek_mut(&mut self) -> Option<&mut I::Item> { | |
|
466 | let iter = &mut self.iter; | |
|
467 | self.peeked.get_or_insert_with(|| iter.next()).as_mut() | |
|
468 | } | |
|
469 | } | |
|
470 | ||
|
471 | // Deduplicate keys redefined in multiple layers | |
|
472 | let mut keys_already_seen = HashSet::new(); | |
|
473 | let mut key_is_new = | |
|
474 | move |&(key, _value): &(&'a [u8], &'a [u8])| -> bool { | |
|
475 | keys_already_seen.insert(key) | |
|
476 | }; | |
|
477 | // This is similar to `flat_map` + `filter_map`, except with a single | |
|
478 | // closure that owns `key_is_new` (and therefore the | |
|
479 | // `keys_already_seen` set): | |
|
480 | let mut layer_iters = Peekable::new( | |
|
481 | self.layers | |
|
482 | .iter() | |
|
483 | .rev() | |
|
484 | .map(move |layer| layer.iter_section(section)), | |
|
485 | ); | |
|
486 | std::iter::from_fn(move || loop { | |
|
487 | if let Some(pair) = layer_iters.peek_mut()?.find(&mut key_is_new) { | |
|
488 | return Some(pair); | |
|
489 | } else { | |
|
490 | layer_iters.next(); | |
|
491 | } | |
|
492 | }) | |
|
493 | } | |
|
494 | ||
|
405 | 495 | /// Get raw values bytes from all layers (even untrusted ones) in order |
|
406 | 496 | /// of precedence. |
|
407 | 497 | #[cfg(test)] |
@@ -127,6 +127,24 b' impl ConfigLayer {' | |||
|
127 | 127 | .flat_map(|section| section.keys().map(|vec| &**vec)) |
|
128 | 128 | } |
|
129 | 129 | |
|
130 | /// Returns the (key, value) pairs defined in the given section | |
|
131 | pub fn iter_section<'layer>( | |
|
132 | &'layer self, | |
|
133 | section: &[u8], | |
|
134 | ) -> impl Iterator<Item = (&'layer [u8], &'layer [u8])> { | |
|
135 | self.sections | |
|
136 | .get(section) | |
|
137 | .into_iter() | |
|
138 | .flat_map(|section| section.iter().map(|(k, v)| (&**k, &*v.bytes))) | |
|
139 | } | |
|
140 | ||
|
141 | /// Returns whether any key is defined in the given section | |
|
142 | pub fn has_non_empty_section(&self, section: &[u8]) -> bool { | |
|
143 | self.sections | |
|
144 | .get(section) | |
|
145 | .map_or(false, |section| !section.is_empty()) | |
|
146 | } | |
|
147 | ||
|
130 | 148 | pub fn is_empty(&self) -> bool { |
|
131 | 149 | self.sections.is_empty() |
|
132 | 150 | } |
@@ -277,16 +295,17 b' pub struct ConfigValue {' | |||
|
277 | 295 | pub line: Option<usize>, |
|
278 | 296 | } |
|
279 | 297 | |
|
280 | #[derive(Clone, Debug)] | |
|
298 | #[derive(Clone, Debug, PartialEq, Eq)] | |
|
281 | 299 | pub enum ConfigOrigin { |
|
282 | 300 | /// From a configuration file |
|
283 | 301 | File(PathBuf), |
|
284 | 302 | /// From a `--config` CLI argument |
|
285 | 303 | CommandLine, |
|
304 | /// From a `--color` CLI argument | |
|
305 | CommandLineColor, | |
|
286 | 306 | /// From environment variables like `$PAGER` or `$EDITOR` |
|
287 | 307 | Environment(Vec<u8>), |
|
288 | /* TODO cli | |
|
289 | * TODO defaults (configitems.py) | |
|
308 | /* TODO defaults (configitems.py) | |
|
290 | 309 | * TODO extensions |
|
291 | 310 | * TODO Python resources? |
|
292 | 311 | * Others? */ |
@@ -300,6 +319,7 b' impl DisplayBytes for ConfigOrigin {' | |||
|
300 | 319 | match self { |
|
301 | 320 | ConfigOrigin::File(p) => out.write_all(&get_bytes_from_path(p)), |
|
302 | 321 | ConfigOrigin::CommandLine => out.write_all(b"--config"), |
|
322 | ConfigOrigin::CommandLineColor => out.write_all(b"--color"), | |
|
303 | 323 | ConfigOrigin::Environment(e) => write_bytes!(out, b"${}", e), |
|
304 | 324 | } |
|
305 | 325 | } |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file renamed from tests/badserverext.py to tests/testlib/badserverext.py | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
This diff has been collapsed as it changes many lines, (804 lines changed) Show them Hide them |
|
1 | NO CONTENT: file was removed | |
This diff has been collapsed as it changes many lines, (576 lines changed) Show them Hide them |
|
1 | NO CONTENT: file was removed | |
This diff has been collapsed as it changes many lines, (1613 lines changed) Show them Hide them |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
General Comments 0
You need to be logged in to leave comments.
Login now