##// END OF EJS Templates
branching: merge with stable
marmoute -
r50994:0f0880c8 merge default
parent child Browse files
Show More
@@ -1,305 +1,305 b''
1 # If you want to change PREFIX, do not just edit it below. The changed
1 # If you want to change PREFIX, do not just edit it below. The changed
2 # value wont get passed on to recursive make calls. You should instead
2 # value wont get passed on to recursive make calls. You should instead
3 # override the variable on the command like:
3 # override the variable on the command like:
4 #
4 #
5 # % make PREFIX=/opt/ install
5 # % make PREFIX=/opt/ install
6
6
7 export PREFIX=/usr/local
7 export PREFIX=/usr/local
8
8
9 # Default to Python 3.
9 # Default to Python 3.
10 #
10 #
11 # Windows ships Python 3 as `python.exe`, which may not be on PATH. py.exe is.
11 # Windows ships Python 3 as `python.exe`, which may not be on PATH. py.exe is.
12 ifeq ($(OS),Windows_NT)
12 ifeq ($(OS),Windows_NT)
13 PYTHON?=py -3
13 PYTHON?=py -3
14 else
14 else
15 PYTHON?=python3
15 PYTHON?=python3
16 endif
16 endif
17
17
18 PYOXIDIZER?=pyoxidizer
18 PYOXIDIZER?=pyoxidizer
19
19
20 $(eval HGROOT := $(shell pwd))
20 $(eval HGROOT := $(shell pwd))
21 HGPYTHONS ?= $(HGROOT)/build/pythons
21 HGPYTHONS ?= $(HGROOT)/build/pythons
22 PURE=
22 PURE=
23 PYFILESCMD=find mercurial hgext doc -name '*.py'
23 PYFILESCMD=find mercurial hgext doc -name '*.py'
24 PYFILES:=$(shell $(PYFILESCMD))
24 PYFILES:=$(shell $(PYFILESCMD))
25 DOCFILES=mercurial/helptext/*.txt
25 DOCFILES=mercurial/helptext/*.txt
26 export LANGUAGE=C
26 export LANGUAGE=C
27 export LC_ALL=C
27 export LC_ALL=C
28 TESTFLAGS ?= $(shell echo $$HGTESTFLAGS)
28 TESTFLAGS ?= $(shell echo $$HGTESTFLAGS)
29 OSXVERSIONFLAGS ?= $(shell echo $$OSXVERSIONFLAGS)
29 OSXVERSIONFLAGS ?= $(shell echo $$OSXVERSIONFLAGS)
30 CARGO = cargo
30 CARGO = cargo
31
31
32 # Set this to e.g. "mingw32" to use a non-default compiler.
32 # Set this to e.g. "mingw32" to use a non-default compiler.
33 COMPILER=
33 COMPILER=
34
34
35 COMPILERFLAG_tmp_ =
35 COMPILERFLAG_tmp_ =
36 COMPILERFLAG_tmp_${COMPILER} ?= -c $(COMPILER)
36 COMPILERFLAG_tmp_${COMPILER} ?= -c $(COMPILER)
37 COMPILERFLAG=${COMPILERFLAG_tmp_${COMPILER}}
37 COMPILERFLAG=${COMPILERFLAG_tmp_${COMPILER}}
38
38
39 help:
39 help:
40 @echo 'Commonly used make targets:'
40 @echo 'Commonly used make targets:'
41 @echo ' all - build program and documentation'
41 @echo ' all - build program and documentation'
42 @echo ' install - install program and man pages to $$PREFIX ($(PREFIX))'
42 @echo ' install - install program and man pages to $$PREFIX ($(PREFIX))'
43 @echo ' install-home - install with setup.py install --home=$$HOME ($(HOME))'
43 @echo ' install-home - install with setup.py install --home=$$HOME ($(HOME))'
44 @echo ' local - build for inplace usage'
44 @echo ' local - build for inplace usage'
45 @echo ' tests - run all tests in the automatic test suite'
45 @echo ' tests - run all tests in the automatic test suite'
46 @echo ' test-foo - run only specified tests (e.g. test-merge1.t)'
46 @echo ' test-foo - run only specified tests (e.g. test-merge1.t)'
47 @echo ' dist - run all tests and create a source tarball in dist/'
47 @echo ' dist - run all tests and create a source tarball in dist/'
48 @echo ' clean - remove files created by other targets'
48 @echo ' clean - remove files created by other targets'
49 @echo ' (except installed files or dist source tarball)'
49 @echo ' (except installed files or dist source tarball)'
50 @echo ' update-pot - update i18n/hg.pot'
50 @echo ' update-pot - update i18n/hg.pot'
51 @echo
51 @echo
52 @echo 'Example for a system-wide installation under /usr/local:'
52 @echo 'Example for a system-wide installation under /usr/local:'
53 @echo ' make all && su -c "make install" && hg version'
53 @echo ' make all && su -c "make install" && hg version'
54 @echo
54 @echo
55 @echo 'Example for a local installation (usable in this directory):'
55 @echo 'Example for a local installation (usable in this directory):'
56 @echo ' make local && ./hg version'
56 @echo ' make local && ./hg version'
57
57
58 all: build doc
58 all: build doc
59
59
60 local:
60 local:
61 $(PYTHON) setup.py $(PURE) \
61 MERCURIAL_SETUP_MAKE_LOCAL=1 $(PYTHON) setup.py $(PURE) \
62 build_py -c -d . \
62 build_py -c -d . \
63 build_ext $(COMPILERFLAG) -i \
63 build_ext $(COMPILERFLAG) -i \
64 build_hgexe $(COMPILERFLAG) -i \
64 build_hgexe $(COMPILERFLAG) -i \
65 build_mo
65 build_mo
66 env HGRCPATH= $(PYTHON) hg version
66 env HGRCPATH= $(PYTHON) hg version
67
67
68 build:
68 build:
69 $(PYTHON) setup.py $(PURE) build $(COMPILERFLAG)
69 $(PYTHON) setup.py $(PURE) build $(COMPILERFLAG)
70
70
71 build-chg:
71 build-chg:
72 make -C contrib/chg
72 make -C contrib/chg
73
73
74 build-rhg:
74 build-rhg:
75 (cd rust/rhg; cargo build --release)
75 (cd rust/rhg; cargo build --release)
76
76
77 wheel:
77 wheel:
78 FORCE_SETUPTOOLS=1 $(PYTHON) setup.py $(PURE) bdist_wheel $(COMPILERFLAG)
78 FORCE_SETUPTOOLS=1 $(PYTHON) setup.py $(PURE) bdist_wheel $(COMPILERFLAG)
79
79
80 doc:
80 doc:
81 $(MAKE) -C doc
81 $(MAKE) -C doc
82
82
83 cleanbutpackages:
83 cleanbutpackages:
84 rm -f hg.exe
84 rm -f hg.exe
85 -$(PYTHON) setup.py clean --all # ignore errors from this command
85 -$(PYTHON) setup.py clean --all # ignore errors from this command
86 find contrib doc hgext hgext3rd i18n mercurial tests hgdemandimport \
86 find contrib doc hgext hgext3rd i18n mercurial tests hgdemandimport \
87 \( -name '*.py[cdo]' -o -name '*.so' \) -exec rm -f '{}' ';'
87 \( -name '*.py[cdo]' -o -name '*.so' \) -exec rm -f '{}' ';'
88 rm -f MANIFEST MANIFEST.in hgext/__index__.py tests/*.err
88 rm -f MANIFEST MANIFEST.in hgext/__index__.py tests/*.err
89 rm -f mercurial/__modulepolicy__.py
89 rm -f mercurial/__modulepolicy__.py
90 if test -d .hg; then rm -f mercurial/__version__.py; fi
90 if test -d .hg; then rm -f mercurial/__version__.py; fi
91 rm -rf build mercurial/locale
91 rm -rf build mercurial/locale
92 $(MAKE) -C doc clean
92 $(MAKE) -C doc clean
93 $(MAKE) -C contrib/chg distclean
93 $(MAKE) -C contrib/chg distclean
94 rm -rf rust/target
94 rm -rf rust/target
95 rm -f mercurial/rustext.so
95 rm -f mercurial/rustext.so
96
96
97 clean: cleanbutpackages
97 clean: cleanbutpackages
98 rm -rf packages
98 rm -rf packages
99
99
100 install: install-bin install-doc
100 install: install-bin install-doc
101
101
102 install-bin: build
102 install-bin: build
103 $(PYTHON) setup.py $(PURE) install --root="$(DESTDIR)/" --prefix="$(PREFIX)" --force
103 $(PYTHON) setup.py $(PURE) install --root="$(DESTDIR)/" --prefix="$(PREFIX)" --force
104
104
105 install-chg: build-chg
105 install-chg: build-chg
106 make -C contrib/chg install PREFIX="$(PREFIX)"
106 make -C contrib/chg install PREFIX="$(PREFIX)"
107
107
108 install-doc: doc
108 install-doc: doc
109 cd doc && $(MAKE) $(MFLAGS) install
109 cd doc && $(MAKE) $(MFLAGS) install
110
110
111 install-home: install-home-bin install-home-doc
111 install-home: install-home-bin install-home-doc
112
112
113 install-home-bin: build
113 install-home-bin: build
114 $(PYTHON) setup.py $(PURE) install --home="$(HOME)" --prefix="" --force
114 $(PYTHON) setup.py $(PURE) install --home="$(HOME)" --prefix="" --force
115
115
116 install-home-doc: doc
116 install-home-doc: doc
117 cd doc && $(MAKE) $(MFLAGS) PREFIX="$(HOME)" install
117 cd doc && $(MAKE) $(MFLAGS) PREFIX="$(HOME)" install
118
118
119 install-rhg: build-rhg
119 install-rhg: build-rhg
120 install -m 755 rust/target/release/rhg "$(PREFIX)"/bin/
120 install -m 755 rust/target/release/rhg "$(PREFIX)"/bin/
121
121
122 MANIFEST-doc:
122 MANIFEST-doc:
123 $(MAKE) -C doc MANIFEST
123 $(MAKE) -C doc MANIFEST
124
124
125 MANIFEST.in: MANIFEST-doc
125 MANIFEST.in: MANIFEST-doc
126 hg manifest | sed -e 's/^/include /' > MANIFEST.in
126 hg manifest | sed -e 's/^/include /' > MANIFEST.in
127 echo include mercurial/__version__.py >> MANIFEST.in
127 echo include mercurial/__version__.py >> MANIFEST.in
128 sed -e 's/^/include /' < doc/MANIFEST >> MANIFEST.in
128 sed -e 's/^/include /' < doc/MANIFEST >> MANIFEST.in
129
129
130 dist: tests dist-notests
130 dist: tests dist-notests
131
131
132 dist-notests: doc MANIFEST.in
132 dist-notests: doc MANIFEST.in
133 TAR_OPTIONS="--owner=root --group=root --mode=u+w,go-w,a+rX-s" $(PYTHON) setup.py -q sdist
133 TAR_OPTIONS="--owner=root --group=root --mode=u+w,go-w,a+rX-s" $(PYTHON) setup.py -q sdist
134
134
135 check: tests
135 check: tests
136
136
137 tests:
137 tests:
138 # Run Rust tests if cargo is installed
138 # Run Rust tests if cargo is installed
139 if command -v $(CARGO) >/dev/null 2>&1; then \
139 if command -v $(CARGO) >/dev/null 2>&1; then \
140 $(MAKE) rust-tests; \
140 $(MAKE) rust-tests; \
141 $(MAKE) cargo-clippy; \
141 $(MAKE) cargo-clippy; \
142 fi
142 fi
143 cd tests && $(PYTHON) run-tests.py $(TESTFLAGS)
143 cd tests && $(PYTHON) run-tests.py $(TESTFLAGS)
144
144
145 test-%:
145 test-%:
146 cd tests && $(PYTHON) run-tests.py $(TESTFLAGS) $@
146 cd tests && $(PYTHON) run-tests.py $(TESTFLAGS) $@
147
147
148 testpy-%:
148 testpy-%:
149 @echo Looking for Python $* in $(HGPYTHONS)
149 @echo Looking for Python $* in $(HGPYTHONS)
150 [ -e $(HGPYTHONS)/$*/bin/python ] || ( \
150 [ -e $(HGPYTHONS)/$*/bin/python ] || ( \
151 cd $$(mktemp --directory --tmpdir) && \
151 cd $$(mktemp --directory --tmpdir) && \
152 $(MAKE) -f $(HGROOT)/contrib/Makefile.python PYTHONVER=$* PREFIX=$(HGPYTHONS)/$* python )
152 $(MAKE) -f $(HGROOT)/contrib/Makefile.python PYTHONVER=$* PREFIX=$(HGPYTHONS)/$* python )
153 cd tests && $(HGPYTHONS)/$*/bin/python run-tests.py $(TESTFLAGS)
153 cd tests && $(HGPYTHONS)/$*/bin/python run-tests.py $(TESTFLAGS)
154
154
155 rust-tests:
155 rust-tests:
156 cd $(HGROOT)/rust \
156 cd $(HGROOT)/rust \
157 && $(CARGO) test --quiet --all --features "$(HG_RUST_FEATURES)"
157 && $(CARGO) test --quiet --all --features "$(HG_RUST_FEATURES)"
158
158
159 cargo-clippy:
159 cargo-clippy:
160 cd $(HGROOT)/rust \
160 cd $(HGROOT)/rust \
161 && $(CARGO) clippy --all --features "$(HG_RUST_FEATURES)" -- -D warnings
161 && $(CARGO) clippy --all --features "$(HG_RUST_FEATURES)" -- -D warnings
162
162
163 check-code:
163 check-code:
164 hg manifest | xargs python contrib/check-code.py
164 hg manifest | xargs python contrib/check-code.py
165
165
166 format-c:
166 format-c:
167 clang-format --style file -i \
167 clang-format --style file -i \
168 `hg files 'set:(**.c or **.cc or **.h) and not "listfile:contrib/clang-format-ignorelist"'`
168 `hg files 'set:(**.c or **.cc or **.h) and not "listfile:contrib/clang-format-ignorelist"'`
169
169
170 update-pot: i18n/hg.pot
170 update-pot: i18n/hg.pot
171
171
172 i18n/hg.pot: $(PYFILES) $(DOCFILES) i18n/posplit i18n/hggettext
172 i18n/hg.pot: $(PYFILES) $(DOCFILES) i18n/posplit i18n/hggettext
173 $(PYTHON) i18n/hggettext mercurial/commands.py \
173 $(PYTHON) i18n/hggettext mercurial/commands.py \
174 hgext/*.py hgext/*/__init__.py \
174 hgext/*.py hgext/*/__init__.py \
175 mercurial/fileset.py mercurial/revset.py \
175 mercurial/fileset.py mercurial/revset.py \
176 mercurial/templatefilters.py \
176 mercurial/templatefilters.py \
177 mercurial/templatefuncs.py \
177 mercurial/templatefuncs.py \
178 mercurial/templatekw.py \
178 mercurial/templatekw.py \
179 mercurial/filemerge.py \
179 mercurial/filemerge.py \
180 mercurial/hgweb/webcommands.py \
180 mercurial/hgweb/webcommands.py \
181 mercurial/util.py \
181 mercurial/util.py \
182 $(DOCFILES) > i18n/hg.pot.tmp
182 $(DOCFILES) > i18n/hg.pot.tmp
183 # All strings marked for translation in Mercurial contain
183 # All strings marked for translation in Mercurial contain
184 # ASCII characters only. But some files contain string
184 # ASCII characters only. But some files contain string
185 # literals like this '\037\213'. xgettext thinks it has to
185 # literals like this '\037\213'. xgettext thinks it has to
186 # parse them even though they are not marked for translation.
186 # parse them even though they are not marked for translation.
187 # Extracting with an explicit encoding of ISO-8859-1 will make
187 # Extracting with an explicit encoding of ISO-8859-1 will make
188 # xgettext "parse" and ignore them.
188 # xgettext "parse" and ignore them.
189 $(PYFILESCMD) | xargs \
189 $(PYFILESCMD) | xargs \
190 xgettext --package-name "Mercurial" \
190 xgettext --package-name "Mercurial" \
191 --msgid-bugs-address "<mercurial-devel@mercurial-scm.org>" \
191 --msgid-bugs-address "<mercurial-devel@mercurial-scm.org>" \
192 --copyright-holder "Olivia Mackall <olivia@selenic.com> and others" \
192 --copyright-holder "Olivia Mackall <olivia@selenic.com> and others" \
193 --from-code ISO-8859-1 --join --sort-by-file --add-comments=i18n: \
193 --from-code ISO-8859-1 --join --sort-by-file --add-comments=i18n: \
194 -d hg -p i18n -o hg.pot.tmp
194 -d hg -p i18n -o hg.pot.tmp
195 $(PYTHON) i18n/posplit i18n/hg.pot.tmp
195 $(PYTHON) i18n/posplit i18n/hg.pot.tmp
196 # The target file is not created before the last step. So it never is in
196 # The target file is not created before the last step. So it never is in
197 # an intermediate state.
197 # an intermediate state.
198 mv -f i18n/hg.pot.tmp i18n/hg.pot
198 mv -f i18n/hg.pot.tmp i18n/hg.pot
199
199
200 %.po: i18n/hg.pot
200 %.po: i18n/hg.pot
201 # work on a temporary copy for never having a half completed target
201 # work on a temporary copy for never having a half completed target
202 cp $@ $@.tmp
202 cp $@ $@.tmp
203 msgmerge --no-location --update $@.tmp $^
203 msgmerge --no-location --update $@.tmp $^
204 mv -f $@.tmp $@
204 mv -f $@.tmp $@
205
205
206 # Packaging targets
206 # Packaging targets
207
207
208 packaging_targets := \
208 packaging_targets := \
209 rhel7 \
209 rhel7 \
210 rhel8 \
210 rhel8 \
211 rhel9 \
211 rhel9 \
212 deb \
212 deb \
213 docker-rhel7 \
213 docker-rhel7 \
214 docker-rhel8 \
214 docker-rhel8 \
215 docker-rhel9 \
215 docker-rhel9 \
216 docker-debian-bullseye \
216 docker-debian-bullseye \
217 docker-debian-buster \
217 docker-debian-buster \
218 docker-debian-stretch \
218 docker-debian-stretch \
219 docker-fedora \
219 docker-fedora \
220 docker-ubuntu-xenial \
220 docker-ubuntu-xenial \
221 docker-ubuntu-xenial-ppa \
221 docker-ubuntu-xenial-ppa \
222 docker-ubuntu-bionic \
222 docker-ubuntu-bionic \
223 docker-ubuntu-bionic-ppa \
223 docker-ubuntu-bionic-ppa \
224 docker-ubuntu-focal \
224 docker-ubuntu-focal \
225 docker-ubuntu-focal-ppa \
225 docker-ubuntu-focal-ppa \
226 fedora \
226 fedora \
227 linux-wheels \
227 linux-wheels \
228 linux-wheels-x86_64 \
228 linux-wheels-x86_64 \
229 linux-wheels-i686 \
229 linux-wheels-i686 \
230 ppa
230 ppa
231
231
232 # Forward packaging targets for convenience.
232 # Forward packaging targets for convenience.
233 $(packaging_targets):
233 $(packaging_targets):
234 $(MAKE) -C contrib/packaging $@
234 $(MAKE) -C contrib/packaging $@
235
235
236 osx:
236 osx:
237 rm -rf build/mercurial
237 rm -rf build/mercurial
238 /usr/bin/python2.7 setup.py install --optimize=1 \
238 /usr/bin/python2.7 setup.py install --optimize=1 \
239 --root=build/mercurial/ --prefix=/usr/local/ \
239 --root=build/mercurial/ --prefix=/usr/local/ \
240 --install-lib=/Library/Python/2.7/site-packages/
240 --install-lib=/Library/Python/2.7/site-packages/
241 make -C doc all install DESTDIR="$(PWD)/build/mercurial/"
241 make -C doc all install DESTDIR="$(PWD)/build/mercurial/"
242 # Place a bogon .DS_Store file in the target dir so we can be
242 # Place a bogon .DS_Store file in the target dir so we can be
243 # sure it doesn't get included in the final package.
243 # sure it doesn't get included in the final package.
244 touch build/mercurial/.DS_Store
244 touch build/mercurial/.DS_Store
245 make -C contrib/chg \
245 make -C contrib/chg \
246 HGPATH=/usr/local/bin/hg \
246 HGPATH=/usr/local/bin/hg \
247 PYTHON=/usr/bin/python2.7 \
247 PYTHON=/usr/bin/python2.7 \
248 DESTDIR=../../build/mercurial \
248 DESTDIR=../../build/mercurial \
249 PREFIX=/usr/local \
249 PREFIX=/usr/local \
250 clean install
250 clean install
251 mkdir -p $${OUTPUTDIR:-dist}
251 mkdir -p $${OUTPUTDIR:-dist}
252 HGVER=$$(python contrib/genosxversion.py $(OSXVERSIONFLAGS) build/mercurial/Library/Python/2.7/site-packages/mercurial/__version__.py) && \
252 HGVER=$$(python contrib/genosxversion.py $(OSXVERSIONFLAGS) build/mercurial/Library/Python/2.7/site-packages/mercurial/__version__.py) && \
253 OSXVER=$$(sw_vers -productVersion | cut -d. -f1,2) && \
253 OSXVER=$$(sw_vers -productVersion | cut -d. -f1,2) && \
254 pkgbuild --filter \\.DS_Store --root build/mercurial/ \
254 pkgbuild --filter \\.DS_Store --root build/mercurial/ \
255 --identifier org.mercurial-scm.mercurial \
255 --identifier org.mercurial-scm.mercurial \
256 --version "$${HGVER}" \
256 --version "$${HGVER}" \
257 build/mercurial.pkg && \
257 build/mercurial.pkg && \
258 productbuild --distribution contrib/packaging/macosx/distribution.xml \
258 productbuild --distribution contrib/packaging/macosx/distribution.xml \
259 --package-path build/ \
259 --package-path build/ \
260 --version "$${HGVER}" \
260 --version "$${HGVER}" \
261 --resources contrib/packaging/macosx/ \
261 --resources contrib/packaging/macosx/ \
262 "$${OUTPUTDIR:-dist/}"/Mercurial-"$${HGVER}"-macosx"$${OSXVER}".pkg
262 "$${OUTPUTDIR:-dist/}"/Mercurial-"$${HGVER}"-macosx"$${OSXVER}".pkg
263
263
264 pyoxidizer:
264 pyoxidizer:
265 $(PYOXIDIZER) build --path ./rust/hgcli --release
265 $(PYOXIDIZER) build --path ./rust/hgcli --release
266
266
267
267
268 # a temporary target to setup all we need for run-tests.py --pyoxidizer
268 # a temporary target to setup all we need for run-tests.py --pyoxidizer
269 # (should go away as the run-tests implementation improves
269 # (should go away as the run-tests implementation improves
270 pyoxidizer-windows-tests: PYOX_DIR=build/pyoxidizer/x86_64-pc-windows-msvc/release/app
270 pyoxidizer-windows-tests: PYOX_DIR=build/pyoxidizer/x86_64-pc-windows-msvc/release/app
271 pyoxidizer-windows-tests: pyoxidizer
271 pyoxidizer-windows-tests: pyoxidizer
272 rm -rf $(PYOX_DIR)/templates
272 rm -rf $(PYOX_DIR)/templates
273 cp -ar $(PYOX_DIR)/lib/mercurial/templates $(PYOX_DIR)/templates
273 cp -ar $(PYOX_DIR)/lib/mercurial/templates $(PYOX_DIR)/templates
274 rm -rf $(PYOX_DIR)/helptext
274 rm -rf $(PYOX_DIR)/helptext
275 cp -ar $(PYOX_DIR)/lib/mercurial/helptext $(PYOX_DIR)/helptext
275 cp -ar $(PYOX_DIR)/lib/mercurial/helptext $(PYOX_DIR)/helptext
276 rm -rf $(PYOX_DIR)/defaultrc
276 rm -rf $(PYOX_DIR)/defaultrc
277 cp -ar $(PYOX_DIR)/lib/mercurial/defaultrc $(PYOX_DIR)/defaultrc
277 cp -ar $(PYOX_DIR)/lib/mercurial/defaultrc $(PYOX_DIR)/defaultrc
278 rm -rf $(PYOX_DIR)/contrib
278 rm -rf $(PYOX_DIR)/contrib
279 cp -ar contrib $(PYOX_DIR)/contrib
279 cp -ar contrib $(PYOX_DIR)/contrib
280 rm -rf $(PYOX_DIR)/doc
280 rm -rf $(PYOX_DIR)/doc
281 cp -ar doc $(PYOX_DIR)/doc
281 cp -ar doc $(PYOX_DIR)/doc
282
282
283
283
284 # a temporary target to setup all we need for run-tests.py --pyoxidizer
284 # a temporary target to setup all we need for run-tests.py --pyoxidizer
285 # (should go away as the run-tests implementation improves
285 # (should go away as the run-tests implementation improves
286 pyoxidizer-macos-tests: PYOX_DIR=build/pyoxidizer/x86_64-apple-darwin/release/app
286 pyoxidizer-macos-tests: PYOX_DIR=build/pyoxidizer/x86_64-apple-darwin/release/app
287 pyoxidizer-macos-tests: pyoxidizer
287 pyoxidizer-macos-tests: pyoxidizer
288 rm -rf $(PYOX_DIR)/templates
288 rm -rf $(PYOX_DIR)/templates
289 cp -a mercurial/templates $(PYOX_DIR)/templates
289 cp -a mercurial/templates $(PYOX_DIR)/templates
290 rm -rf $(PYOX_DIR)/helptext
290 rm -rf $(PYOX_DIR)/helptext
291 cp -a mercurial/helptext $(PYOX_DIR)/helptext
291 cp -a mercurial/helptext $(PYOX_DIR)/helptext
292 rm -rf $(PYOX_DIR)/defaultrc
292 rm -rf $(PYOX_DIR)/defaultrc
293 cp -a mercurial/defaultrc $(PYOX_DIR)/defaultrc
293 cp -a mercurial/defaultrc $(PYOX_DIR)/defaultrc
294 rm -rf $(PYOX_DIR)/contrib
294 rm -rf $(PYOX_DIR)/contrib
295 cp -a contrib $(PYOX_DIR)/contrib
295 cp -a contrib $(PYOX_DIR)/contrib
296 rm -rf $(PYOX_DIR)/doc
296 rm -rf $(PYOX_DIR)/doc
297 cp -a doc $(PYOX_DIR)/doc
297 cp -a doc $(PYOX_DIR)/doc
298
298
299
299
300 .PHONY: help all local build doc cleanbutpackages clean install install-bin \
300 .PHONY: help all local build doc cleanbutpackages clean install install-bin \
301 install-doc install-home install-home-bin install-home-doc \
301 install-doc install-home install-home-bin install-home-doc \
302 dist dist-notests check tests rust-tests check-code format-c \
302 dist dist-notests check tests rust-tests check-code format-c \
303 update-pot pyoxidizer pyoxidizer-windows-tests pyoxidizer-macos-tests \
303 update-pot pyoxidizer pyoxidizer-windows-tests pyoxidizer-macos-tests \
304 $(packaging_targets) \
304 $(packaging_targets) \
305 osx
305 osx
@@ -1,699 +1,701 b''
1 # dirstatemap.py
1 # dirstatemap.py
2 #
2 #
3 # This software may be used and distributed according to the terms of the
3 # This software may be used and distributed according to the terms of the
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5
5
6
6
7 from .i18n import _
7 from .i18n import _
8
8
9 from . import (
9 from . import (
10 error,
10 error,
11 pathutil,
11 pathutil,
12 policy,
12 policy,
13 txnutil,
13 txnutil,
14 util,
14 util,
15 )
15 )
16
16
17 from .dirstateutils import (
17 from .dirstateutils import (
18 docket as docketmod,
18 docket as docketmod,
19 v2,
19 v2,
20 )
20 )
21
21
22 parsers = policy.importmod('parsers')
22 parsers = policy.importmod('parsers')
23 rustmod = policy.importrust('dirstate')
23 rustmod = policy.importrust('dirstate')
24
24
25 propertycache = util.propertycache
25 propertycache = util.propertycache
26
26
27 if rustmod is None:
27 if rustmod is None:
28 DirstateItem = parsers.DirstateItem
28 DirstateItem = parsers.DirstateItem
29 else:
29 else:
30 DirstateItem = rustmod.DirstateItem
30 DirstateItem = rustmod.DirstateItem
31
31
32 rangemask = 0x7FFFFFFF
32 rangemask = 0x7FFFFFFF
33
33
34
34
35 class _dirstatemapcommon:
35 class _dirstatemapcommon:
36 """
36 """
37 Methods that are identical for both implementations of the dirstatemap
37 Methods that are identical for both implementations of the dirstatemap
38 class, with and without Rust extensions enabled.
38 class, with and without Rust extensions enabled.
39 """
39 """
40
40
41 # please pytype
41 # please pytype
42
42
43 _map = None
43 _map = None
44 copymap = None
44 copymap = None
45
45
46 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
46 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
47 self._use_dirstate_v2 = use_dirstate_v2
47 self._use_dirstate_v2 = use_dirstate_v2
48 self._nodeconstants = nodeconstants
48 self._nodeconstants = nodeconstants
49 self._ui = ui
49 self._ui = ui
50 self._opener = opener
50 self._opener = opener
51 self._root = root
51 self._root = root
52 self._filename = b'dirstate'
52 self._filename = b'dirstate'
53 self._nodelen = 20 # Also update Rust code when changing this!
53 self._nodelen = 20 # Also update Rust code when changing this!
54 self._parents = None
54 self._parents = None
55 self._dirtyparents = False
55 self._dirtyparents = False
56 self._docket = None
56 self._docket = None
57
57
58 # for consistent view between _pl() and _read() invocations
58 # for consistent view between _pl() and _read() invocations
59 self._pendingmode = None
59 self._pendingmode = None
60
60
61 def preload(self):
61 def preload(self):
62 """Loads the underlying data, if it's not already loaded"""
62 """Loads the underlying data, if it's not already loaded"""
63 self._map
63 self._map
64
64
65 def get(self, key, default=None):
65 def get(self, key, default=None):
66 return self._map.get(key, default)
66 return self._map.get(key, default)
67
67
68 def __len__(self):
68 def __len__(self):
69 return len(self._map)
69 return len(self._map)
70
70
71 def __iter__(self):
71 def __iter__(self):
72 return iter(self._map)
72 return iter(self._map)
73
73
74 def __contains__(self, key):
74 def __contains__(self, key):
75 return key in self._map
75 return key in self._map
76
76
77 def __getitem__(self, item):
77 def __getitem__(self, item):
78 return self._map[item]
78 return self._map[item]
79
79
80 ### disk interaction
80 ### disk interaction
81
81
82 def _opendirstatefile(self):
82 def _opendirstatefile(self):
83 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
83 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
84 if self._pendingmode is not None and self._pendingmode != mode:
84 if self._pendingmode is not None and self._pendingmode != mode:
85 fp.close()
85 fp.close()
86 raise error.Abort(
86 raise error.Abort(
87 _(b'working directory state may be changed parallelly')
87 _(b'working directory state may be changed parallelly')
88 )
88 )
89 self._pendingmode = mode
89 self._pendingmode = mode
90 return fp
90 return fp
91
91
92 def _readdirstatefile(self, size=-1):
92 def _readdirstatefile(self, size=-1):
93 try:
93 try:
94 with self._opendirstatefile() as fp:
94 with self._opendirstatefile() as fp:
95 return fp.read(size)
95 return fp.read(size)
96 except FileNotFoundError:
96 except FileNotFoundError:
97 # File doesn't exist, so the current state is empty
97 # File doesn't exist, so the current state is empty
98 return b''
98 return b''
99
99
100 @property
100 @property
101 def docket(self):
101 def docket(self):
102 if not self._docket:
102 if not self._docket:
103 if not self._use_dirstate_v2:
103 if not self._use_dirstate_v2:
104 raise error.ProgrammingError(
104 raise error.ProgrammingError(
105 b'dirstate only has a docket in v2 format'
105 b'dirstate only has a docket in v2 format'
106 )
106 )
107 self._docket = docketmod.DirstateDocket.parse(
107 self._docket = docketmod.DirstateDocket.parse(
108 self._readdirstatefile(), self._nodeconstants
108 self._readdirstatefile(), self._nodeconstants
109 )
109 )
110 return self._docket
110 return self._docket
111
111
112 def write_v2_no_append(self, tr, st, meta, packed):
112 def write_v2_no_append(self, tr, st, meta, packed):
113 old_docket = self.docket
113 old_docket = self.docket
114 new_docket = docketmod.DirstateDocket.with_new_uuid(
114 new_docket = docketmod.DirstateDocket.with_new_uuid(
115 self.parents(), len(packed), meta
115 self.parents(), len(packed), meta
116 )
116 )
117 if old_docket.uuid == new_docket.uuid:
118 raise error.ProgrammingError(b'dirstate docket name collision')
117 data_filename = new_docket.data_filename()
119 data_filename = new_docket.data_filename()
118 self._opener.write(data_filename, packed)
120 self._opener.write(data_filename, packed)
119 # tell the transaction that we are adding a new file
121 # tell the transaction that we are adding a new file
120 if tr is not None:
122 if tr is not None:
121 tr.addbackup(data_filename, location=b'plain')
123 tr.addbackup(data_filename, location=b'plain')
122 # Write the new docket after the new data file has been
124 # Write the new docket after the new data file has been
123 # written. Because `st` was opened with `atomictemp=True`,
125 # written. Because `st` was opened with `atomictemp=True`,
124 # the actual `.hg/dirstate` file is only affected on close.
126 # the actual `.hg/dirstate` file is only affected on close.
125 st.write(new_docket.serialize())
127 st.write(new_docket.serialize())
126 st.close()
128 st.close()
127 # Remove the old data file after the new docket pointing to
129 # Remove the old data file after the new docket pointing to
128 # the new data file was written.
130 # the new data file was written.
129 if old_docket.uuid:
131 if old_docket.uuid:
130 data_filename = old_docket.data_filename()
132 data_filename = old_docket.data_filename()
131 if tr is not None:
133 if tr is not None:
132 tr.addbackup(data_filename, location=b'plain')
134 tr.addbackup(data_filename, location=b'plain')
133 unlink = lambda _tr=None: self._opener.unlink(data_filename)
135 unlink = lambda _tr=None: self._opener.unlink(data_filename)
134 if tr:
136 if tr:
135 category = b"dirstate-v2-clean-" + old_docket.uuid
137 category = b"dirstate-v2-clean-" + old_docket.uuid
136 tr.addpostclose(category, unlink)
138 tr.addpostclose(category, unlink)
137 else:
139 else:
138 unlink()
140 unlink()
139 self._docket = new_docket
141 self._docket = new_docket
140
142
141 ### reading/setting parents
143 ### reading/setting parents
142
144
143 def parents(self):
145 def parents(self):
144 if not self._parents:
146 if not self._parents:
145 if self._use_dirstate_v2:
147 if self._use_dirstate_v2:
146 self._parents = self.docket.parents
148 self._parents = self.docket.parents
147 else:
149 else:
148 read_len = self._nodelen * 2
150 read_len = self._nodelen * 2
149 st = self._readdirstatefile(read_len)
151 st = self._readdirstatefile(read_len)
150 l = len(st)
152 l = len(st)
151 if l == read_len:
153 if l == read_len:
152 self._parents = (
154 self._parents = (
153 st[: self._nodelen],
155 st[: self._nodelen],
154 st[self._nodelen : 2 * self._nodelen],
156 st[self._nodelen : 2 * self._nodelen],
155 )
157 )
156 elif l == 0:
158 elif l == 0:
157 self._parents = (
159 self._parents = (
158 self._nodeconstants.nullid,
160 self._nodeconstants.nullid,
159 self._nodeconstants.nullid,
161 self._nodeconstants.nullid,
160 )
162 )
161 else:
163 else:
162 raise error.Abort(
164 raise error.Abort(
163 _(b'working directory state appears damaged!')
165 _(b'working directory state appears damaged!')
164 )
166 )
165
167
166 return self._parents
168 return self._parents
167
169
168
170
169 class dirstatemap(_dirstatemapcommon):
171 class dirstatemap(_dirstatemapcommon):
170 """Map encapsulating the dirstate's contents.
172 """Map encapsulating the dirstate's contents.
171
173
172 The dirstate contains the following state:
174 The dirstate contains the following state:
173
175
174 - `identity` is the identity of the dirstate file, which can be used to
176 - `identity` is the identity of the dirstate file, which can be used to
175 detect when changes have occurred to the dirstate file.
177 detect when changes have occurred to the dirstate file.
176
178
177 - `parents` is a pair containing the parents of the working copy. The
179 - `parents` is a pair containing the parents of the working copy. The
178 parents are updated by calling `setparents`.
180 parents are updated by calling `setparents`.
179
181
180 - the state map maps filenames to tuples of (state, mode, size, mtime),
182 - the state map maps filenames to tuples of (state, mode, size, mtime),
181 where state is a single character representing 'normal', 'added',
183 where state is a single character representing 'normal', 'added',
182 'removed', or 'merged'. It is read by treating the dirstate as a
184 'removed', or 'merged'. It is read by treating the dirstate as a
183 dict. File state is updated by calling various methods (see each
185 dict. File state is updated by calling various methods (see each
184 documentation for details):
186 documentation for details):
185
187
186 - `reset_state`,
188 - `reset_state`,
187 - `set_tracked`
189 - `set_tracked`
188 - `set_untracked`
190 - `set_untracked`
189 - `set_clean`
191 - `set_clean`
190 - `set_possibly_dirty`
192 - `set_possibly_dirty`
191
193
192 - `copymap` maps destination filenames to their source filename.
194 - `copymap` maps destination filenames to their source filename.
193
195
194 The dirstate also provides the following views onto the state:
196 The dirstate also provides the following views onto the state:
195
197
196 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
198 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
197 form that they appear as in the dirstate.
199 form that they appear as in the dirstate.
198
200
199 - `dirfoldmap` is a dict mapping normalized directory names to the
201 - `dirfoldmap` is a dict mapping normalized directory names to the
200 denormalized form that they appear as in the dirstate.
202 denormalized form that they appear as in the dirstate.
201 """
203 """
202
204
203 ### Core data storage and access
205 ### Core data storage and access
204
206
205 @propertycache
207 @propertycache
206 def _map(self):
208 def _map(self):
207 self._map = {}
209 self._map = {}
208 self.read()
210 self.read()
209 return self._map
211 return self._map
210
212
211 @propertycache
213 @propertycache
212 def copymap(self):
214 def copymap(self):
213 self.copymap = {}
215 self.copymap = {}
214 self._map
216 self._map
215 return self.copymap
217 return self.copymap
216
218
217 def clear(self):
219 def clear(self):
218 self._map.clear()
220 self._map.clear()
219 self.copymap.clear()
221 self.copymap.clear()
220 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
222 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
221 util.clearcachedproperty(self, b"_dirs")
223 util.clearcachedproperty(self, b"_dirs")
222 util.clearcachedproperty(self, b"_alldirs")
224 util.clearcachedproperty(self, b"_alldirs")
223 util.clearcachedproperty(self, b"filefoldmap")
225 util.clearcachedproperty(self, b"filefoldmap")
224 util.clearcachedproperty(self, b"dirfoldmap")
226 util.clearcachedproperty(self, b"dirfoldmap")
225
227
226 def items(self):
228 def items(self):
227 return self._map.items()
229 return self._map.items()
228
230
229 # forward for python2,3 compat
231 # forward for python2,3 compat
230 iteritems = items
232 iteritems = items
231
233
232 def debug_iter(self, all):
234 def debug_iter(self, all):
233 """
235 """
234 Return an iterator of (filename, state, mode, size, mtime) tuples
236 Return an iterator of (filename, state, mode, size, mtime) tuples
235
237
236 `all` is unused when Rust is not enabled
238 `all` is unused when Rust is not enabled
237 """
239 """
238 for (filename, item) in self.items():
240 for (filename, item) in self.items():
239 yield (filename, item.state, item.mode, item.size, item.mtime)
241 yield (filename, item.state, item.mode, item.size, item.mtime)
240
242
241 def keys(self):
243 def keys(self):
242 return self._map.keys()
244 return self._map.keys()
243
245
244 ### reading/setting parents
246 ### reading/setting parents
245
247
246 def setparents(self, p1, p2, fold_p2=False):
248 def setparents(self, p1, p2, fold_p2=False):
247 self._parents = (p1, p2)
249 self._parents = (p1, p2)
248 self._dirtyparents = True
250 self._dirtyparents = True
249 copies = {}
251 copies = {}
250 if fold_p2:
252 if fold_p2:
251 for f, s in self._map.items():
253 for f, s in self._map.items():
252 # Discard "merged" markers when moving away from a merge state
254 # Discard "merged" markers when moving away from a merge state
253 if s.p2_info:
255 if s.p2_info:
254 source = self.copymap.pop(f, None)
256 source = self.copymap.pop(f, None)
255 if source:
257 if source:
256 copies[f] = source
258 copies[f] = source
257 s.drop_merge_data()
259 s.drop_merge_data()
258 return copies
260 return copies
259
261
260 ### disk interaction
262 ### disk interaction
261
263
262 def read(self):
264 def read(self):
263 # ignore HG_PENDING because identity is used only for writing
265 # ignore HG_PENDING because identity is used only for writing
264 self.identity = util.filestat.frompath(
266 self.identity = util.filestat.frompath(
265 self._opener.join(self._filename)
267 self._opener.join(self._filename)
266 )
268 )
267
269
268 if self._use_dirstate_v2:
270 if self._use_dirstate_v2:
269 if not self.docket.uuid:
271 if not self.docket.uuid:
270 return
272 return
271 st = self._opener.read(self.docket.data_filename())
273 st = self._opener.read(self.docket.data_filename())
272 else:
274 else:
273 st = self._readdirstatefile()
275 st = self._readdirstatefile()
274
276
275 if not st:
277 if not st:
276 return
278 return
277
279
278 # TODO: adjust this estimate for dirstate-v2
280 # TODO: adjust this estimate for dirstate-v2
279 if util.safehasattr(parsers, b'dict_new_presized'):
281 if util.safehasattr(parsers, b'dict_new_presized'):
280 # Make an estimate of the number of files in the dirstate based on
282 # Make an estimate of the number of files in the dirstate based on
281 # its size. This trades wasting some memory for avoiding costly
283 # its size. This trades wasting some memory for avoiding costly
282 # resizes. Each entry have a prefix of 17 bytes followed by one or
284 # resizes. Each entry have a prefix of 17 bytes followed by one or
283 # two path names. Studies on various large-scale real-world repositories
285 # two path names. Studies on various large-scale real-world repositories
284 # found 54 bytes a reasonable upper limit for the average path names.
286 # found 54 bytes a reasonable upper limit for the average path names.
285 # Copy entries are ignored for the sake of this estimate.
287 # Copy entries are ignored for the sake of this estimate.
286 self._map = parsers.dict_new_presized(len(st) // 71)
288 self._map = parsers.dict_new_presized(len(st) // 71)
287
289
288 # Python's garbage collector triggers a GC each time a certain number
290 # Python's garbage collector triggers a GC each time a certain number
289 # of container objects (the number being defined by
291 # of container objects (the number being defined by
290 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
292 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
291 # for each file in the dirstate. The C version then immediately marks
293 # for each file in the dirstate. The C version then immediately marks
292 # them as not to be tracked by the collector. However, this has no
294 # them as not to be tracked by the collector. However, this has no
293 # effect on when GCs are triggered, only on what objects the GC looks
295 # effect on when GCs are triggered, only on what objects the GC looks
294 # into. This means that O(number of files) GCs are unavoidable.
296 # into. This means that O(number of files) GCs are unavoidable.
295 # Depending on when in the process's lifetime the dirstate is parsed,
297 # Depending on when in the process's lifetime the dirstate is parsed,
296 # this can get very expensive. As a workaround, disable GC while
298 # this can get very expensive. As a workaround, disable GC while
297 # parsing the dirstate.
299 # parsing the dirstate.
298 #
300 #
299 # (we cannot decorate the function directly since it is in a C module)
301 # (we cannot decorate the function directly since it is in a C module)
300 if self._use_dirstate_v2:
302 if self._use_dirstate_v2:
301 p = self.docket.parents
303 p = self.docket.parents
302 meta = self.docket.tree_metadata
304 meta = self.docket.tree_metadata
303 parse_dirstate = util.nogc(v2.parse_dirstate)
305 parse_dirstate = util.nogc(v2.parse_dirstate)
304 parse_dirstate(self._map, self.copymap, st, meta)
306 parse_dirstate(self._map, self.copymap, st, meta)
305 else:
307 else:
306 parse_dirstate = util.nogc(parsers.parse_dirstate)
308 parse_dirstate = util.nogc(parsers.parse_dirstate)
307 p = parse_dirstate(self._map, self.copymap, st)
309 p = parse_dirstate(self._map, self.copymap, st)
308 if not self._dirtyparents:
310 if not self._dirtyparents:
309 self.setparents(*p)
311 self.setparents(*p)
310
312
311 # Avoid excess attribute lookups by fast pathing certain checks
313 # Avoid excess attribute lookups by fast pathing certain checks
312 self.__contains__ = self._map.__contains__
314 self.__contains__ = self._map.__contains__
313 self.__getitem__ = self._map.__getitem__
315 self.__getitem__ = self._map.__getitem__
314 self.get = self._map.get
316 self.get = self._map.get
315
317
316 def write(self, tr, st):
318 def write(self, tr, st):
317 if self._use_dirstate_v2:
319 if self._use_dirstate_v2:
318 packed, meta = v2.pack_dirstate(self._map, self.copymap)
320 packed, meta = v2.pack_dirstate(self._map, self.copymap)
319 self.write_v2_no_append(tr, st, meta, packed)
321 self.write_v2_no_append(tr, st, meta, packed)
320 else:
322 else:
321 packed = parsers.pack_dirstate(
323 packed = parsers.pack_dirstate(
322 self._map, self.copymap, self.parents()
324 self._map, self.copymap, self.parents()
323 )
325 )
324 st.write(packed)
326 st.write(packed)
325 st.close()
327 st.close()
326 self._dirtyparents = False
328 self._dirtyparents = False
327
329
328 @propertycache
330 @propertycache
329 def identity(self):
331 def identity(self):
330 self._map
332 self._map
331 return self.identity
333 return self.identity
332
334
333 ### code related to maintaining and accessing "extra" property
335 ### code related to maintaining and accessing "extra" property
334 # (e.g. "has_dir")
336 # (e.g. "has_dir")
335
337
336 def _dirs_incr(self, filename, old_entry=None):
338 def _dirs_incr(self, filename, old_entry=None):
337 """increment the dirstate counter if applicable"""
339 """increment the dirstate counter if applicable"""
338 if (
340 if (
339 old_entry is None or old_entry.removed
341 old_entry is None or old_entry.removed
340 ) and "_dirs" in self.__dict__:
342 ) and "_dirs" in self.__dict__:
341 self._dirs.addpath(filename)
343 self._dirs.addpath(filename)
342 if old_entry is None and "_alldirs" in self.__dict__:
344 if old_entry is None and "_alldirs" in self.__dict__:
343 self._alldirs.addpath(filename)
345 self._alldirs.addpath(filename)
344
346
345 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
347 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
346 """decrement the dirstate counter if applicable"""
348 """decrement the dirstate counter if applicable"""
347 if old_entry is not None:
349 if old_entry is not None:
348 if "_dirs" in self.__dict__ and not old_entry.removed:
350 if "_dirs" in self.__dict__ and not old_entry.removed:
349 self._dirs.delpath(filename)
351 self._dirs.delpath(filename)
350 if "_alldirs" in self.__dict__ and not remove_variant:
352 if "_alldirs" in self.__dict__ and not remove_variant:
351 self._alldirs.delpath(filename)
353 self._alldirs.delpath(filename)
352 elif remove_variant and "_alldirs" in self.__dict__:
354 elif remove_variant and "_alldirs" in self.__dict__:
353 self._alldirs.addpath(filename)
355 self._alldirs.addpath(filename)
354 if "filefoldmap" in self.__dict__:
356 if "filefoldmap" in self.__dict__:
355 normed = util.normcase(filename)
357 normed = util.normcase(filename)
356 self.filefoldmap.pop(normed, None)
358 self.filefoldmap.pop(normed, None)
357
359
358 @propertycache
360 @propertycache
359 def filefoldmap(self):
361 def filefoldmap(self):
360 """Returns a dictionary mapping normalized case paths to their
362 """Returns a dictionary mapping normalized case paths to their
361 non-normalized versions.
363 non-normalized versions.
362 """
364 """
363 try:
365 try:
364 makefilefoldmap = parsers.make_file_foldmap
366 makefilefoldmap = parsers.make_file_foldmap
365 except AttributeError:
367 except AttributeError:
366 pass
368 pass
367 else:
369 else:
368 return makefilefoldmap(
370 return makefilefoldmap(
369 self._map, util.normcasespec, util.normcasefallback
371 self._map, util.normcasespec, util.normcasefallback
370 )
372 )
371
373
372 f = {}
374 f = {}
373 normcase = util.normcase
375 normcase = util.normcase
374 for name, s in self._map.items():
376 for name, s in self._map.items():
375 if not s.removed:
377 if not s.removed:
376 f[normcase(name)] = name
378 f[normcase(name)] = name
377 f[b'.'] = b'.' # prevents useless util.fspath() invocation
379 f[b'.'] = b'.' # prevents useless util.fspath() invocation
378 return f
380 return f
379
381
380 @propertycache
382 @propertycache
381 def dirfoldmap(self):
383 def dirfoldmap(self):
382 f = {}
384 f = {}
383 normcase = util.normcase
385 normcase = util.normcase
384 for name in self._dirs:
386 for name in self._dirs:
385 f[normcase(name)] = name
387 f[normcase(name)] = name
386 return f
388 return f
387
389
388 def hastrackeddir(self, d):
390 def hastrackeddir(self, d):
389 """
391 """
390 Returns True if the dirstate contains a tracked (not removed) file
392 Returns True if the dirstate contains a tracked (not removed) file
391 in this directory.
393 in this directory.
392 """
394 """
393 return d in self._dirs
395 return d in self._dirs
394
396
395 def hasdir(self, d):
397 def hasdir(self, d):
396 """
398 """
397 Returns True if the dirstate contains a file (tracked or removed)
399 Returns True if the dirstate contains a file (tracked or removed)
398 in this directory.
400 in this directory.
399 """
401 """
400 return d in self._alldirs
402 return d in self._alldirs
401
403
402 @propertycache
404 @propertycache
403 def _dirs(self):
405 def _dirs(self):
404 return pathutil.dirs(self._map, only_tracked=True)
406 return pathutil.dirs(self._map, only_tracked=True)
405
407
406 @propertycache
408 @propertycache
407 def _alldirs(self):
409 def _alldirs(self):
408 return pathutil.dirs(self._map)
410 return pathutil.dirs(self._map)
409
411
410 ### code related to manipulation of entries and copy-sources
412 ### code related to manipulation of entries and copy-sources
411
413
412 def reset_state(
414 def reset_state(
413 self,
415 self,
414 filename,
416 filename,
415 wc_tracked=False,
417 wc_tracked=False,
416 p1_tracked=False,
418 p1_tracked=False,
417 p2_info=False,
419 p2_info=False,
418 has_meaningful_mtime=True,
420 has_meaningful_mtime=True,
419 parentfiledata=None,
421 parentfiledata=None,
420 ):
422 ):
421 """Set a entry to a given state, diregarding all previous state
423 """Set a entry to a given state, diregarding all previous state
422
424
423 This is to be used by the part of the dirstate API dedicated to
425 This is to be used by the part of the dirstate API dedicated to
424 adjusting the dirstate after a update/merge.
426 adjusting the dirstate after a update/merge.
425
427
426 note: calling this might result to no entry existing at all if the
428 note: calling this might result to no entry existing at all if the
427 dirstate map does not see any point at having one for this file
429 dirstate map does not see any point at having one for this file
428 anymore.
430 anymore.
429 """
431 """
430 # copy information are now outdated
432 # copy information are now outdated
431 # (maybe new information should be in directly passed to this function)
433 # (maybe new information should be in directly passed to this function)
432 self.copymap.pop(filename, None)
434 self.copymap.pop(filename, None)
433
435
434 if not (p1_tracked or p2_info or wc_tracked):
436 if not (p1_tracked or p2_info or wc_tracked):
435 old_entry = self._map.get(filename)
437 old_entry = self._map.get(filename)
436 self._drop_entry(filename)
438 self._drop_entry(filename)
437 self._dirs_decr(filename, old_entry=old_entry)
439 self._dirs_decr(filename, old_entry=old_entry)
438 return
440 return
439
441
440 old_entry = self._map.get(filename)
442 old_entry = self._map.get(filename)
441 self._dirs_incr(filename, old_entry)
443 self._dirs_incr(filename, old_entry)
442 entry = DirstateItem(
444 entry = DirstateItem(
443 wc_tracked=wc_tracked,
445 wc_tracked=wc_tracked,
444 p1_tracked=p1_tracked,
446 p1_tracked=p1_tracked,
445 p2_info=p2_info,
447 p2_info=p2_info,
446 has_meaningful_mtime=has_meaningful_mtime,
448 has_meaningful_mtime=has_meaningful_mtime,
447 parentfiledata=parentfiledata,
449 parentfiledata=parentfiledata,
448 )
450 )
449 self._map[filename] = entry
451 self._map[filename] = entry
450
452
451 def set_tracked(self, filename):
453 def set_tracked(self, filename):
452 new = False
454 new = False
453 entry = self.get(filename)
455 entry = self.get(filename)
454 if entry is None:
456 if entry is None:
455 self._dirs_incr(filename)
457 self._dirs_incr(filename)
456 entry = DirstateItem(
458 entry = DirstateItem(
457 wc_tracked=True,
459 wc_tracked=True,
458 )
460 )
459
461
460 self._map[filename] = entry
462 self._map[filename] = entry
461 new = True
463 new = True
462 elif not entry.tracked:
464 elif not entry.tracked:
463 self._dirs_incr(filename, entry)
465 self._dirs_incr(filename, entry)
464 entry.set_tracked()
466 entry.set_tracked()
465 self._refresh_entry(filename, entry)
467 self._refresh_entry(filename, entry)
466 new = True
468 new = True
467 else:
469 else:
468 # XXX This is probably overkill for more case, but we need this to
470 # XXX This is probably overkill for more case, but we need this to
469 # fully replace the `normallookup` call with `set_tracked` one.
471 # fully replace the `normallookup` call with `set_tracked` one.
470 # Consider smoothing this in the future.
472 # Consider smoothing this in the future.
471 entry.set_possibly_dirty()
473 entry.set_possibly_dirty()
472 self._refresh_entry(filename, entry)
474 self._refresh_entry(filename, entry)
473 return new
475 return new
474
476
475 def set_untracked(self, f):
477 def set_untracked(self, f):
476 """Mark a file as no longer tracked in the dirstate map"""
478 """Mark a file as no longer tracked in the dirstate map"""
477 entry = self.get(f)
479 entry = self.get(f)
478 if entry is None:
480 if entry is None:
479 return False
481 return False
480 else:
482 else:
481 self._dirs_decr(f, old_entry=entry, remove_variant=not entry.added)
483 self._dirs_decr(f, old_entry=entry, remove_variant=not entry.added)
482 if not entry.p2_info:
484 if not entry.p2_info:
483 self.copymap.pop(f, None)
485 self.copymap.pop(f, None)
484 entry.set_untracked()
486 entry.set_untracked()
485 self._refresh_entry(f, entry)
487 self._refresh_entry(f, entry)
486 return True
488 return True
487
489
488 def set_clean(self, filename, mode, size, mtime):
490 def set_clean(self, filename, mode, size, mtime):
489 """mark a file as back to a clean state"""
491 """mark a file as back to a clean state"""
490 entry = self[filename]
492 entry = self[filename]
491 size = size & rangemask
493 size = size & rangemask
492 entry.set_clean(mode, size, mtime)
494 entry.set_clean(mode, size, mtime)
493 self._refresh_entry(filename, entry)
495 self._refresh_entry(filename, entry)
494 self.copymap.pop(filename, None)
496 self.copymap.pop(filename, None)
495
497
496 def set_possibly_dirty(self, filename):
498 def set_possibly_dirty(self, filename):
497 """record that the current state of the file on disk is unknown"""
499 """record that the current state of the file on disk is unknown"""
498 entry = self[filename]
500 entry = self[filename]
499 entry.set_possibly_dirty()
501 entry.set_possibly_dirty()
500 self._refresh_entry(filename, entry)
502 self._refresh_entry(filename, entry)
501
503
502 def _refresh_entry(self, f, entry):
504 def _refresh_entry(self, f, entry):
503 """record updated state of an entry"""
505 """record updated state of an entry"""
504 if not entry.any_tracked:
506 if not entry.any_tracked:
505 self._map.pop(f, None)
507 self._map.pop(f, None)
506
508
507 def _drop_entry(self, f):
509 def _drop_entry(self, f):
508 """remove any entry for file f
510 """remove any entry for file f
509
511
510 This should also drop associated copy information
512 This should also drop associated copy information
511
513
512 The fact we actually need to drop it is the responsability of the caller"""
514 The fact we actually need to drop it is the responsability of the caller"""
513 self._map.pop(f, None)
515 self._map.pop(f, None)
514 self.copymap.pop(f, None)
516 self.copymap.pop(f, None)
515
517
516
518
517 if rustmod is not None:
519 if rustmod is not None:
518
520
519 class dirstatemap(_dirstatemapcommon):
521 class dirstatemap(_dirstatemapcommon):
520
522
521 ### Core data storage and access
523 ### Core data storage and access
522
524
523 @propertycache
525 @propertycache
524 def _map(self):
526 def _map(self):
525 """
527 """
526 Fills the Dirstatemap when called.
528 Fills the Dirstatemap when called.
527 """
529 """
528 # ignore HG_PENDING because identity is used only for writing
530 # ignore HG_PENDING because identity is used only for writing
529 self.identity = util.filestat.frompath(
531 self.identity = util.filestat.frompath(
530 self._opener.join(self._filename)
532 self._opener.join(self._filename)
531 )
533 )
532
534
533 if self._use_dirstate_v2:
535 if self._use_dirstate_v2:
534 if self.docket.uuid:
536 if self.docket.uuid:
535 # TODO: use mmap when possible
537 # TODO: use mmap when possible
536 data = self._opener.read(self.docket.data_filename())
538 data = self._opener.read(self.docket.data_filename())
537 else:
539 else:
538 data = b''
540 data = b''
539 self._map = rustmod.DirstateMap.new_v2(
541 self._map = rustmod.DirstateMap.new_v2(
540 data, self.docket.data_size, self.docket.tree_metadata
542 data, self.docket.data_size, self.docket.tree_metadata
541 )
543 )
542 parents = self.docket.parents
544 parents = self.docket.parents
543 else:
545 else:
544 self._map, parents = rustmod.DirstateMap.new_v1(
546 self._map, parents = rustmod.DirstateMap.new_v1(
545 self._readdirstatefile()
547 self._readdirstatefile()
546 )
548 )
547
549
548 if parents and not self._dirtyparents:
550 if parents and not self._dirtyparents:
549 self.setparents(*parents)
551 self.setparents(*parents)
550
552
551 self.__contains__ = self._map.__contains__
553 self.__contains__ = self._map.__contains__
552 self.__getitem__ = self._map.__getitem__
554 self.__getitem__ = self._map.__getitem__
553 self.get = self._map.get
555 self.get = self._map.get
554 return self._map
556 return self._map
555
557
556 @property
558 @property
557 def copymap(self):
559 def copymap(self):
558 return self._map.copymap()
560 return self._map.copymap()
559
561
560 def debug_iter(self, all):
562 def debug_iter(self, all):
561 """
563 """
562 Return an iterator of (filename, state, mode, size, mtime) tuples
564 Return an iterator of (filename, state, mode, size, mtime) tuples
563
565
564 `all`: also include with `state == b' '` dirstate tree nodes that
566 `all`: also include with `state == b' '` dirstate tree nodes that
565 don't have an associated `DirstateItem`.
567 don't have an associated `DirstateItem`.
566
568
567 """
569 """
568 return self._map.debug_iter(all)
570 return self._map.debug_iter(all)
569
571
570 def clear(self):
572 def clear(self):
571 self._map.clear()
573 self._map.clear()
572 self.setparents(
574 self.setparents(
573 self._nodeconstants.nullid, self._nodeconstants.nullid
575 self._nodeconstants.nullid, self._nodeconstants.nullid
574 )
576 )
575 util.clearcachedproperty(self, b"_dirs")
577 util.clearcachedproperty(self, b"_dirs")
576 util.clearcachedproperty(self, b"_alldirs")
578 util.clearcachedproperty(self, b"_alldirs")
577 util.clearcachedproperty(self, b"dirfoldmap")
579 util.clearcachedproperty(self, b"dirfoldmap")
578
580
579 def items(self):
581 def items(self):
580 return self._map.items()
582 return self._map.items()
581
583
582 # forward for python2,3 compat
584 # forward for python2,3 compat
583 iteritems = items
585 iteritems = items
584
586
585 def keys(self):
587 def keys(self):
586 return iter(self._map)
588 return iter(self._map)
587
589
588 ### reading/setting parents
590 ### reading/setting parents
589
591
590 def setparents(self, p1, p2, fold_p2=False):
592 def setparents(self, p1, p2, fold_p2=False):
591 self._parents = (p1, p2)
593 self._parents = (p1, p2)
592 self._dirtyparents = True
594 self._dirtyparents = True
593 copies = {}
595 copies = {}
594 if fold_p2:
596 if fold_p2:
595 copies = self._map.setparents_fixup()
597 copies = self._map.setparents_fixup()
596 return copies
598 return copies
597
599
598 ### disk interaction
600 ### disk interaction
599
601
600 @propertycache
602 @propertycache
601 def identity(self):
603 def identity(self):
602 self._map
604 self._map
603 return self.identity
605 return self.identity
604
606
605 def write(self, tr, st):
607 def write(self, tr, st):
606 if not self._use_dirstate_v2:
608 if not self._use_dirstate_v2:
607 p1, p2 = self.parents()
609 p1, p2 = self.parents()
608 packed = self._map.write_v1(p1, p2)
610 packed = self._map.write_v1(p1, p2)
609 st.write(packed)
611 st.write(packed)
610 st.close()
612 st.close()
611 self._dirtyparents = False
613 self._dirtyparents = False
612 return
614 return
613
615
614 # We can only append to an existing data file if there is one
616 # We can only append to an existing data file if there is one
615 can_append = self.docket.uuid is not None
617 can_append = self.docket.uuid is not None
616 packed, meta, append = self._map.write_v2(can_append)
618 packed, meta, append = self._map.write_v2(can_append)
617 if append:
619 if append:
618 docket = self.docket
620 docket = self.docket
619 data_filename = docket.data_filename()
621 data_filename = docket.data_filename()
620 # We mark it for backup to make sure a future `hg rollback` (or
622 # We mark it for backup to make sure a future `hg rollback` (or
621 # `hg recover`?) call find the data it needs to restore a
623 # `hg recover`?) call find the data it needs to restore a
622 # working repository.
624 # working repository.
623 #
625 #
624 # The backup can use a hardlink because the format is resistant
626 # The backup can use a hardlink because the format is resistant
625 # to trailing "dead" data.
627 # to trailing "dead" data.
626 if tr is not None:
628 if tr is not None:
627 tr.addbackup(data_filename, location=b'plain')
629 tr.addbackup(data_filename, location=b'plain')
628 with self._opener(data_filename, b'r+b') as fp:
630 with self._opener(data_filename, b'r+b') as fp:
629 fp.seek(docket.data_size)
631 fp.seek(docket.data_size)
630 assert fp.tell() == docket.data_size
632 assert fp.tell() == docket.data_size
631 written = fp.write(packed)
633 written = fp.write(packed)
632 if written is not None: # py2 may return None
634 if written is not None: # py2 may return None
633 assert written == len(packed), (written, len(packed))
635 assert written == len(packed), (written, len(packed))
634 docket.data_size += len(packed)
636 docket.data_size += len(packed)
635 docket.parents = self.parents()
637 docket.parents = self.parents()
636 docket.tree_metadata = meta
638 docket.tree_metadata = meta
637 st.write(docket.serialize())
639 st.write(docket.serialize())
638 st.close()
640 st.close()
639 else:
641 else:
640 self.write_v2_no_append(tr, st, meta, packed)
642 self.write_v2_no_append(tr, st, meta, packed)
641 # Reload from the newly-written file
643 # Reload from the newly-written file
642 util.clearcachedproperty(self, b"_map")
644 util.clearcachedproperty(self, b"_map")
643 self._dirtyparents = False
645 self._dirtyparents = False
644
646
645 ### code related to maintaining and accessing "extra" property
647 ### code related to maintaining and accessing "extra" property
646 # (e.g. "has_dir")
648 # (e.g. "has_dir")
647
649
648 @propertycache
650 @propertycache
649 def filefoldmap(self):
651 def filefoldmap(self):
650 """Returns a dictionary mapping normalized case paths to their
652 """Returns a dictionary mapping normalized case paths to their
651 non-normalized versions.
653 non-normalized versions.
652 """
654 """
653 return self._map.filefoldmapasdict()
655 return self._map.filefoldmapasdict()
654
656
655 def hastrackeddir(self, d):
657 def hastrackeddir(self, d):
656 return self._map.hastrackeddir(d)
658 return self._map.hastrackeddir(d)
657
659
658 def hasdir(self, d):
660 def hasdir(self, d):
659 return self._map.hasdir(d)
661 return self._map.hasdir(d)
660
662
661 @propertycache
663 @propertycache
662 def dirfoldmap(self):
664 def dirfoldmap(self):
663 f = {}
665 f = {}
664 normcase = util.normcase
666 normcase = util.normcase
665 for name in self._map.tracked_dirs():
667 for name in self._map.tracked_dirs():
666 f[normcase(name)] = name
668 f[normcase(name)] = name
667 return f
669 return f
668
670
669 ### code related to manipulation of entries and copy-sources
671 ### code related to manipulation of entries and copy-sources
670
672
671 def set_tracked(self, f):
673 def set_tracked(self, f):
672 return self._map.set_tracked(f)
674 return self._map.set_tracked(f)
673
675
674 def set_untracked(self, f):
676 def set_untracked(self, f):
675 return self._map.set_untracked(f)
677 return self._map.set_untracked(f)
676
678
677 def set_clean(self, filename, mode, size, mtime):
679 def set_clean(self, filename, mode, size, mtime):
678 self._map.set_clean(filename, mode, size, mtime)
680 self._map.set_clean(filename, mode, size, mtime)
679
681
680 def set_possibly_dirty(self, f):
682 def set_possibly_dirty(self, f):
681 self._map.set_possibly_dirty(f)
683 self._map.set_possibly_dirty(f)
682
684
683 def reset_state(
685 def reset_state(
684 self,
686 self,
685 filename,
687 filename,
686 wc_tracked=False,
688 wc_tracked=False,
687 p1_tracked=False,
689 p1_tracked=False,
688 p2_info=False,
690 p2_info=False,
689 has_meaningful_mtime=True,
691 has_meaningful_mtime=True,
690 parentfiledata=None,
692 parentfiledata=None,
691 ):
693 ):
692 return self._map.reset_state(
694 return self._map.reset_state(
693 filename,
695 filename,
694 wc_tracked,
696 wc_tracked,
695 p1_tracked,
697 p1_tracked,
696 p2_info,
698 p2_info,
697 has_meaningful_mtime,
699 has_meaningful_mtime,
698 parentfiledata,
700 parentfiledata,
699 )
701 )
@@ -1,1762 +1,1795 b''
1 #
1 #
2 # This is the mercurial setup script.
2 # This is the mercurial setup script.
3 #
3 #
4 # 'python setup.py install', or
4 # 'python setup.py install', or
5 # 'python setup.py --help' for more options
5 # 'python setup.py --help' for more options
6 import os
6 import os
7
7
8 # Mercurial can't work on 3.6.0 or 3.6.1 due to a bug in % formatting
8 # Mercurial can't work on 3.6.0 or 3.6.1 due to a bug in % formatting
9 # in bytestrings.
9 # in bytestrings.
10 supportedpy = ','.join(
10 supportedpy = ','.join(
11 [
11 [
12 '>=3.6.2',
12 '>=3.6.2',
13 ]
13 ]
14 )
14 )
15
15
16 import sys, platform
16 import sys, platform
17 import sysconfig
17 import sysconfig
18
18
19
19
20 def sysstr(s):
20 def sysstr(s):
21 return s.decode('latin-1')
21 return s.decode('latin-1')
22
22
23
23
24 def eprint(*args, **kwargs):
25 kwargs['file'] = sys.stderr
26 print(*args, **kwargs)
27
28
24 import ssl
29 import ssl
25
30
26 # ssl.HAS_TLSv1* are preferred to check support but they were added in Python
31 # ssl.HAS_TLSv1* are preferred to check support but they were added in Python
27 # 3.7. Prior to CPython commit 6e8cda91d92da72800d891b2fc2073ecbc134d98
32 # 3.7. Prior to CPython commit 6e8cda91d92da72800d891b2fc2073ecbc134d98
28 # (backported to the 3.7 branch), ssl.PROTOCOL_TLSv1_1 / ssl.PROTOCOL_TLSv1_2
33 # (backported to the 3.7 branch), ssl.PROTOCOL_TLSv1_1 / ssl.PROTOCOL_TLSv1_2
29 # were defined only if compiled against a OpenSSL version with TLS 1.1 / 1.2
34 # were defined only if compiled against a OpenSSL version with TLS 1.1 / 1.2
30 # support. At the mentioned commit, they were unconditionally defined.
35 # support. At the mentioned commit, they were unconditionally defined.
31 _notset = object()
36 _notset = object()
32 has_tlsv1_1 = getattr(ssl, 'HAS_TLSv1_1', _notset)
37 has_tlsv1_1 = getattr(ssl, 'HAS_TLSv1_1', _notset)
33 if has_tlsv1_1 is _notset:
38 if has_tlsv1_1 is _notset:
34 has_tlsv1_1 = getattr(ssl, 'PROTOCOL_TLSv1_1', _notset) is not _notset
39 has_tlsv1_1 = getattr(ssl, 'PROTOCOL_TLSv1_1', _notset) is not _notset
35 has_tlsv1_2 = getattr(ssl, 'HAS_TLSv1_2', _notset)
40 has_tlsv1_2 = getattr(ssl, 'HAS_TLSv1_2', _notset)
36 if has_tlsv1_2 is _notset:
41 if has_tlsv1_2 is _notset:
37 has_tlsv1_2 = getattr(ssl, 'PROTOCOL_TLSv1_2', _notset) is not _notset
42 has_tlsv1_2 = getattr(ssl, 'PROTOCOL_TLSv1_2', _notset) is not _notset
38 if not (has_tlsv1_1 or has_tlsv1_2):
43 if not (has_tlsv1_1 or has_tlsv1_2):
39 error = """
44 error = """
40 The `ssl` module does not advertise support for TLS 1.1 or TLS 1.2.
45 The `ssl` module does not advertise support for TLS 1.1 or TLS 1.2.
41 Please make sure that your Python installation was compiled against an OpenSSL
46 Please make sure that your Python installation was compiled against an OpenSSL
42 version enabling these features (likely this requires the OpenSSL version to
47 version enabling these features (likely this requires the OpenSSL version to
43 be at least 1.0.1).
48 be at least 1.0.1).
44 """
49 """
45 print(error, file=sys.stderr)
50 print(error, file=sys.stderr)
46 sys.exit(1)
51 sys.exit(1)
47
52
48 DYLIB_SUFFIX = sysconfig.get_config_vars()['EXT_SUFFIX']
53 DYLIB_SUFFIX = sysconfig.get_config_vars()['EXT_SUFFIX']
49
54
50 # Solaris Python packaging brain damage
55 # Solaris Python packaging brain damage
51 try:
56 try:
52 import hashlib
57 import hashlib
53
58
54 sha = hashlib.sha1()
59 sha = hashlib.sha1()
55 except ImportError:
60 except ImportError:
56 try:
61 try:
57 import sha
62 import sha
58
63
59 sha.sha # silence unused import warning
64 sha.sha # silence unused import warning
60 except ImportError:
65 except ImportError:
61 raise SystemExit(
66 raise SystemExit(
62 "Couldn't import standard hashlib (incomplete Python install)."
67 "Couldn't import standard hashlib (incomplete Python install)."
63 )
68 )
64
69
65 try:
70 try:
66 import zlib
71 import zlib
67
72
68 zlib.compressobj # silence unused import warning
73 zlib.compressobj # silence unused import warning
69 except ImportError:
74 except ImportError:
70 raise SystemExit(
75 raise SystemExit(
71 "Couldn't import standard zlib (incomplete Python install)."
76 "Couldn't import standard zlib (incomplete Python install)."
72 )
77 )
73
78
74 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
79 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
75 isironpython = False
80 isironpython = False
76 try:
81 try:
77 isironpython = (
82 isironpython = (
78 platform.python_implementation().lower().find("ironpython") != -1
83 platform.python_implementation().lower().find("ironpython") != -1
79 )
84 )
80 except AttributeError:
85 except AttributeError:
81 pass
86 pass
82
87
83 if isironpython:
88 if isironpython:
84 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
89 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
85 else:
90 else:
86 try:
91 try:
87 import bz2
92 import bz2
88
93
89 bz2.BZ2Compressor # silence unused import warning
94 bz2.BZ2Compressor # silence unused import warning
90 except ImportError:
95 except ImportError:
91 raise SystemExit(
96 raise SystemExit(
92 "Couldn't import standard bz2 (incomplete Python install)."
97 "Couldn't import standard bz2 (incomplete Python install)."
93 )
98 )
94
99
95 ispypy = "PyPy" in sys.version
100 ispypy = "PyPy" in sys.version
96
101
97 import ctypes
102 import ctypes
98 import stat, subprocess, time
103 import stat, subprocess, time
99 import re
104 import re
100 import shutil
105 import shutil
101 import tempfile
106 import tempfile
102
107
103 # We have issues with setuptools on some platforms and builders. Until
108 # We have issues with setuptools on some platforms and builders. Until
104 # those are resolved, setuptools is opt-in except for platforms where
109 # those are resolved, setuptools is opt-in except for platforms where
105 # we don't have issues.
110 # we don't have issues.
106 issetuptools = os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ
111 issetuptools = os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ
107 if issetuptools:
112 if issetuptools:
108 from setuptools import setup
113 from setuptools import setup
109 else:
114 else:
110 from distutils.core import setup
115 from distutils.core import setup
111 from distutils.ccompiler import new_compiler
116 from distutils.ccompiler import new_compiler
112 from distutils.core import Command, Extension
117 from distutils.core import Command, Extension
113 from distutils.dist import Distribution
118 from distutils.dist import Distribution
114 from distutils.command.build import build
119 from distutils.command.build import build
115 from distutils.command.build_ext import build_ext
120 from distutils.command.build_ext import build_ext
116 from distutils.command.build_py import build_py
121 from distutils.command.build_py import build_py
117 from distutils.command.build_scripts import build_scripts
122 from distutils.command.build_scripts import build_scripts
118 from distutils.command.install import install
123 from distutils.command.install import install
119 from distutils.command.install_lib import install_lib
124 from distutils.command.install_lib import install_lib
120 from distutils.command.install_scripts import install_scripts
125 from distutils.command.install_scripts import install_scripts
121 from distutils import log
126 from distutils import log
122 from distutils.spawn import spawn, find_executable
127 from distutils.spawn import spawn, find_executable
123 from distutils import file_util
128 from distutils import file_util
124 from distutils.errors import (
129 from distutils.errors import (
125 CCompilerError,
130 CCompilerError,
126 DistutilsError,
131 DistutilsError,
127 DistutilsExecError,
132 DistutilsExecError,
128 )
133 )
129 from distutils.sysconfig import get_python_inc
134 from distutils.sysconfig import get_python_inc
130
135
131
136
132 def write_if_changed(path, content):
137 def write_if_changed(path, content):
133 """Write content to a file iff the content hasn't changed."""
138 """Write content to a file iff the content hasn't changed."""
134 if os.path.exists(path):
139 if os.path.exists(path):
135 with open(path, 'rb') as fh:
140 with open(path, 'rb') as fh:
136 current = fh.read()
141 current = fh.read()
137 else:
142 else:
138 current = b''
143 current = b''
139
144
140 if current != content:
145 if current != content:
141 with open(path, 'wb') as fh:
146 with open(path, 'wb') as fh:
142 fh.write(content)
147 fh.write(content)
143
148
144
149
145 scripts = ['hg']
150 scripts = ['hg']
146 if os.name == 'nt':
151 if os.name == 'nt':
147 # We remove hg.bat if we are able to build hg.exe.
152 # We remove hg.bat if we are able to build hg.exe.
148 scripts.append('contrib/win32/hg.bat')
153 scripts.append('contrib/win32/hg.bat')
149
154
150
155
151 def cancompile(cc, code):
156 def cancompile(cc, code):
152 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
157 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
153 devnull = oldstderr = None
158 devnull = oldstderr = None
154 try:
159 try:
155 fname = os.path.join(tmpdir, 'testcomp.c')
160 fname = os.path.join(tmpdir, 'testcomp.c')
156 f = open(fname, 'w')
161 f = open(fname, 'w')
157 f.write(code)
162 f.write(code)
158 f.close()
163 f.close()
159 # Redirect stderr to /dev/null to hide any error messages
164 # Redirect stderr to /dev/null to hide any error messages
160 # from the compiler.
165 # from the compiler.
161 # This will have to be changed if we ever have to check
166 # This will have to be changed if we ever have to check
162 # for a function on Windows.
167 # for a function on Windows.
163 devnull = open('/dev/null', 'w')
168 devnull = open('/dev/null', 'w')
164 oldstderr = os.dup(sys.stderr.fileno())
169 oldstderr = os.dup(sys.stderr.fileno())
165 os.dup2(devnull.fileno(), sys.stderr.fileno())
170 os.dup2(devnull.fileno(), sys.stderr.fileno())
166 objects = cc.compile([fname], output_dir=tmpdir)
171 objects = cc.compile([fname], output_dir=tmpdir)
167 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
172 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
168 return True
173 return True
169 except Exception:
174 except Exception:
170 return False
175 return False
171 finally:
176 finally:
172 if oldstderr is not None:
177 if oldstderr is not None:
173 os.dup2(oldstderr, sys.stderr.fileno())
178 os.dup2(oldstderr, sys.stderr.fileno())
174 if devnull is not None:
179 if devnull is not None:
175 devnull.close()
180 devnull.close()
176 shutil.rmtree(tmpdir)
181 shutil.rmtree(tmpdir)
177
182
178
183
179 # simplified version of distutils.ccompiler.CCompiler.has_function
184 # simplified version of distutils.ccompiler.CCompiler.has_function
180 # that actually removes its temporary files.
185 # that actually removes its temporary files.
181 def hasfunction(cc, funcname):
186 def hasfunction(cc, funcname):
182 code = 'int main(void) { %s(); }\n' % funcname
187 code = 'int main(void) { %s(); }\n' % funcname
183 return cancompile(cc, code)
188 return cancompile(cc, code)
184
189
185
190
186 def hasheader(cc, headername):
191 def hasheader(cc, headername):
187 code = '#include <%s>\nint main(void) { return 0; }\n' % headername
192 code = '#include <%s>\nint main(void) { return 0; }\n' % headername
188 return cancompile(cc, code)
193 return cancompile(cc, code)
189
194
190
195
191 # py2exe needs to be installed to work
196 # py2exe needs to be installed to work
192 try:
197 try:
193 import py2exe
198 import py2exe
194
199
195 py2exe.patch_distutils()
200 py2exe.patch_distutils()
196 py2exeloaded = True
201 py2exeloaded = True
197 # import py2exe's patched Distribution class
202 # import py2exe's patched Distribution class
198 from distutils.core import Distribution
203 from distutils.core import Distribution
199 except ImportError:
204 except ImportError:
200 py2exeloaded = False
205 py2exeloaded = False
201
206
202
207
203 def runcmd(cmd, env, cwd=None):
208 def runcmd(cmd, env, cwd=None):
204 p = subprocess.Popen(
209 p = subprocess.Popen(
205 cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, cwd=cwd
210 cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, cwd=cwd
206 )
211 )
207 out, err = p.communicate()
212 out, err = p.communicate()
208 return p.returncode, out, err
213 return p.returncode, out, err
209
214
210
215
211 class hgcommand:
216 class hgcommand:
212 def __init__(self, cmd, env):
217 def __init__(self, cmd, env):
213 self.cmd = cmd
218 self.cmd = cmd
214 self.env = env
219 self.env = env
215
220
216 def run(self, args):
221 def run(self, args):
217 cmd = self.cmd + args
222 cmd = self.cmd + args
218 returncode, out, err = runcmd(cmd, self.env)
223 returncode, out, err = runcmd(cmd, self.env)
219 err = filterhgerr(err)
224 err = filterhgerr(err)
220 if err:
225 if err:
221 print("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
226 print("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
222 print(err, file=sys.stderr)
227 print(err, file=sys.stderr)
223 if returncode != 0:
228 if returncode != 0:
224 return b''
229 return b''
225 return out
230 return out
226
231
227
232
228 def filterhgerr(err):
233 def filterhgerr(err):
229 # If root is executing setup.py, but the repository is owned by
234 # If root is executing setup.py, but the repository is owned by
230 # another user (as in "sudo python setup.py install") we will get
235 # another user (as in "sudo python setup.py install") we will get
231 # trust warnings since the .hg/hgrc file is untrusted. That is
236 # trust warnings since the .hg/hgrc file is untrusted. That is
232 # fine, we don't want to load it anyway. Python may warn about
237 # fine, we don't want to load it anyway. Python may warn about
233 # a missing __init__.py in mercurial/locale, we also ignore that.
238 # a missing __init__.py in mercurial/locale, we also ignore that.
234 err = [
239 err = [
235 e
240 e
236 for e in err.splitlines()
241 for e in err.splitlines()
237 if (
242 if (
238 not e.startswith(b'not trusting file')
243 not e.startswith(b'not trusting file')
239 and not e.startswith(b'warning: Not importing')
244 and not e.startswith(b'warning: Not importing')
240 and not e.startswith(b'obsolete feature not enabled')
245 and not e.startswith(b'obsolete feature not enabled')
241 and not e.startswith(b'*** failed to import extension')
246 and not e.startswith(b'*** failed to import extension')
242 and not e.startswith(b'devel-warn:')
247 and not e.startswith(b'devel-warn:')
243 and not (
248 and not (
244 e.startswith(b'(third party extension')
249 e.startswith(b'(third party extension')
245 and e.endswith(b'or newer of Mercurial; disabling)')
250 and e.endswith(b'or newer of Mercurial; disabling)')
246 )
251 )
247 )
252 )
248 ]
253 ]
249 return b'\n'.join(b' ' + e for e in err)
254 return b'\n'.join(b' ' + e for e in err)
250
255
251
256
252 def findhg():
257 def findhg():
253 """Try to figure out how we should invoke hg for examining the local
258 """Try to figure out how we should invoke hg for examining the local
254 repository contents.
259 repository contents.
255
260
256 Returns an hgcommand object."""
261 Returns an hgcommand object."""
257 # By default, prefer the "hg" command in the user's path. This was
262 # By default, prefer the "hg" command in the user's path. This was
258 # presumably the hg command that the user used to create this repository.
263 # presumably the hg command that the user used to create this repository.
259 #
264 #
260 # This repository may require extensions or other settings that would not
265 # This repository may require extensions or other settings that would not
261 # be enabled by running the hg script directly from this local repository.
266 # be enabled by running the hg script directly from this local repository.
262 hgenv = os.environ.copy()
267 hgenv = os.environ.copy()
263 # Use HGPLAIN to disable hgrc settings that would change output formatting,
268 # Use HGPLAIN to disable hgrc settings that would change output formatting,
264 # and disable localization for the same reasons.
269 # and disable localization for the same reasons.
265 hgenv['HGPLAIN'] = '1'
270 hgenv['HGPLAIN'] = '1'
266 hgenv['LANGUAGE'] = 'C'
271 hgenv['LANGUAGE'] = 'C'
267 hgcmd = ['hg']
272 hgcmd = ['hg']
268 # Run a simple "hg log" command just to see if using hg from the user's
273 # Run a simple "hg log" command just to see if using hg from the user's
269 # path works and can successfully interact with this repository. Windows
274 # path works and can successfully interact with this repository. Windows
270 # gives precedence to hg.exe in the current directory, so fall back to the
275 # gives precedence to hg.exe in the current directory, so fall back to the
271 # python invocation of local hg, where pythonXY.dll can always be found.
276 # python invocation of local hg, where pythonXY.dll can always be found.
272 check_cmd = ['log', '-r.', '-Ttest']
277 check_cmd = ['log', '-r.', '-Ttest']
273 if os.name != 'nt' or not os.path.exists("hg.exe"):
278 if os.name != 'nt' or not os.path.exists("hg.exe"):
274 try:
279 try:
275 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
280 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
276 except EnvironmentError:
281 except EnvironmentError:
277 retcode = -1
282 retcode = -1
278 if retcode == 0 and not filterhgerr(err):
283 if retcode == 0 and not filterhgerr(err):
279 return hgcommand(hgcmd, hgenv)
284 return hgcommand(hgcmd, hgenv)
280
285
281 # Fall back to trying the local hg installation.
286 # Fall back to trying the local hg installation.
282 hgenv = localhgenv()
287 hgenv = localhgenv()
283 hgcmd = [sys.executable, 'hg']
288 hgcmd = [sys.executable, 'hg']
284 try:
289 try:
285 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
290 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
286 except EnvironmentError:
291 except EnvironmentError:
287 retcode = -1
292 retcode = -1
288 if retcode == 0 and not filterhgerr(err):
293 if retcode == 0 and not filterhgerr(err):
289 return hgcommand(hgcmd, hgenv)
294 return hgcommand(hgcmd, hgenv)
290
295
291 raise SystemExit(
296 eprint("/!\\")
292 'Unable to find a working hg binary to extract the '
297 eprint(r"/!\ Unable to find a working hg binary")
293 'version from the repository tags'
298 eprint(r"/!\ Version cannot be extract from the repository")
294 )
299 eprint(r"/!\ Re-run the setup once a first version is built")
300 return None
295
301
296
302
297 def localhgenv():
303 def localhgenv():
298 """Get an environment dictionary to use for invoking or importing
304 """Get an environment dictionary to use for invoking or importing
299 mercurial from the local repository."""
305 mercurial from the local repository."""
300 # Execute hg out of this directory with a custom environment which takes
306 # Execute hg out of this directory with a custom environment which takes
301 # care to not use any hgrc files and do no localization.
307 # care to not use any hgrc files and do no localization.
302 env = {
308 env = {
303 'HGMODULEPOLICY': 'py',
309 'HGMODULEPOLICY': 'py',
304 'HGRCPATH': '',
310 'HGRCPATH': '',
305 'LANGUAGE': 'C',
311 'LANGUAGE': 'C',
306 'PATH': '',
312 'PATH': '',
307 } # make pypi modules that use os.environ['PATH'] happy
313 } # make pypi modules that use os.environ['PATH'] happy
308 if 'LD_LIBRARY_PATH' in os.environ:
314 if 'LD_LIBRARY_PATH' in os.environ:
309 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
315 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
310 if 'SystemRoot' in os.environ:
316 if 'SystemRoot' in os.environ:
311 # SystemRoot is required by Windows to load various DLLs. See:
317 # SystemRoot is required by Windows to load various DLLs. See:
312 # https://bugs.python.org/issue13524#msg148850
318 # https://bugs.python.org/issue13524#msg148850
313 env['SystemRoot'] = os.environ['SystemRoot']
319 env['SystemRoot'] = os.environ['SystemRoot']
314 return env
320 return env
315
321
316
322
317 version = ''
323 version = ''
318
324
319 if os.path.isdir('.hg'):
325
326 def _try_get_version():
320 hg = findhg()
327 hg = findhg()
328 if hg is None:
329 return ''
330 hgid = None
331 numerictags = []
321 cmd = ['log', '-r', '.', '--template', '{tags}\n']
332 cmd = ['log', '-r', '.', '--template', '{tags}\n']
322 numerictags = [t for t in sysstr(hg.run(cmd)).split() if t[0:1].isdigit()]
333 pieces = sysstr(hg.run(cmd)).split()
334 numerictags = [t for t in pieces if t[0:1].isdigit()]
323 hgid = sysstr(hg.run(['id', '-i'])).strip()
335 hgid = sysstr(hg.run(['id', '-i'])).strip()
324 if not hgid:
336 if not hgid:
325 # Bail out if hg is having problems interacting with this repository,
337 eprint("/!\\")
326 # rather than falling through and producing a bogus version number.
338 eprint(r"/!\ Unable to determine hg version from local repository")
327 # Continuing with an invalid version number will break extensions
339 eprint(r"/!\ Failed to retrieve current revision tags")
328 # that define minimumhgversion.
340 return ''
329 raise SystemExit('Unable to determine hg version from local repository')
330 if numerictags: # tag(s) found
341 if numerictags: # tag(s) found
331 version = numerictags[-1]
342 version = numerictags[-1]
332 if hgid.endswith('+'): # propagate the dirty status to the tag
343 if hgid.endswith('+'): # propagate the dirty status to the tag
333 version += '+'
344 version += '+'
334 else: # no tag found
345 else: # no tag found on the checked out revision
335 ltagcmd = ['parents', '--template', '{latesttag}']
346 ltagcmd = ['parents', '--template', '{latesttag}']
336 ltag = sysstr(hg.run(ltagcmd))
347 ltag = sysstr(hg.run(ltagcmd))
337 if not ltag:
348 if not ltag:
338 ltag = 'null'
349 eprint("/!\\")
350 eprint(r"/!\ Unable to determine hg version from local repository")
351 eprint(
352 r"/!\ Failed to retrieve current revision distance to lated tag"
353 )
354 return ''
339 changessincecmd = ['log', '-T', 'x\n', '-r', "only(.,'%s')" % ltag]
355 changessincecmd = ['log', '-T', 'x\n', '-r', "only(.,'%s')" % ltag]
340 changessince = len(hg.run(changessincecmd).splitlines())
356 changessince = len(hg.run(changessincecmd).splitlines())
341 if ltag == 'null':
342 ltag = '0.0'
343 version = '%s+hg%s.%s' % (ltag, changessince, hgid)
357 version = '%s+hg%s.%s' % (ltag, changessince, hgid)
344 if version.endswith('+'):
358 if version.endswith('+'):
345 version = version[:-1] + 'local' + time.strftime('%Y%m%d')
359 version = version[:-1] + 'local' + time.strftime('%Y%m%d')
360 return version
361
362
363 if os.path.isdir('.hg'):
364 version = _try_get_version()
346 elif os.path.exists('.hg_archival.txt'):
365 elif os.path.exists('.hg_archival.txt'):
347 kw = dict(
366 kw = dict(
348 [[t.strip() for t in l.split(':', 1)] for l in open('.hg_archival.txt')]
367 [[t.strip() for t in l.split(':', 1)] for l in open('.hg_archival.txt')]
349 )
368 )
350 if 'tag' in kw:
369 if 'tag' in kw:
351 version = kw['tag']
370 version = kw['tag']
352 elif 'latesttag' in kw:
371 elif 'latesttag' in kw:
353 if 'changessincelatesttag' in kw:
372 if 'changessincelatesttag' in kw:
354 version = (
373 version = (
355 '%(latesttag)s+hg%(changessincelatesttag)s.%(node).12s' % kw
374 '%(latesttag)s+hg%(changessincelatesttag)s.%(node).12s' % kw
356 )
375 )
357 else:
376 else:
358 version = '%(latesttag)s+hg%(latesttagdistance)s.%(node).12s' % kw
377 version = '%(latesttag)s+hg%(latesttagdistance)s.%(node).12s' % kw
359 else:
378 else:
360 version = '0+hg' + kw.get('node', '')[:12]
379 version = '0+hg' + kw.get('node', '')[:12]
361 elif os.path.exists('mercurial/__version__.py'):
380 elif os.path.exists('mercurial/__version__.py'):
362 with open('mercurial/__version__.py') as f:
381 with open('mercurial/__version__.py') as f:
363 data = f.read()
382 data = f.read()
364 version = re.search('version = b"(.*)"', data).group(1)
383 version = re.search('version = b"(.*)"', data).group(1)
384 if not version:
385 if os.environ.get("MERCURIAL_SETUP_MAKE_LOCAL") == "1":
386 version = "0.0+0"
387 eprint("/!\\")
388 eprint(r"/!\ Using '0.0+0' as the default version")
389 eprint(r"/!\ Re-run make local once that first version is built")
390 eprint("/!\\")
391 else:
392 eprint("/!\\")
393 eprint(r"/!\ Could not determine the Mercurial version")
394 eprint(r"/!\ You need to build a local version first")
395 eprint(r"/!\ Run `make local` and try again")
396 eprint("/!\\")
397 msg = "Run `make local` first to get a working local version"
398 raise SystemExit(msg)
365
399
366 if version:
367 versionb = version
400 versionb = version
368 if not isinstance(versionb, bytes):
401 if not isinstance(versionb, bytes):
369 versionb = versionb.encode('ascii')
402 versionb = versionb.encode('ascii')
370
403
371 write_if_changed(
404 write_if_changed(
372 'mercurial/__version__.py',
405 'mercurial/__version__.py',
373 b''.join(
406 b''.join(
374 [
407 [
375 b'# this file is autogenerated by setup.py\n'
408 b'# this file is autogenerated by setup.py\n'
376 b'version = b"%s"\n' % versionb,
409 b'version = b"%s"\n' % versionb,
377 ]
410 ]
378 ),
411 ),
379 )
412 )
380
413
381
414
382 class hgbuild(build):
415 class hgbuild(build):
383 # Insert hgbuildmo first so that files in mercurial/locale/ are found
416 # Insert hgbuildmo first so that files in mercurial/locale/ are found
384 # when build_py is run next.
417 # when build_py is run next.
385 sub_commands = [('build_mo', None)] + build.sub_commands
418 sub_commands = [('build_mo', None)] + build.sub_commands
386
419
387
420
388 class hgbuildmo(build):
421 class hgbuildmo(build):
389
422
390 description = "build translations (.mo files)"
423 description = "build translations (.mo files)"
391
424
392 def run(self):
425 def run(self):
393 if not find_executable('msgfmt'):
426 if not find_executable('msgfmt'):
394 self.warn(
427 self.warn(
395 "could not find msgfmt executable, no translations "
428 "could not find msgfmt executable, no translations "
396 "will be built"
429 "will be built"
397 )
430 )
398 return
431 return
399
432
400 podir = 'i18n'
433 podir = 'i18n'
401 if not os.path.isdir(podir):
434 if not os.path.isdir(podir):
402 self.warn("could not find %s/ directory" % podir)
435 self.warn("could not find %s/ directory" % podir)
403 return
436 return
404
437
405 join = os.path.join
438 join = os.path.join
406 for po in os.listdir(podir):
439 for po in os.listdir(podir):
407 if not po.endswith('.po'):
440 if not po.endswith('.po'):
408 continue
441 continue
409 pofile = join(podir, po)
442 pofile = join(podir, po)
410 modir = join('locale', po[:-3], 'LC_MESSAGES')
443 modir = join('locale', po[:-3], 'LC_MESSAGES')
411 mofile = join(modir, 'hg.mo')
444 mofile = join(modir, 'hg.mo')
412 mobuildfile = join('mercurial', mofile)
445 mobuildfile = join('mercurial', mofile)
413 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
446 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
414 if sys.platform != 'sunos5':
447 if sys.platform != 'sunos5':
415 # msgfmt on Solaris does not know about -c
448 # msgfmt on Solaris does not know about -c
416 cmd.append('-c')
449 cmd.append('-c')
417 self.mkpath(join('mercurial', modir))
450 self.mkpath(join('mercurial', modir))
418 self.make_file([pofile], mobuildfile, spawn, (cmd,))
451 self.make_file([pofile], mobuildfile, spawn, (cmd,))
419
452
420
453
421 class hgdist(Distribution):
454 class hgdist(Distribution):
422 pure = False
455 pure = False
423 rust = False
456 rust = False
424 no_rust = False
457 no_rust = False
425 cffi = ispypy
458 cffi = ispypy
426
459
427 global_options = Distribution.global_options + [
460 global_options = Distribution.global_options + [
428 ('pure', None, "use pure (slow) Python code instead of C extensions"),
461 ('pure', None, "use pure (slow) Python code instead of C extensions"),
429 ('rust', None, "use Rust extensions additionally to C extensions"),
462 ('rust', None, "use Rust extensions additionally to C extensions"),
430 (
463 (
431 'no-rust',
464 'no-rust',
432 None,
465 None,
433 "do not use Rust extensions additionally to C extensions",
466 "do not use Rust extensions additionally to C extensions",
434 ),
467 ),
435 ]
468 ]
436
469
437 negative_opt = Distribution.negative_opt.copy()
470 negative_opt = Distribution.negative_opt.copy()
438 boolean_options = ['pure', 'rust', 'no-rust']
471 boolean_options = ['pure', 'rust', 'no-rust']
439 negative_opt['no-rust'] = 'rust'
472 negative_opt['no-rust'] = 'rust'
440
473
441 def _set_command_options(self, command_obj, option_dict=None):
474 def _set_command_options(self, command_obj, option_dict=None):
442 # Not all distutils versions in the wild have boolean_options.
475 # Not all distutils versions in the wild have boolean_options.
443 # This should be cleaned up when we're Python 3 only.
476 # This should be cleaned up when we're Python 3 only.
444 command_obj.boolean_options = (
477 command_obj.boolean_options = (
445 getattr(command_obj, 'boolean_options', []) + self.boolean_options
478 getattr(command_obj, 'boolean_options', []) + self.boolean_options
446 )
479 )
447 return Distribution._set_command_options(
480 return Distribution._set_command_options(
448 self, command_obj, option_dict=option_dict
481 self, command_obj, option_dict=option_dict
449 )
482 )
450
483
451 def parse_command_line(self):
484 def parse_command_line(self):
452 ret = Distribution.parse_command_line(self)
485 ret = Distribution.parse_command_line(self)
453 if not (self.rust or self.no_rust):
486 if not (self.rust or self.no_rust):
454 hgrustext = os.environ.get('HGWITHRUSTEXT')
487 hgrustext = os.environ.get('HGWITHRUSTEXT')
455 # TODO record it for proper rebuild upon changes
488 # TODO record it for proper rebuild upon changes
456 # (see mercurial/__modulepolicy__.py)
489 # (see mercurial/__modulepolicy__.py)
457 if hgrustext != 'cpython' and hgrustext is not None:
490 if hgrustext != 'cpython' and hgrustext is not None:
458 if hgrustext:
491 if hgrustext:
459 msg = 'unknown HGWITHRUSTEXT value: %s' % hgrustext
492 msg = 'unknown HGWITHRUSTEXT value: %s' % hgrustext
460 print(msg, file=sys.stderr)
493 print(msg, file=sys.stderr)
461 hgrustext = None
494 hgrustext = None
462 self.rust = hgrustext is not None
495 self.rust = hgrustext is not None
463 self.no_rust = not self.rust
496 self.no_rust = not self.rust
464 return ret
497 return ret
465
498
466 def has_ext_modules(self):
499 def has_ext_modules(self):
467 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
500 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
468 # too late for some cases
501 # too late for some cases
469 return not self.pure and Distribution.has_ext_modules(self)
502 return not self.pure and Distribution.has_ext_modules(self)
470
503
471
504
472 # This is ugly as a one-liner. So use a variable.
505 # This is ugly as a one-liner. So use a variable.
473 buildextnegops = dict(getattr(build_ext, 'negative_options', {}))
506 buildextnegops = dict(getattr(build_ext, 'negative_options', {}))
474 buildextnegops['no-zstd'] = 'zstd'
507 buildextnegops['no-zstd'] = 'zstd'
475 buildextnegops['no-rust'] = 'rust'
508 buildextnegops['no-rust'] = 'rust'
476
509
477
510
478 class hgbuildext(build_ext):
511 class hgbuildext(build_ext):
479 user_options = build_ext.user_options + [
512 user_options = build_ext.user_options + [
480 ('zstd', None, 'compile zstd bindings [default]'),
513 ('zstd', None, 'compile zstd bindings [default]'),
481 ('no-zstd', None, 'do not compile zstd bindings'),
514 ('no-zstd', None, 'do not compile zstd bindings'),
482 (
515 (
483 'rust',
516 'rust',
484 None,
517 None,
485 'compile Rust extensions if they are in use '
518 'compile Rust extensions if they are in use '
486 '(requires Cargo) [default]',
519 '(requires Cargo) [default]',
487 ),
520 ),
488 ('no-rust', None, 'do not compile Rust extensions'),
521 ('no-rust', None, 'do not compile Rust extensions'),
489 ]
522 ]
490
523
491 boolean_options = build_ext.boolean_options + ['zstd', 'rust']
524 boolean_options = build_ext.boolean_options + ['zstd', 'rust']
492 negative_opt = buildextnegops
525 negative_opt = buildextnegops
493
526
494 def initialize_options(self):
527 def initialize_options(self):
495 self.zstd = True
528 self.zstd = True
496 self.rust = True
529 self.rust = True
497
530
498 return build_ext.initialize_options(self)
531 return build_ext.initialize_options(self)
499
532
500 def finalize_options(self):
533 def finalize_options(self):
501 # Unless overridden by the end user, build extensions in parallel.
534 # Unless overridden by the end user, build extensions in parallel.
502 # Only influences behavior on Python 3.5+.
535 # Only influences behavior on Python 3.5+.
503 if getattr(self, 'parallel', None) is None:
536 if getattr(self, 'parallel', None) is None:
504 self.parallel = True
537 self.parallel = True
505
538
506 return build_ext.finalize_options(self)
539 return build_ext.finalize_options(self)
507
540
508 def build_extensions(self):
541 def build_extensions(self):
509 ruststandalones = [
542 ruststandalones = [
510 e for e in self.extensions if isinstance(e, RustStandaloneExtension)
543 e for e in self.extensions if isinstance(e, RustStandaloneExtension)
511 ]
544 ]
512 self.extensions = [
545 self.extensions = [
513 e for e in self.extensions if e not in ruststandalones
546 e for e in self.extensions if e not in ruststandalones
514 ]
547 ]
515 # Filter out zstd if disabled via argument.
548 # Filter out zstd if disabled via argument.
516 if not self.zstd:
549 if not self.zstd:
517 self.extensions = [
550 self.extensions = [
518 e for e in self.extensions if e.name != 'mercurial.zstd'
551 e for e in self.extensions if e.name != 'mercurial.zstd'
519 ]
552 ]
520
553
521 # Build Rust standalone extensions if it'll be used
554 # Build Rust standalone extensions if it'll be used
522 # and its build is not explicitly disabled (for external build
555 # and its build is not explicitly disabled (for external build
523 # as Linux distributions would do)
556 # as Linux distributions would do)
524 if self.distribution.rust and self.rust:
557 if self.distribution.rust and self.rust:
525 if not sys.platform.startswith('linux'):
558 if not sys.platform.startswith('linux'):
526 self.warn(
559 self.warn(
527 "rust extensions have only been tested on Linux "
560 "rust extensions have only been tested on Linux "
528 "and may not behave correctly on other platforms"
561 "and may not behave correctly on other platforms"
529 )
562 )
530
563
531 for rustext in ruststandalones:
564 for rustext in ruststandalones:
532 rustext.build('' if self.inplace else self.build_lib)
565 rustext.build('' if self.inplace else self.build_lib)
533
566
534 return build_ext.build_extensions(self)
567 return build_ext.build_extensions(self)
535
568
536 def build_extension(self, ext):
569 def build_extension(self, ext):
537 if (
570 if (
538 self.distribution.rust
571 self.distribution.rust
539 and self.rust
572 and self.rust
540 and isinstance(ext, RustExtension)
573 and isinstance(ext, RustExtension)
541 ):
574 ):
542 ext.rustbuild()
575 ext.rustbuild()
543 try:
576 try:
544 build_ext.build_extension(self, ext)
577 build_ext.build_extension(self, ext)
545 except CCompilerError:
578 except CCompilerError:
546 if not getattr(ext, 'optional', False):
579 if not getattr(ext, 'optional', False):
547 raise
580 raise
548 log.warn(
581 log.warn(
549 "Failed to build optional extension '%s' (skipping)", ext.name
582 "Failed to build optional extension '%s' (skipping)", ext.name
550 )
583 )
551
584
552
585
553 class hgbuildscripts(build_scripts):
586 class hgbuildscripts(build_scripts):
554 def run(self):
587 def run(self):
555 if os.name != 'nt' or self.distribution.pure:
588 if os.name != 'nt' or self.distribution.pure:
556 return build_scripts.run(self)
589 return build_scripts.run(self)
557
590
558 exebuilt = False
591 exebuilt = False
559 try:
592 try:
560 self.run_command('build_hgexe')
593 self.run_command('build_hgexe')
561 exebuilt = True
594 exebuilt = True
562 except (DistutilsError, CCompilerError):
595 except (DistutilsError, CCompilerError):
563 log.warn('failed to build optional hg.exe')
596 log.warn('failed to build optional hg.exe')
564
597
565 if exebuilt:
598 if exebuilt:
566 # Copying hg.exe to the scripts build directory ensures it is
599 # Copying hg.exe to the scripts build directory ensures it is
567 # installed by the install_scripts command.
600 # installed by the install_scripts command.
568 hgexecommand = self.get_finalized_command('build_hgexe')
601 hgexecommand = self.get_finalized_command('build_hgexe')
569 dest = os.path.join(self.build_dir, 'hg.exe')
602 dest = os.path.join(self.build_dir, 'hg.exe')
570 self.mkpath(self.build_dir)
603 self.mkpath(self.build_dir)
571 self.copy_file(hgexecommand.hgexepath, dest)
604 self.copy_file(hgexecommand.hgexepath, dest)
572
605
573 # Remove hg.bat because it is redundant with hg.exe.
606 # Remove hg.bat because it is redundant with hg.exe.
574 self.scripts.remove('contrib/win32/hg.bat')
607 self.scripts.remove('contrib/win32/hg.bat')
575
608
576 return build_scripts.run(self)
609 return build_scripts.run(self)
577
610
578
611
579 class hgbuildpy(build_py):
612 class hgbuildpy(build_py):
580 def finalize_options(self):
613 def finalize_options(self):
581 build_py.finalize_options(self)
614 build_py.finalize_options(self)
582
615
583 if self.distribution.pure:
616 if self.distribution.pure:
584 self.distribution.ext_modules = []
617 self.distribution.ext_modules = []
585 elif self.distribution.cffi:
618 elif self.distribution.cffi:
586 from mercurial.cffi import (
619 from mercurial.cffi import (
587 bdiffbuild,
620 bdiffbuild,
588 mpatchbuild,
621 mpatchbuild,
589 )
622 )
590
623
591 exts = [
624 exts = [
592 mpatchbuild.ffi.distutils_extension(),
625 mpatchbuild.ffi.distutils_extension(),
593 bdiffbuild.ffi.distutils_extension(),
626 bdiffbuild.ffi.distutils_extension(),
594 ]
627 ]
595 # cffi modules go here
628 # cffi modules go here
596 if sys.platform == 'darwin':
629 if sys.platform == 'darwin':
597 from mercurial.cffi import osutilbuild
630 from mercurial.cffi import osutilbuild
598
631
599 exts.append(osutilbuild.ffi.distutils_extension())
632 exts.append(osutilbuild.ffi.distutils_extension())
600 self.distribution.ext_modules = exts
633 self.distribution.ext_modules = exts
601 else:
634 else:
602 h = os.path.join(get_python_inc(), 'Python.h')
635 h = os.path.join(get_python_inc(), 'Python.h')
603 if not os.path.exists(h):
636 if not os.path.exists(h):
604 raise SystemExit(
637 raise SystemExit(
605 'Python headers are required to build '
638 'Python headers are required to build '
606 'Mercurial but weren\'t found in %s' % h
639 'Mercurial but weren\'t found in %s' % h
607 )
640 )
608
641
609 def run(self):
642 def run(self):
610 basepath = os.path.join(self.build_lib, 'mercurial')
643 basepath = os.path.join(self.build_lib, 'mercurial')
611 self.mkpath(basepath)
644 self.mkpath(basepath)
612
645
613 rust = self.distribution.rust
646 rust = self.distribution.rust
614 if self.distribution.pure:
647 if self.distribution.pure:
615 modulepolicy = 'py'
648 modulepolicy = 'py'
616 elif self.build_lib == '.':
649 elif self.build_lib == '.':
617 # in-place build should run without rebuilding and Rust extensions
650 # in-place build should run without rebuilding and Rust extensions
618 modulepolicy = 'rust+c-allow' if rust else 'allow'
651 modulepolicy = 'rust+c-allow' if rust else 'allow'
619 else:
652 else:
620 modulepolicy = 'rust+c' if rust else 'c'
653 modulepolicy = 'rust+c' if rust else 'c'
621
654
622 content = b''.join(
655 content = b''.join(
623 [
656 [
624 b'# this file is autogenerated by setup.py\n',
657 b'# this file is autogenerated by setup.py\n',
625 b'modulepolicy = b"%s"\n' % modulepolicy.encode('ascii'),
658 b'modulepolicy = b"%s"\n' % modulepolicy.encode('ascii'),
626 ]
659 ]
627 )
660 )
628 write_if_changed(os.path.join(basepath, '__modulepolicy__.py'), content)
661 write_if_changed(os.path.join(basepath, '__modulepolicy__.py'), content)
629
662
630 build_py.run(self)
663 build_py.run(self)
631
664
632
665
633 class buildhgextindex(Command):
666 class buildhgextindex(Command):
634 description = 'generate prebuilt index of hgext (for frozen package)'
667 description = 'generate prebuilt index of hgext (for frozen package)'
635 user_options = []
668 user_options = []
636 _indexfilename = 'hgext/__index__.py'
669 _indexfilename = 'hgext/__index__.py'
637
670
638 def initialize_options(self):
671 def initialize_options(self):
639 pass
672 pass
640
673
641 def finalize_options(self):
674 def finalize_options(self):
642 pass
675 pass
643
676
644 def run(self):
677 def run(self):
645 if os.path.exists(self._indexfilename):
678 if os.path.exists(self._indexfilename):
646 with open(self._indexfilename, 'w') as f:
679 with open(self._indexfilename, 'w') as f:
647 f.write('# empty\n')
680 f.write('# empty\n')
648
681
649 # here no extension enabled, disabled() lists up everything
682 # here no extension enabled, disabled() lists up everything
650 code = (
683 code = (
651 'import pprint; from mercurial import extensions; '
684 'import pprint; from mercurial import extensions; '
652 'ext = extensions.disabled();'
685 'ext = extensions.disabled();'
653 'ext.pop("__index__", None);'
686 'ext.pop("__index__", None);'
654 'pprint.pprint(ext)'
687 'pprint.pprint(ext)'
655 )
688 )
656 returncode, out, err = runcmd(
689 returncode, out, err = runcmd(
657 [sys.executable, '-c', code], localhgenv()
690 [sys.executable, '-c', code], localhgenv()
658 )
691 )
659 if err or returncode != 0:
692 if err or returncode != 0:
660 raise DistutilsExecError(err)
693 raise DistutilsExecError(err)
661
694
662 with open(self._indexfilename, 'wb') as f:
695 with open(self._indexfilename, 'wb') as f:
663 f.write(b'# this file is autogenerated by setup.py\n')
696 f.write(b'# this file is autogenerated by setup.py\n')
664 f.write(b'docs = ')
697 f.write(b'docs = ')
665 f.write(out)
698 f.write(out)
666
699
667
700
668 class buildhgexe(build_ext):
701 class buildhgexe(build_ext):
669 description = 'compile hg.exe from mercurial/exewrapper.c'
702 description = 'compile hg.exe from mercurial/exewrapper.c'
670
703
671 LONG_PATHS_MANIFEST = """\
704 LONG_PATHS_MANIFEST = """\
672 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
705 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
673 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
706 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
674 <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
707 <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
675 <security>
708 <security>
676 <requestedPrivileges>
709 <requestedPrivileges>
677 <requestedExecutionLevel
710 <requestedExecutionLevel
678 level="asInvoker"
711 level="asInvoker"
679 uiAccess="false"
712 uiAccess="false"
680 />
713 />
681 </requestedPrivileges>
714 </requestedPrivileges>
682 </security>
715 </security>
683 </trustInfo>
716 </trustInfo>
684 <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
717 <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
685 <application>
718 <application>
686 <!-- Windows Vista -->
719 <!-- Windows Vista -->
687 <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
720 <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
688 <!-- Windows 7 -->
721 <!-- Windows 7 -->
689 <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
722 <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
690 <!-- Windows 8 -->
723 <!-- Windows 8 -->
691 <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
724 <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
692 <!-- Windows 8.1 -->
725 <!-- Windows 8.1 -->
693 <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
726 <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
694 <!-- Windows 10 and Windows 11 -->
727 <!-- Windows 10 and Windows 11 -->
695 <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
728 <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
696 </application>
729 </application>
697 </compatibility>
730 </compatibility>
698 <application xmlns="urn:schemas-microsoft-com:asm.v3">
731 <application xmlns="urn:schemas-microsoft-com:asm.v3">
699 <windowsSettings
732 <windowsSettings
700 xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
733 xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
701 <ws2:longPathAware>true</ws2:longPathAware>
734 <ws2:longPathAware>true</ws2:longPathAware>
702 </windowsSettings>
735 </windowsSettings>
703 </application>
736 </application>
704 <dependency>
737 <dependency>
705 <dependentAssembly>
738 <dependentAssembly>
706 <assemblyIdentity type="win32"
739 <assemblyIdentity type="win32"
707 name="Microsoft.Windows.Common-Controls"
740 name="Microsoft.Windows.Common-Controls"
708 version="6.0.0.0"
741 version="6.0.0.0"
709 processorArchitecture="*"
742 processorArchitecture="*"
710 publicKeyToken="6595b64144ccf1df"
743 publicKeyToken="6595b64144ccf1df"
711 language="*" />
744 language="*" />
712 </dependentAssembly>
745 </dependentAssembly>
713 </dependency>
746 </dependency>
714 </assembly>
747 </assembly>
715 """
748 """
716
749
717 def initialize_options(self):
750 def initialize_options(self):
718 build_ext.initialize_options(self)
751 build_ext.initialize_options(self)
719
752
720 def build_extensions(self):
753 def build_extensions(self):
721 if os.name != 'nt':
754 if os.name != 'nt':
722 return
755 return
723 if isinstance(self.compiler, HackedMingw32CCompiler):
756 if isinstance(self.compiler, HackedMingw32CCompiler):
724 self.compiler.compiler_so = self.compiler.compiler # no -mdll
757 self.compiler.compiler_so = self.compiler.compiler # no -mdll
725 self.compiler.dll_libraries = [] # no -lmsrvc90
758 self.compiler.dll_libraries = [] # no -lmsrvc90
726
759
727 pythonlib = None
760 pythonlib = None
728
761
729 dirname = os.path.dirname(self.get_ext_fullpath('dummy'))
762 dirname = os.path.dirname(self.get_ext_fullpath('dummy'))
730 self.hgtarget = os.path.join(dirname, 'hg')
763 self.hgtarget = os.path.join(dirname, 'hg')
731
764
732 if getattr(sys, 'dllhandle', None):
765 if getattr(sys, 'dllhandle', None):
733 # Different Python installs can have different Python library
766 # Different Python installs can have different Python library
734 # names. e.g. the official CPython distribution uses pythonXY.dll
767 # names. e.g. the official CPython distribution uses pythonXY.dll
735 # and MinGW uses libpythonX.Y.dll.
768 # and MinGW uses libpythonX.Y.dll.
736 _kernel32 = ctypes.windll.kernel32
769 _kernel32 = ctypes.windll.kernel32
737 _kernel32.GetModuleFileNameA.argtypes = [
770 _kernel32.GetModuleFileNameA.argtypes = [
738 ctypes.c_void_p,
771 ctypes.c_void_p,
739 ctypes.c_void_p,
772 ctypes.c_void_p,
740 ctypes.c_ulong,
773 ctypes.c_ulong,
741 ]
774 ]
742 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
775 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
743 size = 1000
776 size = 1000
744 buf = ctypes.create_string_buffer(size + 1)
777 buf = ctypes.create_string_buffer(size + 1)
745 filelen = _kernel32.GetModuleFileNameA(
778 filelen = _kernel32.GetModuleFileNameA(
746 sys.dllhandle, ctypes.byref(buf), size
779 sys.dllhandle, ctypes.byref(buf), size
747 )
780 )
748
781
749 if filelen > 0 and filelen != size:
782 if filelen > 0 and filelen != size:
750 dllbasename = os.path.basename(buf.value)
783 dllbasename = os.path.basename(buf.value)
751 if not dllbasename.lower().endswith(b'.dll'):
784 if not dllbasename.lower().endswith(b'.dll'):
752 raise SystemExit(
785 raise SystemExit(
753 'Python DLL does not end with .dll: %s' % dllbasename
786 'Python DLL does not end with .dll: %s' % dllbasename
754 )
787 )
755 pythonlib = dllbasename[:-4]
788 pythonlib = dllbasename[:-4]
756
789
757 # Copy the pythonXY.dll next to the binary so that it runs
790 # Copy the pythonXY.dll next to the binary so that it runs
758 # without tampering with PATH.
791 # without tampering with PATH.
759 dest = os.path.join(
792 dest = os.path.join(
760 os.path.dirname(self.hgtarget),
793 os.path.dirname(self.hgtarget),
761 os.fsdecode(dllbasename),
794 os.fsdecode(dllbasename),
762 )
795 )
763
796
764 if not os.path.exists(dest):
797 if not os.path.exists(dest):
765 shutil.copy(buf.value, dest)
798 shutil.copy(buf.value, dest)
766
799
767 # Also overwrite python3.dll so that hgext.git is usable.
800 # Also overwrite python3.dll so that hgext.git is usable.
768 # TODO: also handle the MSYS flavor
801 # TODO: also handle the MSYS flavor
769 python_x = os.path.join(
802 python_x = os.path.join(
770 os.path.dirname(os.fsdecode(buf.value)),
803 os.path.dirname(os.fsdecode(buf.value)),
771 "python3.dll",
804 "python3.dll",
772 )
805 )
773
806
774 if os.path.exists(python_x):
807 if os.path.exists(python_x):
775 dest = os.path.join(
808 dest = os.path.join(
776 os.path.dirname(self.hgtarget),
809 os.path.dirname(self.hgtarget),
777 os.path.basename(python_x),
810 os.path.basename(python_x),
778 )
811 )
779
812
780 shutil.copy(python_x, dest)
813 shutil.copy(python_x, dest)
781
814
782 if not pythonlib:
815 if not pythonlib:
783 log.warn(
816 log.warn(
784 'could not determine Python DLL filename; assuming pythonXY'
817 'could not determine Python DLL filename; assuming pythonXY'
785 )
818 )
786
819
787 hv = sys.hexversion
820 hv = sys.hexversion
788 pythonlib = b'python%d%d' % (hv >> 24, (hv >> 16) & 0xFF)
821 pythonlib = b'python%d%d' % (hv >> 24, (hv >> 16) & 0xFF)
789
822
790 log.info('using %s as Python library name' % pythonlib)
823 log.info('using %s as Python library name' % pythonlib)
791 with open('mercurial/hgpythonlib.h', 'wb') as f:
824 with open('mercurial/hgpythonlib.h', 'wb') as f:
792 f.write(b'/* this file is autogenerated by setup.py */\n')
825 f.write(b'/* this file is autogenerated by setup.py */\n')
793 f.write(b'#define HGPYTHONLIB "%s"\n' % pythonlib)
826 f.write(b'#define HGPYTHONLIB "%s"\n' % pythonlib)
794
827
795 objects = self.compiler.compile(
828 objects = self.compiler.compile(
796 ['mercurial/exewrapper.c'],
829 ['mercurial/exewrapper.c'],
797 output_dir=self.build_temp,
830 output_dir=self.build_temp,
798 macros=[('_UNICODE', None), ('UNICODE', None)],
831 macros=[('_UNICODE', None), ('UNICODE', None)],
799 )
832 )
800 self.compiler.link_executable(
833 self.compiler.link_executable(
801 objects, self.hgtarget, libraries=[], output_dir=self.build_temp
834 objects, self.hgtarget, libraries=[], output_dir=self.build_temp
802 )
835 )
803
836
804 self.addlongpathsmanifest()
837 self.addlongpathsmanifest()
805
838
806 def addlongpathsmanifest(self):
839 def addlongpathsmanifest(self):
807 """Add manifest pieces so that hg.exe understands long paths
840 """Add manifest pieces so that hg.exe understands long paths
808
841
809 Why resource #1 should be used for .exe manifests? I don't know and
842 Why resource #1 should be used for .exe manifests? I don't know and
810 wasn't able to find an explanation for mortals. But it seems to work.
843 wasn't able to find an explanation for mortals. But it seems to work.
811 """
844 """
812 exefname = self.compiler.executable_filename(self.hgtarget)
845 exefname = self.compiler.executable_filename(self.hgtarget)
813 fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
846 fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
814 os.close(fdauto)
847 os.close(fdauto)
815 with open(manfname, 'w', encoding="UTF-8") as f:
848 with open(manfname, 'w', encoding="UTF-8") as f:
816 f.write(self.LONG_PATHS_MANIFEST)
849 f.write(self.LONG_PATHS_MANIFEST)
817 log.info("long paths manifest is written to '%s'" % manfname)
850 log.info("long paths manifest is written to '%s'" % manfname)
818 outputresource = '-outputresource:%s;#1' % exefname
851 outputresource = '-outputresource:%s;#1' % exefname
819 log.info("running mt.exe to update hg.exe's manifest in-place")
852 log.info("running mt.exe to update hg.exe's manifest in-place")
820
853
821 self.spawn(
854 self.spawn(
822 [
855 [
823 self.compiler.mt,
856 self.compiler.mt,
824 '-nologo',
857 '-nologo',
825 '-manifest',
858 '-manifest',
826 manfname,
859 manfname,
827 outputresource,
860 outputresource,
828 ]
861 ]
829 )
862 )
830 log.info("done updating hg.exe's manifest")
863 log.info("done updating hg.exe's manifest")
831 os.remove(manfname)
864 os.remove(manfname)
832
865
833 @property
866 @property
834 def hgexepath(self):
867 def hgexepath(self):
835 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
868 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
836 return os.path.join(self.build_temp, dir, 'hg.exe')
869 return os.path.join(self.build_temp, dir, 'hg.exe')
837
870
838
871
839 class hgbuilddoc(Command):
872 class hgbuilddoc(Command):
840 description = 'build documentation'
873 description = 'build documentation'
841 user_options = [
874 user_options = [
842 ('man', None, 'generate man pages'),
875 ('man', None, 'generate man pages'),
843 ('html', None, 'generate html pages'),
876 ('html', None, 'generate html pages'),
844 ]
877 ]
845
878
846 def initialize_options(self):
879 def initialize_options(self):
847 self.man = None
880 self.man = None
848 self.html = None
881 self.html = None
849
882
850 def finalize_options(self):
883 def finalize_options(self):
851 # If --man or --html are set, only generate what we're told to.
884 # If --man or --html are set, only generate what we're told to.
852 # Otherwise generate everything.
885 # Otherwise generate everything.
853 have_subset = self.man is not None or self.html is not None
886 have_subset = self.man is not None or self.html is not None
854
887
855 if have_subset:
888 if have_subset:
856 self.man = True if self.man else False
889 self.man = True if self.man else False
857 self.html = True if self.html else False
890 self.html = True if self.html else False
858 else:
891 else:
859 self.man = True
892 self.man = True
860 self.html = True
893 self.html = True
861
894
862 def run(self):
895 def run(self):
863 def normalizecrlf(p):
896 def normalizecrlf(p):
864 with open(p, 'rb') as fh:
897 with open(p, 'rb') as fh:
865 orig = fh.read()
898 orig = fh.read()
866
899
867 if b'\r\n' not in orig:
900 if b'\r\n' not in orig:
868 return
901 return
869
902
870 log.info('normalizing %s to LF line endings' % p)
903 log.info('normalizing %s to LF line endings' % p)
871 with open(p, 'wb') as fh:
904 with open(p, 'wb') as fh:
872 fh.write(orig.replace(b'\r\n', b'\n'))
905 fh.write(orig.replace(b'\r\n', b'\n'))
873
906
874 def gentxt(root):
907 def gentxt(root):
875 txt = 'doc/%s.txt' % root
908 txt = 'doc/%s.txt' % root
876 log.info('generating %s' % txt)
909 log.info('generating %s' % txt)
877 res, out, err = runcmd(
910 res, out, err = runcmd(
878 [sys.executable, 'gendoc.py', root], os.environ, cwd='doc'
911 [sys.executable, 'gendoc.py', root], os.environ, cwd='doc'
879 )
912 )
880 if res:
913 if res:
881 raise SystemExit(
914 raise SystemExit(
882 'error running gendoc.py: %s'
915 'error running gendoc.py: %s'
883 % '\n'.join([sysstr(out), sysstr(err)])
916 % '\n'.join([sysstr(out), sysstr(err)])
884 )
917 )
885
918
886 with open(txt, 'wb') as fh:
919 with open(txt, 'wb') as fh:
887 fh.write(out)
920 fh.write(out)
888
921
889 def gengendoc(root):
922 def gengendoc(root):
890 gendoc = 'doc/%s.gendoc.txt' % root
923 gendoc = 'doc/%s.gendoc.txt' % root
891
924
892 log.info('generating %s' % gendoc)
925 log.info('generating %s' % gendoc)
893 res, out, err = runcmd(
926 res, out, err = runcmd(
894 [sys.executable, 'gendoc.py', '%s.gendoc' % root],
927 [sys.executable, 'gendoc.py', '%s.gendoc' % root],
895 os.environ,
928 os.environ,
896 cwd='doc',
929 cwd='doc',
897 )
930 )
898 if res:
931 if res:
899 raise SystemExit(
932 raise SystemExit(
900 'error running gendoc: %s'
933 'error running gendoc: %s'
901 % '\n'.join([sysstr(out), sysstr(err)])
934 % '\n'.join([sysstr(out), sysstr(err)])
902 )
935 )
903
936
904 with open(gendoc, 'wb') as fh:
937 with open(gendoc, 'wb') as fh:
905 fh.write(out)
938 fh.write(out)
906
939
907 def genman(root):
940 def genman(root):
908 log.info('generating doc/%s' % root)
941 log.info('generating doc/%s' % root)
909 res, out, err = runcmd(
942 res, out, err = runcmd(
910 [
943 [
911 sys.executable,
944 sys.executable,
912 'runrst',
945 'runrst',
913 'hgmanpage',
946 'hgmanpage',
914 '--halt',
947 '--halt',
915 'warning',
948 'warning',
916 '--strip-elements-with-class',
949 '--strip-elements-with-class',
917 'htmlonly',
950 'htmlonly',
918 '%s.txt' % root,
951 '%s.txt' % root,
919 root,
952 root,
920 ],
953 ],
921 os.environ,
954 os.environ,
922 cwd='doc',
955 cwd='doc',
923 )
956 )
924 if res:
957 if res:
925 raise SystemExit(
958 raise SystemExit(
926 'error running runrst: %s'
959 'error running runrst: %s'
927 % '\n'.join([sysstr(out), sysstr(err)])
960 % '\n'.join([sysstr(out), sysstr(err)])
928 )
961 )
929
962
930 normalizecrlf('doc/%s' % root)
963 normalizecrlf('doc/%s' % root)
931
964
932 def genhtml(root):
965 def genhtml(root):
933 log.info('generating doc/%s.html' % root)
966 log.info('generating doc/%s.html' % root)
934 res, out, err = runcmd(
967 res, out, err = runcmd(
935 [
968 [
936 sys.executable,
969 sys.executable,
937 'runrst',
970 'runrst',
938 'html',
971 'html',
939 '--halt',
972 '--halt',
940 'warning',
973 'warning',
941 '--link-stylesheet',
974 '--link-stylesheet',
942 '--stylesheet-path',
975 '--stylesheet-path',
943 'style.css',
976 'style.css',
944 '%s.txt' % root,
977 '%s.txt' % root,
945 '%s.html' % root,
978 '%s.html' % root,
946 ],
979 ],
947 os.environ,
980 os.environ,
948 cwd='doc',
981 cwd='doc',
949 )
982 )
950 if res:
983 if res:
951 raise SystemExit(
984 raise SystemExit(
952 'error running runrst: %s'
985 'error running runrst: %s'
953 % '\n'.join([sysstr(out), sysstr(err)])
986 % '\n'.join([sysstr(out), sysstr(err)])
954 )
987 )
955
988
956 normalizecrlf('doc/%s.html' % root)
989 normalizecrlf('doc/%s.html' % root)
957
990
958 # This logic is duplicated in doc/Makefile.
991 # This logic is duplicated in doc/Makefile.
959 sources = {
992 sources = {
960 f
993 f
961 for f in os.listdir('mercurial/helptext')
994 for f in os.listdir('mercurial/helptext')
962 if re.search(r'[0-9]\.txt$', f)
995 if re.search(r'[0-9]\.txt$', f)
963 }
996 }
964
997
965 # common.txt is a one-off.
998 # common.txt is a one-off.
966 gentxt('common')
999 gentxt('common')
967
1000
968 for source in sorted(sources):
1001 for source in sorted(sources):
969 assert source[-4:] == '.txt'
1002 assert source[-4:] == '.txt'
970 root = source[:-4]
1003 root = source[:-4]
971
1004
972 gentxt(root)
1005 gentxt(root)
973 gengendoc(root)
1006 gengendoc(root)
974
1007
975 if self.man:
1008 if self.man:
976 genman(root)
1009 genman(root)
977 if self.html:
1010 if self.html:
978 genhtml(root)
1011 genhtml(root)
979
1012
980
1013
981 class hginstall(install):
1014 class hginstall(install):
982
1015
983 user_options = install.user_options + [
1016 user_options = install.user_options + [
984 (
1017 (
985 'old-and-unmanageable',
1018 'old-and-unmanageable',
986 None,
1019 None,
987 'noop, present for eggless setuptools compat',
1020 'noop, present for eggless setuptools compat',
988 ),
1021 ),
989 (
1022 (
990 'single-version-externally-managed',
1023 'single-version-externally-managed',
991 None,
1024 None,
992 'noop, present for eggless setuptools compat',
1025 'noop, present for eggless setuptools compat',
993 ),
1026 ),
994 ]
1027 ]
995
1028
996 sub_commands = install.sub_commands + [
1029 sub_commands = install.sub_commands + [
997 ('install_completion', lambda self: True)
1030 ('install_completion', lambda self: True)
998 ]
1031 ]
999
1032
1000 # Also helps setuptools not be sad while we refuse to create eggs.
1033 # Also helps setuptools not be sad while we refuse to create eggs.
1001 single_version_externally_managed = True
1034 single_version_externally_managed = True
1002
1035
1003 def get_sub_commands(self):
1036 def get_sub_commands(self):
1004 # Screen out egg related commands to prevent egg generation. But allow
1037 # Screen out egg related commands to prevent egg generation. But allow
1005 # mercurial.egg-info generation, since that is part of modern
1038 # mercurial.egg-info generation, since that is part of modern
1006 # packaging.
1039 # packaging.
1007 excl = {'bdist_egg'}
1040 excl = {'bdist_egg'}
1008 return filter(lambda x: x not in excl, install.get_sub_commands(self))
1041 return filter(lambda x: x not in excl, install.get_sub_commands(self))
1009
1042
1010
1043
1011 class hginstalllib(install_lib):
1044 class hginstalllib(install_lib):
1012 """
1045 """
1013 This is a specialization of install_lib that replaces the copy_file used
1046 This is a specialization of install_lib that replaces the copy_file used
1014 there so that it supports setting the mode of files after copying them,
1047 there so that it supports setting the mode of files after copying them,
1015 instead of just preserving the mode that the files originally had. If your
1048 instead of just preserving the mode that the files originally had. If your
1016 system has a umask of something like 027, preserving the permissions when
1049 system has a umask of something like 027, preserving the permissions when
1017 copying will lead to a broken install.
1050 copying will lead to a broken install.
1018
1051
1019 Note that just passing keep_permissions=False to copy_file would be
1052 Note that just passing keep_permissions=False to copy_file would be
1020 insufficient, as it might still be applying a umask.
1053 insufficient, as it might still be applying a umask.
1021 """
1054 """
1022
1055
1023 def run(self):
1056 def run(self):
1024 realcopyfile = file_util.copy_file
1057 realcopyfile = file_util.copy_file
1025
1058
1026 def copyfileandsetmode(*args, **kwargs):
1059 def copyfileandsetmode(*args, **kwargs):
1027 src, dst = args[0], args[1]
1060 src, dst = args[0], args[1]
1028 dst, copied = realcopyfile(*args, **kwargs)
1061 dst, copied = realcopyfile(*args, **kwargs)
1029 if copied:
1062 if copied:
1030 st = os.stat(src)
1063 st = os.stat(src)
1031 # Persist executable bit (apply it to group and other if user
1064 # Persist executable bit (apply it to group and other if user
1032 # has it)
1065 # has it)
1033 if st[stat.ST_MODE] & stat.S_IXUSR:
1066 if st[stat.ST_MODE] & stat.S_IXUSR:
1034 setmode = int('0755', 8)
1067 setmode = int('0755', 8)
1035 else:
1068 else:
1036 setmode = int('0644', 8)
1069 setmode = int('0644', 8)
1037 m = stat.S_IMODE(st[stat.ST_MODE])
1070 m = stat.S_IMODE(st[stat.ST_MODE])
1038 m = (m & ~int('0777', 8)) | setmode
1071 m = (m & ~int('0777', 8)) | setmode
1039 os.chmod(dst, m)
1072 os.chmod(dst, m)
1040
1073
1041 file_util.copy_file = copyfileandsetmode
1074 file_util.copy_file = copyfileandsetmode
1042 try:
1075 try:
1043 install_lib.run(self)
1076 install_lib.run(self)
1044 finally:
1077 finally:
1045 file_util.copy_file = realcopyfile
1078 file_util.copy_file = realcopyfile
1046
1079
1047
1080
1048 class hginstallscripts(install_scripts):
1081 class hginstallscripts(install_scripts):
1049 """
1082 """
1050 This is a specialization of install_scripts that replaces the @LIBDIR@ with
1083 This is a specialization of install_scripts that replaces the @LIBDIR@ with
1051 the configured directory for modules. If possible, the path is made relative
1084 the configured directory for modules. If possible, the path is made relative
1052 to the directory for scripts.
1085 to the directory for scripts.
1053 """
1086 """
1054
1087
1055 def initialize_options(self):
1088 def initialize_options(self):
1056 install_scripts.initialize_options(self)
1089 install_scripts.initialize_options(self)
1057
1090
1058 self.install_lib = None
1091 self.install_lib = None
1059
1092
1060 def finalize_options(self):
1093 def finalize_options(self):
1061 install_scripts.finalize_options(self)
1094 install_scripts.finalize_options(self)
1062 self.set_undefined_options('install', ('install_lib', 'install_lib'))
1095 self.set_undefined_options('install', ('install_lib', 'install_lib'))
1063
1096
1064 def run(self):
1097 def run(self):
1065 install_scripts.run(self)
1098 install_scripts.run(self)
1066
1099
1067 # It only makes sense to replace @LIBDIR@ with the install path if
1100 # It only makes sense to replace @LIBDIR@ with the install path if
1068 # the install path is known. For wheels, the logic below calculates
1101 # the install path is known. For wheels, the logic below calculates
1069 # the libdir to be "../..". This is because the internal layout of a
1102 # the libdir to be "../..". This is because the internal layout of a
1070 # wheel archive looks like:
1103 # wheel archive looks like:
1071 #
1104 #
1072 # mercurial-3.6.1.data/scripts/hg
1105 # mercurial-3.6.1.data/scripts/hg
1073 # mercurial/__init__.py
1106 # mercurial/__init__.py
1074 #
1107 #
1075 # When installing wheels, the subdirectories of the "<pkg>.data"
1108 # When installing wheels, the subdirectories of the "<pkg>.data"
1076 # directory are translated to system local paths and files therein
1109 # directory are translated to system local paths and files therein
1077 # are copied in place. The mercurial/* files are installed into the
1110 # are copied in place. The mercurial/* files are installed into the
1078 # site-packages directory. However, the site-packages directory
1111 # site-packages directory. However, the site-packages directory
1079 # isn't known until wheel install time. This means we have no clue
1112 # isn't known until wheel install time. This means we have no clue
1080 # at wheel generation time what the installed site-packages directory
1113 # at wheel generation time what the installed site-packages directory
1081 # will be. And, wheels don't appear to provide the ability to register
1114 # will be. And, wheels don't appear to provide the ability to register
1082 # custom code to run during wheel installation. This all means that
1115 # custom code to run during wheel installation. This all means that
1083 # we can't reliably set the libdir in wheels: the default behavior
1116 # we can't reliably set the libdir in wheels: the default behavior
1084 # of looking in sys.path must do.
1117 # of looking in sys.path must do.
1085
1118
1086 if (
1119 if (
1087 os.path.splitdrive(self.install_dir)[0]
1120 os.path.splitdrive(self.install_dir)[0]
1088 != os.path.splitdrive(self.install_lib)[0]
1121 != os.path.splitdrive(self.install_lib)[0]
1089 ):
1122 ):
1090 # can't make relative paths from one drive to another, so use an
1123 # can't make relative paths from one drive to another, so use an
1091 # absolute path instead
1124 # absolute path instead
1092 libdir = self.install_lib
1125 libdir = self.install_lib
1093 else:
1126 else:
1094 libdir = os.path.relpath(self.install_lib, self.install_dir)
1127 libdir = os.path.relpath(self.install_lib, self.install_dir)
1095
1128
1096 for outfile in self.outfiles:
1129 for outfile in self.outfiles:
1097 with open(outfile, 'rb') as fp:
1130 with open(outfile, 'rb') as fp:
1098 data = fp.read()
1131 data = fp.read()
1099
1132
1100 # skip binary files
1133 # skip binary files
1101 if b'\0' in data:
1134 if b'\0' in data:
1102 continue
1135 continue
1103
1136
1104 # During local installs, the shebang will be rewritten to the final
1137 # During local installs, the shebang will be rewritten to the final
1105 # install path. During wheel packaging, the shebang has a special
1138 # install path. During wheel packaging, the shebang has a special
1106 # value.
1139 # value.
1107 if data.startswith(b'#!python'):
1140 if data.startswith(b'#!python'):
1108 log.info(
1141 log.info(
1109 'not rewriting @LIBDIR@ in %s because install path '
1142 'not rewriting @LIBDIR@ in %s because install path '
1110 'not known' % outfile
1143 'not known' % outfile
1111 )
1144 )
1112 continue
1145 continue
1113
1146
1114 data = data.replace(b'@LIBDIR@', libdir.encode('unicode_escape'))
1147 data = data.replace(b'@LIBDIR@', libdir.encode('unicode_escape'))
1115 with open(outfile, 'wb') as fp:
1148 with open(outfile, 'wb') as fp:
1116 fp.write(data)
1149 fp.write(data)
1117
1150
1118
1151
1119 class hginstallcompletion(Command):
1152 class hginstallcompletion(Command):
1120 description = 'Install shell completion'
1153 description = 'Install shell completion'
1121
1154
1122 def initialize_options(self):
1155 def initialize_options(self):
1123 self.install_dir = None
1156 self.install_dir = None
1124 self.outputs = []
1157 self.outputs = []
1125
1158
1126 def finalize_options(self):
1159 def finalize_options(self):
1127 self.set_undefined_options(
1160 self.set_undefined_options(
1128 'install_data', ('install_dir', 'install_dir')
1161 'install_data', ('install_dir', 'install_dir')
1129 )
1162 )
1130
1163
1131 def get_outputs(self):
1164 def get_outputs(self):
1132 return self.outputs
1165 return self.outputs
1133
1166
1134 def run(self):
1167 def run(self):
1135 for src, dir_path, dest in (
1168 for src, dir_path, dest in (
1136 (
1169 (
1137 'bash_completion',
1170 'bash_completion',
1138 ('share', 'bash-completion', 'completions'),
1171 ('share', 'bash-completion', 'completions'),
1139 'hg',
1172 'hg',
1140 ),
1173 ),
1141 ('zsh_completion', ('share', 'zsh', 'site-functions'), '_hg'),
1174 ('zsh_completion', ('share', 'zsh', 'site-functions'), '_hg'),
1142 ):
1175 ):
1143 dir = os.path.join(self.install_dir, *dir_path)
1176 dir = os.path.join(self.install_dir, *dir_path)
1144 self.mkpath(dir)
1177 self.mkpath(dir)
1145
1178
1146 dest = os.path.join(dir, dest)
1179 dest = os.path.join(dir, dest)
1147 self.outputs.append(dest)
1180 self.outputs.append(dest)
1148 self.copy_file(os.path.join('contrib', src), dest)
1181 self.copy_file(os.path.join('contrib', src), dest)
1149
1182
1150
1183
1151 # virtualenv installs custom distutils/__init__.py and
1184 # virtualenv installs custom distutils/__init__.py and
1152 # distutils/distutils.cfg files which essentially proxy back to the
1185 # distutils/distutils.cfg files which essentially proxy back to the
1153 # "real" distutils in the main Python install. The presence of this
1186 # "real" distutils in the main Python install. The presence of this
1154 # directory causes py2exe to pick up the "hacked" distutils package
1187 # directory causes py2exe to pick up the "hacked" distutils package
1155 # from the virtualenv and "import distutils" will fail from the py2exe
1188 # from the virtualenv and "import distutils" will fail from the py2exe
1156 # build because the "real" distutils files can't be located.
1189 # build because the "real" distutils files can't be located.
1157 #
1190 #
1158 # We work around this by monkeypatching the py2exe code finding Python
1191 # We work around this by monkeypatching the py2exe code finding Python
1159 # modules to replace the found virtualenv distutils modules with the
1192 # modules to replace the found virtualenv distutils modules with the
1160 # original versions via filesystem scanning. This is a bit hacky. But
1193 # original versions via filesystem scanning. This is a bit hacky. But
1161 # it allows us to use virtualenvs for py2exe packaging, which is more
1194 # it allows us to use virtualenvs for py2exe packaging, which is more
1162 # deterministic and reproducible.
1195 # deterministic and reproducible.
1163 #
1196 #
1164 # It's worth noting that the common StackOverflow suggestions for this
1197 # It's worth noting that the common StackOverflow suggestions for this
1165 # problem involve copying the original distutils files into the
1198 # problem involve copying the original distutils files into the
1166 # virtualenv or into the staging directory after setup() is invoked.
1199 # virtualenv or into the staging directory after setup() is invoked.
1167 # The former is very brittle and can easily break setup(). Our hacking
1200 # The former is very brittle and can easily break setup(). Our hacking
1168 # of the found modules routine has a similar result as copying the files
1201 # of the found modules routine has a similar result as copying the files
1169 # manually. But it makes fewer assumptions about how py2exe works and
1202 # manually. But it makes fewer assumptions about how py2exe works and
1170 # is less brittle.
1203 # is less brittle.
1171
1204
1172 # This only catches virtualenvs made with virtualenv (as opposed to
1205 # This only catches virtualenvs made with virtualenv (as opposed to
1173 # venv, which is likely what Python 3 uses).
1206 # venv, which is likely what Python 3 uses).
1174 py2exehacked = py2exeloaded and getattr(sys, 'real_prefix', None) is not None
1207 py2exehacked = py2exeloaded and getattr(sys, 'real_prefix', None) is not None
1175
1208
1176 if py2exehacked:
1209 if py2exehacked:
1177 from distutils.command.py2exe import py2exe as buildpy2exe
1210 from distutils.command.py2exe import py2exe as buildpy2exe
1178 from py2exe.mf import Module as py2exemodule
1211 from py2exe.mf import Module as py2exemodule
1179
1212
1180 class hgbuildpy2exe(buildpy2exe):
1213 class hgbuildpy2exe(buildpy2exe):
1181 def find_needed_modules(self, mf, files, modules):
1214 def find_needed_modules(self, mf, files, modules):
1182 res = buildpy2exe.find_needed_modules(self, mf, files, modules)
1215 res = buildpy2exe.find_needed_modules(self, mf, files, modules)
1183
1216
1184 # Replace virtualenv's distutils modules with the real ones.
1217 # Replace virtualenv's distutils modules with the real ones.
1185 modules = {}
1218 modules = {}
1186 for k, v in res.modules.items():
1219 for k, v in res.modules.items():
1187 if k != 'distutils' and not k.startswith('distutils.'):
1220 if k != 'distutils' and not k.startswith('distutils.'):
1188 modules[k] = v
1221 modules[k] = v
1189
1222
1190 res.modules = modules
1223 res.modules = modules
1191
1224
1192 import opcode
1225 import opcode
1193
1226
1194 distutilsreal = os.path.join(
1227 distutilsreal = os.path.join(
1195 os.path.dirname(opcode.__file__), 'distutils'
1228 os.path.dirname(opcode.__file__), 'distutils'
1196 )
1229 )
1197
1230
1198 for root, dirs, files in os.walk(distutilsreal):
1231 for root, dirs, files in os.walk(distutilsreal):
1199 for f in sorted(files):
1232 for f in sorted(files):
1200 if not f.endswith('.py'):
1233 if not f.endswith('.py'):
1201 continue
1234 continue
1202
1235
1203 full = os.path.join(root, f)
1236 full = os.path.join(root, f)
1204
1237
1205 parents = ['distutils']
1238 parents = ['distutils']
1206
1239
1207 if root != distutilsreal:
1240 if root != distutilsreal:
1208 rel = os.path.relpath(root, distutilsreal)
1241 rel = os.path.relpath(root, distutilsreal)
1209 parents.extend(p for p in rel.split(os.sep))
1242 parents.extend(p for p in rel.split(os.sep))
1210
1243
1211 modname = '%s.%s' % ('.'.join(parents), f[:-3])
1244 modname = '%s.%s' % ('.'.join(parents), f[:-3])
1212
1245
1213 if modname.startswith('distutils.tests.'):
1246 if modname.startswith('distutils.tests.'):
1214 continue
1247 continue
1215
1248
1216 if modname.endswith('.__init__'):
1249 if modname.endswith('.__init__'):
1217 modname = modname[: -len('.__init__')]
1250 modname = modname[: -len('.__init__')]
1218 path = os.path.dirname(full)
1251 path = os.path.dirname(full)
1219 else:
1252 else:
1220 path = None
1253 path = None
1221
1254
1222 res.modules[modname] = py2exemodule(
1255 res.modules[modname] = py2exemodule(
1223 modname, full, path=path
1256 modname, full, path=path
1224 )
1257 )
1225
1258
1226 if 'distutils' not in res.modules:
1259 if 'distutils' not in res.modules:
1227 raise SystemExit('could not find distutils modules')
1260 raise SystemExit('could not find distutils modules')
1228
1261
1229 return res
1262 return res
1230
1263
1231
1264
1232 cmdclass = {
1265 cmdclass = {
1233 'build': hgbuild,
1266 'build': hgbuild,
1234 'build_doc': hgbuilddoc,
1267 'build_doc': hgbuilddoc,
1235 'build_mo': hgbuildmo,
1268 'build_mo': hgbuildmo,
1236 'build_ext': hgbuildext,
1269 'build_ext': hgbuildext,
1237 'build_py': hgbuildpy,
1270 'build_py': hgbuildpy,
1238 'build_scripts': hgbuildscripts,
1271 'build_scripts': hgbuildscripts,
1239 'build_hgextindex': buildhgextindex,
1272 'build_hgextindex': buildhgextindex,
1240 'install': hginstall,
1273 'install': hginstall,
1241 'install_completion': hginstallcompletion,
1274 'install_completion': hginstallcompletion,
1242 'install_lib': hginstalllib,
1275 'install_lib': hginstalllib,
1243 'install_scripts': hginstallscripts,
1276 'install_scripts': hginstallscripts,
1244 'build_hgexe': buildhgexe,
1277 'build_hgexe': buildhgexe,
1245 }
1278 }
1246
1279
1247 if py2exehacked:
1280 if py2exehacked:
1248 cmdclass['py2exe'] = hgbuildpy2exe
1281 cmdclass['py2exe'] = hgbuildpy2exe
1249
1282
1250 packages = [
1283 packages = [
1251 'mercurial',
1284 'mercurial',
1252 'mercurial.cext',
1285 'mercurial.cext',
1253 'mercurial.cffi',
1286 'mercurial.cffi',
1254 'mercurial.defaultrc',
1287 'mercurial.defaultrc',
1255 'mercurial.dirstateutils',
1288 'mercurial.dirstateutils',
1256 'mercurial.helptext',
1289 'mercurial.helptext',
1257 'mercurial.helptext.internals',
1290 'mercurial.helptext.internals',
1258 'mercurial.hgweb',
1291 'mercurial.hgweb',
1259 'mercurial.interfaces',
1292 'mercurial.interfaces',
1260 'mercurial.pure',
1293 'mercurial.pure',
1261 'mercurial.templates',
1294 'mercurial.templates',
1262 'mercurial.thirdparty',
1295 'mercurial.thirdparty',
1263 'mercurial.thirdparty.attr',
1296 'mercurial.thirdparty.attr',
1264 'mercurial.thirdparty.zope',
1297 'mercurial.thirdparty.zope',
1265 'mercurial.thirdparty.zope.interface',
1298 'mercurial.thirdparty.zope.interface',
1266 'mercurial.upgrade_utils',
1299 'mercurial.upgrade_utils',
1267 'mercurial.utils',
1300 'mercurial.utils',
1268 'mercurial.revlogutils',
1301 'mercurial.revlogutils',
1269 'mercurial.testing',
1302 'mercurial.testing',
1270 'hgext',
1303 'hgext',
1271 'hgext.convert',
1304 'hgext.convert',
1272 'hgext.fsmonitor',
1305 'hgext.fsmonitor',
1273 'hgext.fastannotate',
1306 'hgext.fastannotate',
1274 'hgext.fsmonitor.pywatchman',
1307 'hgext.fsmonitor.pywatchman',
1275 'hgext.git',
1308 'hgext.git',
1276 'hgext.highlight',
1309 'hgext.highlight',
1277 'hgext.hooklib',
1310 'hgext.hooklib',
1278 'hgext.infinitepush',
1311 'hgext.infinitepush',
1279 'hgext.largefiles',
1312 'hgext.largefiles',
1280 'hgext.lfs',
1313 'hgext.lfs',
1281 'hgext.narrow',
1314 'hgext.narrow',
1282 'hgext.remotefilelog',
1315 'hgext.remotefilelog',
1283 'hgext.zeroconf',
1316 'hgext.zeroconf',
1284 'hgext3rd',
1317 'hgext3rd',
1285 'hgdemandimport',
1318 'hgdemandimport',
1286 ]
1319 ]
1287
1320
1288 for name in os.listdir(os.path.join('mercurial', 'templates')):
1321 for name in os.listdir(os.path.join('mercurial', 'templates')):
1289 if name != '__pycache__' and os.path.isdir(
1322 if name != '__pycache__' and os.path.isdir(
1290 os.path.join('mercurial', 'templates', name)
1323 os.path.join('mercurial', 'templates', name)
1291 ):
1324 ):
1292 packages.append('mercurial.templates.%s' % name)
1325 packages.append('mercurial.templates.%s' % name)
1293
1326
1294 if 'HG_PY2EXE_EXTRA_INSTALL_PACKAGES' in os.environ:
1327 if 'HG_PY2EXE_EXTRA_INSTALL_PACKAGES' in os.environ:
1295 # py2exe can't cope with namespace packages very well, so we have to
1328 # py2exe can't cope with namespace packages very well, so we have to
1296 # install any hgext3rd.* extensions that we want in the final py2exe
1329 # install any hgext3rd.* extensions that we want in the final py2exe
1297 # image here. This is gross, but you gotta do what you gotta do.
1330 # image here. This is gross, but you gotta do what you gotta do.
1298 packages.extend(os.environ['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'].split(' '))
1331 packages.extend(os.environ['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'].split(' '))
1299
1332
1300 common_depends = [
1333 common_depends = [
1301 'mercurial/bitmanipulation.h',
1334 'mercurial/bitmanipulation.h',
1302 'mercurial/compat.h',
1335 'mercurial/compat.h',
1303 'mercurial/cext/util.h',
1336 'mercurial/cext/util.h',
1304 ]
1337 ]
1305 common_include_dirs = ['mercurial']
1338 common_include_dirs = ['mercurial']
1306
1339
1307 common_cflags = []
1340 common_cflags = []
1308
1341
1309 # MSVC 2008 still needs declarations at the top of the scope, but Python 3.9
1342 # MSVC 2008 still needs declarations at the top of the scope, but Python 3.9
1310 # makes declarations not at the top of a scope in the headers.
1343 # makes declarations not at the top of a scope in the headers.
1311 if os.name != 'nt' and sys.version_info[1] < 9:
1344 if os.name != 'nt' and sys.version_info[1] < 9:
1312 common_cflags = ['-Werror=declaration-after-statement']
1345 common_cflags = ['-Werror=declaration-after-statement']
1313
1346
1314 osutil_cflags = []
1347 osutil_cflags = []
1315 osutil_ldflags = []
1348 osutil_ldflags = []
1316
1349
1317 # platform specific macros
1350 # platform specific macros
1318 for plat, func in [('bsd', 'setproctitle')]:
1351 for plat, func in [('bsd', 'setproctitle')]:
1319 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
1352 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
1320 osutil_cflags.append('-DHAVE_%s' % func.upper())
1353 osutil_cflags.append('-DHAVE_%s' % func.upper())
1321
1354
1322 for plat, macro, code in [
1355 for plat, macro, code in [
1323 (
1356 (
1324 'bsd|darwin',
1357 'bsd|darwin',
1325 'BSD_STATFS',
1358 'BSD_STATFS',
1326 '''
1359 '''
1327 #include <sys/param.h>
1360 #include <sys/param.h>
1328 #include <sys/mount.h>
1361 #include <sys/mount.h>
1329 int main() { struct statfs s; return sizeof(s.f_fstypename); }
1362 int main() { struct statfs s; return sizeof(s.f_fstypename); }
1330 ''',
1363 ''',
1331 ),
1364 ),
1332 (
1365 (
1333 'linux',
1366 'linux',
1334 'LINUX_STATFS',
1367 'LINUX_STATFS',
1335 '''
1368 '''
1336 #include <linux/magic.h>
1369 #include <linux/magic.h>
1337 #include <sys/vfs.h>
1370 #include <sys/vfs.h>
1338 int main() { struct statfs s; return sizeof(s.f_type); }
1371 int main() { struct statfs s; return sizeof(s.f_type); }
1339 ''',
1372 ''',
1340 ),
1373 ),
1341 ]:
1374 ]:
1342 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
1375 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
1343 osutil_cflags.append('-DHAVE_%s' % macro)
1376 osutil_cflags.append('-DHAVE_%s' % macro)
1344
1377
1345 if sys.platform == 'darwin':
1378 if sys.platform == 'darwin':
1346 osutil_ldflags += ['-framework', 'ApplicationServices']
1379 osutil_ldflags += ['-framework', 'ApplicationServices']
1347
1380
1348 if sys.platform == 'sunos5':
1381 if sys.platform == 'sunos5':
1349 osutil_ldflags += ['-lsocket']
1382 osutil_ldflags += ['-lsocket']
1350
1383
1351 xdiff_srcs = [
1384 xdiff_srcs = [
1352 'mercurial/thirdparty/xdiff/xdiffi.c',
1385 'mercurial/thirdparty/xdiff/xdiffi.c',
1353 'mercurial/thirdparty/xdiff/xprepare.c',
1386 'mercurial/thirdparty/xdiff/xprepare.c',
1354 'mercurial/thirdparty/xdiff/xutils.c',
1387 'mercurial/thirdparty/xdiff/xutils.c',
1355 ]
1388 ]
1356
1389
1357 xdiff_headers = [
1390 xdiff_headers = [
1358 'mercurial/thirdparty/xdiff/xdiff.h',
1391 'mercurial/thirdparty/xdiff/xdiff.h',
1359 'mercurial/thirdparty/xdiff/xdiffi.h',
1392 'mercurial/thirdparty/xdiff/xdiffi.h',
1360 'mercurial/thirdparty/xdiff/xinclude.h',
1393 'mercurial/thirdparty/xdiff/xinclude.h',
1361 'mercurial/thirdparty/xdiff/xmacros.h',
1394 'mercurial/thirdparty/xdiff/xmacros.h',
1362 'mercurial/thirdparty/xdiff/xprepare.h',
1395 'mercurial/thirdparty/xdiff/xprepare.h',
1363 'mercurial/thirdparty/xdiff/xtypes.h',
1396 'mercurial/thirdparty/xdiff/xtypes.h',
1364 'mercurial/thirdparty/xdiff/xutils.h',
1397 'mercurial/thirdparty/xdiff/xutils.h',
1365 ]
1398 ]
1366
1399
1367
1400
1368 class RustCompilationError(CCompilerError):
1401 class RustCompilationError(CCompilerError):
1369 """Exception class for Rust compilation errors."""
1402 """Exception class for Rust compilation errors."""
1370
1403
1371
1404
1372 class RustExtension(Extension):
1405 class RustExtension(Extension):
1373 """Base classes for concrete Rust Extension classes."""
1406 """Base classes for concrete Rust Extension classes."""
1374
1407
1375 rusttargetdir = os.path.join('rust', 'target', 'release')
1408 rusttargetdir = os.path.join('rust', 'target', 'release')
1376
1409
1377 def __init__(self, mpath, sources, rustlibname, subcrate, **kw):
1410 def __init__(self, mpath, sources, rustlibname, subcrate, **kw):
1378 Extension.__init__(self, mpath, sources, **kw)
1411 Extension.__init__(self, mpath, sources, **kw)
1379 srcdir = self.rustsrcdir = os.path.join('rust', subcrate)
1412 srcdir = self.rustsrcdir = os.path.join('rust', subcrate)
1380
1413
1381 # adding Rust source and control files to depends so that the extension
1414 # adding Rust source and control files to depends so that the extension
1382 # gets rebuilt if they've changed
1415 # gets rebuilt if they've changed
1383 self.depends.append(os.path.join(srcdir, 'Cargo.toml'))
1416 self.depends.append(os.path.join(srcdir, 'Cargo.toml'))
1384 cargo_lock = os.path.join(srcdir, 'Cargo.lock')
1417 cargo_lock = os.path.join(srcdir, 'Cargo.lock')
1385 if os.path.exists(cargo_lock):
1418 if os.path.exists(cargo_lock):
1386 self.depends.append(cargo_lock)
1419 self.depends.append(cargo_lock)
1387 for dirpath, subdir, fnames in os.walk(os.path.join(srcdir, 'src')):
1420 for dirpath, subdir, fnames in os.walk(os.path.join(srcdir, 'src')):
1388 self.depends.extend(
1421 self.depends.extend(
1389 os.path.join(dirpath, fname)
1422 os.path.join(dirpath, fname)
1390 for fname in fnames
1423 for fname in fnames
1391 if os.path.splitext(fname)[1] == '.rs'
1424 if os.path.splitext(fname)[1] == '.rs'
1392 )
1425 )
1393
1426
1394 @staticmethod
1427 @staticmethod
1395 def rustdylibsuffix():
1428 def rustdylibsuffix():
1396 """Return the suffix for shared libraries produced by rustc.
1429 """Return the suffix for shared libraries produced by rustc.
1397
1430
1398 See also: https://doc.rust-lang.org/reference/linkage.html
1431 See also: https://doc.rust-lang.org/reference/linkage.html
1399 """
1432 """
1400 if sys.platform == 'darwin':
1433 if sys.platform == 'darwin':
1401 return '.dylib'
1434 return '.dylib'
1402 elif os.name == 'nt':
1435 elif os.name == 'nt':
1403 return '.dll'
1436 return '.dll'
1404 else:
1437 else:
1405 return '.so'
1438 return '.so'
1406
1439
1407 def rustbuild(self):
1440 def rustbuild(self):
1408 env = os.environ.copy()
1441 env = os.environ.copy()
1409 if 'HGTEST_RESTOREENV' in env:
1442 if 'HGTEST_RESTOREENV' in env:
1410 # Mercurial tests change HOME to a temporary directory,
1443 # Mercurial tests change HOME to a temporary directory,
1411 # but, if installed with rustup, the Rust toolchain needs
1444 # but, if installed with rustup, the Rust toolchain needs
1412 # HOME to be correct (otherwise the 'no default toolchain'
1445 # HOME to be correct (otherwise the 'no default toolchain'
1413 # error message is issued and the build fails).
1446 # error message is issued and the build fails).
1414 # This happens currently with test-hghave.t, which does
1447 # This happens currently with test-hghave.t, which does
1415 # invoke this build.
1448 # invoke this build.
1416
1449
1417 # Unix only fix (os.path.expanduser not really reliable if
1450 # Unix only fix (os.path.expanduser not really reliable if
1418 # HOME is shadowed like this)
1451 # HOME is shadowed like this)
1419 import pwd
1452 import pwd
1420
1453
1421 env['HOME'] = pwd.getpwuid(os.getuid()).pw_dir
1454 env['HOME'] = pwd.getpwuid(os.getuid()).pw_dir
1422
1455
1423 cargocmd = ['cargo', 'rustc', '--release']
1456 cargocmd = ['cargo', 'rustc', '--release']
1424
1457
1425 rust_features = env.get("HG_RUST_FEATURES")
1458 rust_features = env.get("HG_RUST_FEATURES")
1426 if rust_features:
1459 if rust_features:
1427 cargocmd.extend(('--features', rust_features))
1460 cargocmd.extend(('--features', rust_features))
1428
1461
1429 cargocmd.append('--')
1462 cargocmd.append('--')
1430 if sys.platform == 'darwin':
1463 if sys.platform == 'darwin':
1431 cargocmd.extend(
1464 cargocmd.extend(
1432 ("-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup")
1465 ("-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup")
1433 )
1466 )
1434 try:
1467 try:
1435 subprocess.check_call(cargocmd, env=env, cwd=self.rustsrcdir)
1468 subprocess.check_call(cargocmd, env=env, cwd=self.rustsrcdir)
1436 except FileNotFoundError:
1469 except FileNotFoundError:
1437 raise RustCompilationError("Cargo not found")
1470 raise RustCompilationError("Cargo not found")
1438 except PermissionError:
1471 except PermissionError:
1439 raise RustCompilationError(
1472 raise RustCompilationError(
1440 "Cargo found, but permission to execute it is denied"
1473 "Cargo found, but permission to execute it is denied"
1441 )
1474 )
1442 except subprocess.CalledProcessError:
1475 except subprocess.CalledProcessError:
1443 raise RustCompilationError(
1476 raise RustCompilationError(
1444 "Cargo failed. Working directory: %r, "
1477 "Cargo failed. Working directory: %r, "
1445 "command: %r, environment: %r"
1478 "command: %r, environment: %r"
1446 % (self.rustsrcdir, cargocmd, env)
1479 % (self.rustsrcdir, cargocmd, env)
1447 )
1480 )
1448
1481
1449
1482
1450 class RustStandaloneExtension(RustExtension):
1483 class RustStandaloneExtension(RustExtension):
1451 def __init__(self, pydottedname, rustcrate, dylibname, **kw):
1484 def __init__(self, pydottedname, rustcrate, dylibname, **kw):
1452 RustExtension.__init__(
1485 RustExtension.__init__(
1453 self, pydottedname, [], dylibname, rustcrate, **kw
1486 self, pydottedname, [], dylibname, rustcrate, **kw
1454 )
1487 )
1455 self.dylibname = dylibname
1488 self.dylibname = dylibname
1456
1489
1457 def build(self, target_dir):
1490 def build(self, target_dir):
1458 self.rustbuild()
1491 self.rustbuild()
1459 target = [target_dir]
1492 target = [target_dir]
1460 target.extend(self.name.split('.'))
1493 target.extend(self.name.split('.'))
1461 target[-1] += DYLIB_SUFFIX
1494 target[-1] += DYLIB_SUFFIX
1462 target = os.path.join(*target)
1495 target = os.path.join(*target)
1463 os.makedirs(os.path.dirname(target), exist_ok=True)
1496 os.makedirs(os.path.dirname(target), exist_ok=True)
1464 shutil.copy2(
1497 shutil.copy2(
1465 os.path.join(
1498 os.path.join(
1466 self.rusttargetdir, self.dylibname + self.rustdylibsuffix()
1499 self.rusttargetdir, self.dylibname + self.rustdylibsuffix()
1467 ),
1500 ),
1468 target,
1501 target,
1469 )
1502 )
1470
1503
1471
1504
1472 extmodules = [
1505 extmodules = [
1473 Extension(
1506 Extension(
1474 'mercurial.cext.base85',
1507 'mercurial.cext.base85',
1475 ['mercurial/cext/base85.c'],
1508 ['mercurial/cext/base85.c'],
1476 include_dirs=common_include_dirs,
1509 include_dirs=common_include_dirs,
1477 extra_compile_args=common_cflags,
1510 extra_compile_args=common_cflags,
1478 depends=common_depends,
1511 depends=common_depends,
1479 ),
1512 ),
1480 Extension(
1513 Extension(
1481 'mercurial.cext.bdiff',
1514 'mercurial.cext.bdiff',
1482 ['mercurial/bdiff.c', 'mercurial/cext/bdiff.c'] + xdiff_srcs,
1515 ['mercurial/bdiff.c', 'mercurial/cext/bdiff.c'] + xdiff_srcs,
1483 include_dirs=common_include_dirs,
1516 include_dirs=common_include_dirs,
1484 extra_compile_args=common_cflags,
1517 extra_compile_args=common_cflags,
1485 depends=common_depends + ['mercurial/bdiff.h'] + xdiff_headers,
1518 depends=common_depends + ['mercurial/bdiff.h'] + xdiff_headers,
1486 ),
1519 ),
1487 Extension(
1520 Extension(
1488 'mercurial.cext.mpatch',
1521 'mercurial.cext.mpatch',
1489 ['mercurial/mpatch.c', 'mercurial/cext/mpatch.c'],
1522 ['mercurial/mpatch.c', 'mercurial/cext/mpatch.c'],
1490 include_dirs=common_include_dirs,
1523 include_dirs=common_include_dirs,
1491 extra_compile_args=common_cflags,
1524 extra_compile_args=common_cflags,
1492 depends=common_depends,
1525 depends=common_depends,
1493 ),
1526 ),
1494 Extension(
1527 Extension(
1495 'mercurial.cext.parsers',
1528 'mercurial.cext.parsers',
1496 [
1529 [
1497 'mercurial/cext/charencode.c',
1530 'mercurial/cext/charencode.c',
1498 'mercurial/cext/dirs.c',
1531 'mercurial/cext/dirs.c',
1499 'mercurial/cext/manifest.c',
1532 'mercurial/cext/manifest.c',
1500 'mercurial/cext/parsers.c',
1533 'mercurial/cext/parsers.c',
1501 'mercurial/cext/pathencode.c',
1534 'mercurial/cext/pathencode.c',
1502 'mercurial/cext/revlog.c',
1535 'mercurial/cext/revlog.c',
1503 ],
1536 ],
1504 include_dirs=common_include_dirs,
1537 include_dirs=common_include_dirs,
1505 extra_compile_args=common_cflags,
1538 extra_compile_args=common_cflags,
1506 depends=common_depends
1539 depends=common_depends
1507 + [
1540 + [
1508 'mercurial/cext/charencode.h',
1541 'mercurial/cext/charencode.h',
1509 'mercurial/cext/revlog.h',
1542 'mercurial/cext/revlog.h',
1510 ],
1543 ],
1511 ),
1544 ),
1512 Extension(
1545 Extension(
1513 'mercurial.cext.osutil',
1546 'mercurial.cext.osutil',
1514 ['mercurial/cext/osutil.c'],
1547 ['mercurial/cext/osutil.c'],
1515 include_dirs=common_include_dirs,
1548 include_dirs=common_include_dirs,
1516 extra_compile_args=common_cflags + osutil_cflags,
1549 extra_compile_args=common_cflags + osutil_cflags,
1517 extra_link_args=osutil_ldflags,
1550 extra_link_args=osutil_ldflags,
1518 depends=common_depends,
1551 depends=common_depends,
1519 ),
1552 ),
1520 Extension(
1553 Extension(
1521 'mercurial.thirdparty.zope.interface._zope_interface_coptimizations',
1554 'mercurial.thirdparty.zope.interface._zope_interface_coptimizations',
1522 [
1555 [
1523 'mercurial/thirdparty/zope/interface/_zope_interface_coptimizations.c',
1556 'mercurial/thirdparty/zope/interface/_zope_interface_coptimizations.c',
1524 ],
1557 ],
1525 extra_compile_args=common_cflags,
1558 extra_compile_args=common_cflags,
1526 ),
1559 ),
1527 Extension(
1560 Extension(
1528 'mercurial.thirdparty.sha1dc',
1561 'mercurial.thirdparty.sha1dc',
1529 [
1562 [
1530 'mercurial/thirdparty/sha1dc/cext.c',
1563 'mercurial/thirdparty/sha1dc/cext.c',
1531 'mercurial/thirdparty/sha1dc/lib/sha1.c',
1564 'mercurial/thirdparty/sha1dc/lib/sha1.c',
1532 'mercurial/thirdparty/sha1dc/lib/ubc_check.c',
1565 'mercurial/thirdparty/sha1dc/lib/ubc_check.c',
1533 ],
1566 ],
1534 extra_compile_args=common_cflags,
1567 extra_compile_args=common_cflags,
1535 ),
1568 ),
1536 Extension(
1569 Extension(
1537 'hgext.fsmonitor.pywatchman.bser',
1570 'hgext.fsmonitor.pywatchman.bser',
1538 ['hgext/fsmonitor/pywatchman/bser.c'],
1571 ['hgext/fsmonitor/pywatchman/bser.c'],
1539 extra_compile_args=common_cflags,
1572 extra_compile_args=common_cflags,
1540 ),
1573 ),
1541 RustStandaloneExtension(
1574 RustStandaloneExtension(
1542 'mercurial.rustext',
1575 'mercurial.rustext',
1543 'hg-cpython',
1576 'hg-cpython',
1544 'librusthg',
1577 'librusthg',
1545 ),
1578 ),
1546 ]
1579 ]
1547
1580
1548
1581
1549 sys.path.insert(0, 'contrib/python-zstandard')
1582 sys.path.insert(0, 'contrib/python-zstandard')
1550 import setup_zstd
1583 import setup_zstd
1551
1584
1552 zstd = setup_zstd.get_c_extension(
1585 zstd = setup_zstd.get_c_extension(
1553 name='mercurial.zstd', root=os.path.abspath(os.path.dirname(__file__))
1586 name='mercurial.zstd', root=os.path.abspath(os.path.dirname(__file__))
1554 )
1587 )
1555 zstd.extra_compile_args += common_cflags
1588 zstd.extra_compile_args += common_cflags
1556 extmodules.append(zstd)
1589 extmodules.append(zstd)
1557
1590
1558 try:
1591 try:
1559 from distutils import cygwinccompiler
1592 from distutils import cygwinccompiler
1560
1593
1561 # the -mno-cygwin option has been deprecated for years
1594 # the -mno-cygwin option has been deprecated for years
1562 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
1595 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
1563
1596
1564 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
1597 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
1565 def __init__(self, *args, **kwargs):
1598 def __init__(self, *args, **kwargs):
1566 mingw32compilerclass.__init__(self, *args, **kwargs)
1599 mingw32compilerclass.__init__(self, *args, **kwargs)
1567 for i in 'compiler compiler_so linker_exe linker_so'.split():
1600 for i in 'compiler compiler_so linker_exe linker_so'.split():
1568 try:
1601 try:
1569 getattr(self, i).remove('-mno-cygwin')
1602 getattr(self, i).remove('-mno-cygwin')
1570 except ValueError:
1603 except ValueError:
1571 pass
1604 pass
1572
1605
1573 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
1606 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
1574 except ImportError:
1607 except ImportError:
1575 # the cygwinccompiler package is not available on some Python
1608 # the cygwinccompiler package is not available on some Python
1576 # distributions like the ones from the optware project for Synology
1609 # distributions like the ones from the optware project for Synology
1577 # DiskStation boxes
1610 # DiskStation boxes
1578 class HackedMingw32CCompiler:
1611 class HackedMingw32CCompiler:
1579 pass
1612 pass
1580
1613
1581
1614
1582 if os.name == 'nt':
1615 if os.name == 'nt':
1583 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
1616 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
1584 # extra_link_args to distutils.extensions.Extension() doesn't have any
1617 # extra_link_args to distutils.extensions.Extension() doesn't have any
1585 # effect.
1618 # effect.
1586 from distutils import msvccompiler
1619 from distutils import msvccompiler
1587
1620
1588 msvccompilerclass = msvccompiler.MSVCCompiler
1621 msvccompilerclass = msvccompiler.MSVCCompiler
1589
1622
1590 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
1623 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
1591 def initialize(self):
1624 def initialize(self):
1592 msvccompilerclass.initialize(self)
1625 msvccompilerclass.initialize(self)
1593 # "warning LNK4197: export 'func' specified multiple times"
1626 # "warning LNK4197: export 'func' specified multiple times"
1594 self.ldflags_shared.append('/ignore:4197')
1627 self.ldflags_shared.append('/ignore:4197')
1595 self.ldflags_shared_debug.append('/ignore:4197')
1628 self.ldflags_shared_debug.append('/ignore:4197')
1596
1629
1597 msvccompiler.MSVCCompiler = HackedMSVCCompiler
1630 msvccompiler.MSVCCompiler = HackedMSVCCompiler
1598
1631
1599 packagedata = {
1632 packagedata = {
1600 'mercurial': [
1633 'mercurial': [
1601 'locale/*/LC_MESSAGES/hg.mo',
1634 'locale/*/LC_MESSAGES/hg.mo',
1602 'dummycert.pem',
1635 'dummycert.pem',
1603 ],
1636 ],
1604 'mercurial.defaultrc': [
1637 'mercurial.defaultrc': [
1605 '*.rc',
1638 '*.rc',
1606 ],
1639 ],
1607 'mercurial.helptext': [
1640 'mercurial.helptext': [
1608 '*.txt',
1641 '*.txt',
1609 ],
1642 ],
1610 'mercurial.helptext.internals': [
1643 'mercurial.helptext.internals': [
1611 '*.txt',
1644 '*.txt',
1612 ],
1645 ],
1613 'mercurial.thirdparty.attr': [
1646 'mercurial.thirdparty.attr': [
1614 '*.pyi',
1647 '*.pyi',
1615 'py.typed',
1648 'py.typed',
1616 ],
1649 ],
1617 }
1650 }
1618
1651
1619
1652
1620 def ordinarypath(p):
1653 def ordinarypath(p):
1621 return p and p[0] != '.' and p[-1] != '~'
1654 return p and p[0] != '.' and p[-1] != '~'
1622
1655
1623
1656
1624 for root in ('templates',):
1657 for root in ('templates',):
1625 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
1658 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
1626 packagename = curdir.replace(os.sep, '.')
1659 packagename = curdir.replace(os.sep, '.')
1627 packagedata[packagename] = list(filter(ordinarypath, files))
1660 packagedata[packagename] = list(filter(ordinarypath, files))
1628
1661
1629 datafiles = []
1662 datafiles = []
1630
1663
1631 # distutils expects version to be str/unicode. Converting it to
1664 # distutils expects version to be str/unicode. Converting it to
1632 # unicode on Python 2 still works because it won't contain any
1665 # unicode on Python 2 still works because it won't contain any
1633 # non-ascii bytes and will be implicitly converted back to bytes
1666 # non-ascii bytes and will be implicitly converted back to bytes
1634 # when operated on.
1667 # when operated on.
1635 assert isinstance(version, str)
1668 assert isinstance(version, str)
1636 setupversion = version
1669 setupversion = version
1637
1670
1638 extra = {}
1671 extra = {}
1639
1672
1640 py2exepackages = [
1673 py2exepackages = [
1641 'hgdemandimport',
1674 'hgdemandimport',
1642 'hgext3rd',
1675 'hgext3rd',
1643 'hgext',
1676 'hgext',
1644 'email',
1677 'email',
1645 # implicitly imported per module policy
1678 # implicitly imported per module policy
1646 # (cffi wouldn't be used as a frozen exe)
1679 # (cffi wouldn't be used as a frozen exe)
1647 'mercurial.cext',
1680 'mercurial.cext',
1648 #'mercurial.cffi',
1681 #'mercurial.cffi',
1649 'mercurial.pure',
1682 'mercurial.pure',
1650 ]
1683 ]
1651
1684
1652 py2exe_includes = []
1685 py2exe_includes = []
1653
1686
1654 py2exeexcludes = []
1687 py2exeexcludes = []
1655 py2exedllexcludes = ['crypt32.dll']
1688 py2exedllexcludes = ['crypt32.dll']
1656
1689
1657 if issetuptools:
1690 if issetuptools:
1658 extra['python_requires'] = supportedpy
1691 extra['python_requires'] = supportedpy
1659
1692
1660 if py2exeloaded:
1693 if py2exeloaded:
1661 extra['console'] = [
1694 extra['console'] = [
1662 {
1695 {
1663 'script': 'hg',
1696 'script': 'hg',
1664 'copyright': 'Copyright (C) 2005-2023 Olivia Mackall and others',
1697 'copyright': 'Copyright (C) 2005-2023 Olivia Mackall and others',
1665 'product_version': version,
1698 'product_version': version,
1666 }
1699 }
1667 ]
1700 ]
1668 # Sub command of 'build' because 'py2exe' does not handle sub_commands.
1701 # Sub command of 'build' because 'py2exe' does not handle sub_commands.
1669 # Need to override hgbuild because it has a private copy of
1702 # Need to override hgbuild because it has a private copy of
1670 # build.sub_commands.
1703 # build.sub_commands.
1671 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1704 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1672 # put dlls in sub directory so that they won't pollute PATH
1705 # put dlls in sub directory so that they won't pollute PATH
1673 extra['zipfile'] = 'lib/library.zip'
1706 extra['zipfile'] = 'lib/library.zip'
1674
1707
1675 # We allow some configuration to be supplemented via environment
1708 # We allow some configuration to be supplemented via environment
1676 # variables. This is better than setup.cfg files because it allows
1709 # variables. This is better than setup.cfg files because it allows
1677 # supplementing configs instead of replacing them.
1710 # supplementing configs instead of replacing them.
1678 extrapackages = os.environ.get('HG_PY2EXE_EXTRA_PACKAGES')
1711 extrapackages = os.environ.get('HG_PY2EXE_EXTRA_PACKAGES')
1679 if extrapackages:
1712 if extrapackages:
1680 py2exepackages.extend(extrapackages.split(' '))
1713 py2exepackages.extend(extrapackages.split(' '))
1681
1714
1682 extra_includes = os.environ.get('HG_PY2EXE_EXTRA_INCLUDES')
1715 extra_includes = os.environ.get('HG_PY2EXE_EXTRA_INCLUDES')
1683 if extra_includes:
1716 if extra_includes:
1684 py2exe_includes.extend(extra_includes.split(' '))
1717 py2exe_includes.extend(extra_includes.split(' '))
1685
1718
1686 excludes = os.environ.get('HG_PY2EXE_EXTRA_EXCLUDES')
1719 excludes = os.environ.get('HG_PY2EXE_EXTRA_EXCLUDES')
1687 if excludes:
1720 if excludes:
1688 py2exeexcludes.extend(excludes.split(' '))
1721 py2exeexcludes.extend(excludes.split(' '))
1689
1722
1690 dllexcludes = os.environ.get('HG_PY2EXE_EXTRA_DLL_EXCLUDES')
1723 dllexcludes = os.environ.get('HG_PY2EXE_EXTRA_DLL_EXCLUDES')
1691 if dllexcludes:
1724 if dllexcludes:
1692 py2exedllexcludes.extend(dllexcludes.split(' '))
1725 py2exedllexcludes.extend(dllexcludes.split(' '))
1693
1726
1694 if os.environ.get('PYOXIDIZER'):
1727 if os.environ.get('PYOXIDIZER'):
1695 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1728 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1696
1729
1697 if os.name == 'nt':
1730 if os.name == 'nt':
1698 # Windows binary file versions for exe/dll files must have the
1731 # Windows binary file versions for exe/dll files must have the
1699 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
1732 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
1700 setupversion = setupversion.split(r'+', 1)[0]
1733 setupversion = setupversion.split(r'+', 1)[0]
1701
1734
1702 setup(
1735 setup(
1703 name='mercurial',
1736 name='mercurial',
1704 version=setupversion,
1737 version=setupversion,
1705 author='Olivia Mackall and many others',
1738 author='Olivia Mackall and many others',
1706 author_email='mercurial@mercurial-scm.org',
1739 author_email='mercurial@mercurial-scm.org',
1707 url='https://mercurial-scm.org/',
1740 url='https://mercurial-scm.org/',
1708 download_url='https://mercurial-scm.org/release/',
1741 download_url='https://mercurial-scm.org/release/',
1709 description=(
1742 description=(
1710 'Fast scalable distributed SCM (revision control, version '
1743 'Fast scalable distributed SCM (revision control, version '
1711 'control) system'
1744 'control) system'
1712 ),
1745 ),
1713 long_description=(
1746 long_description=(
1714 'Mercurial is a distributed SCM tool written in Python.'
1747 'Mercurial is a distributed SCM tool written in Python.'
1715 ' It is used by a number of large projects that require'
1748 ' It is used by a number of large projects that require'
1716 ' fast, reliable distributed revision control, such as '
1749 ' fast, reliable distributed revision control, such as '
1717 'Mozilla.'
1750 'Mozilla.'
1718 ),
1751 ),
1719 license='GNU GPLv2 or any later version',
1752 license='GNU GPLv2 or any later version',
1720 classifiers=[
1753 classifiers=[
1721 'Development Status :: 6 - Mature',
1754 'Development Status :: 6 - Mature',
1722 'Environment :: Console',
1755 'Environment :: Console',
1723 'Intended Audience :: Developers',
1756 'Intended Audience :: Developers',
1724 'Intended Audience :: System Administrators',
1757 'Intended Audience :: System Administrators',
1725 'License :: OSI Approved :: GNU General Public License (GPL)',
1758 'License :: OSI Approved :: GNU General Public License (GPL)',
1726 'Natural Language :: Danish',
1759 'Natural Language :: Danish',
1727 'Natural Language :: English',
1760 'Natural Language :: English',
1728 'Natural Language :: German',
1761 'Natural Language :: German',
1729 'Natural Language :: Italian',
1762 'Natural Language :: Italian',
1730 'Natural Language :: Japanese',
1763 'Natural Language :: Japanese',
1731 'Natural Language :: Portuguese (Brazilian)',
1764 'Natural Language :: Portuguese (Brazilian)',
1732 'Operating System :: Microsoft :: Windows',
1765 'Operating System :: Microsoft :: Windows',
1733 'Operating System :: OS Independent',
1766 'Operating System :: OS Independent',
1734 'Operating System :: POSIX',
1767 'Operating System :: POSIX',
1735 'Programming Language :: C',
1768 'Programming Language :: C',
1736 'Programming Language :: Python',
1769 'Programming Language :: Python',
1737 'Topic :: Software Development :: Version Control',
1770 'Topic :: Software Development :: Version Control',
1738 ],
1771 ],
1739 scripts=scripts,
1772 scripts=scripts,
1740 packages=packages,
1773 packages=packages,
1741 ext_modules=extmodules,
1774 ext_modules=extmodules,
1742 data_files=datafiles,
1775 data_files=datafiles,
1743 package_data=packagedata,
1776 package_data=packagedata,
1744 cmdclass=cmdclass,
1777 cmdclass=cmdclass,
1745 distclass=hgdist,
1778 distclass=hgdist,
1746 options={
1779 options={
1747 'py2exe': {
1780 'py2exe': {
1748 'bundle_files': 3,
1781 'bundle_files': 3,
1749 'dll_excludes': py2exedllexcludes,
1782 'dll_excludes': py2exedllexcludes,
1750 'includes': py2exe_includes,
1783 'includes': py2exe_includes,
1751 'excludes': py2exeexcludes,
1784 'excludes': py2exeexcludes,
1752 'packages': py2exepackages,
1785 'packages': py2exepackages,
1753 },
1786 },
1754 'bdist_mpkg': {
1787 'bdist_mpkg': {
1755 'zipdist': False,
1788 'zipdist': False,
1756 'license': 'COPYING',
1789 'license': 'COPYING',
1757 'readme': 'contrib/packaging/macosx/Readme.html',
1790 'readme': 'contrib/packaging/macosx/Readme.html',
1758 'welcome': 'contrib/packaging/macosx/Welcome.html',
1791 'welcome': 'contrib/packaging/macosx/Welcome.html',
1759 },
1792 },
1760 },
1793 },
1761 **extra
1794 **extra
1762 )
1795 )
@@ -1,558 +1,555 b''
1 #require chg
1 #require chg
2
2
3 Scale the timeout for the chg-server to the test timeout scaling.
3 Scale the timeout for the chg-server to the test timeout scaling.
4 This is done to reduce the flakiness of this test on heavy load.
4 This is done to reduce the flakiness of this test on heavy load.
5
5
6 $ CHGTIMEOUT=`expr $HGTEST_TIMEOUT / 6`
6 $ CHGTIMEOUT=`expr $HGTEST_TIMEOUT / 6`
7 $ export CHGTIMEOUT
7 $ export CHGTIMEOUT
8
8
9 $ mkdir log
9 $ mkdir log
10 $ cp $HGRCPATH $HGRCPATH.unconfigured
10 $ cp $HGRCPATH $HGRCPATH.unconfigured
11 $ cat <<'EOF' >> $HGRCPATH
11 $ cat <<'EOF' >> $HGRCPATH
12 > [cmdserver]
12 > [cmdserver]
13 > log = $TESTTMP/log/server.log
13 > log = $TESTTMP/log/server.log
14 > max-log-files = 1
14 > max-log-files = 1
15 > max-log-size = 10 kB
15 > max-log-size = 10 kB
16 > EOF
16 > EOF
17 $ cp $HGRCPATH $HGRCPATH.orig
17 $ cp $HGRCPATH $HGRCPATH.orig
18
18
19 $ filterlog () {
19 $ filterlog () {
20 > sed -e 's!^[0-9/]* [0-9:]* ([0-9]*)>!YYYY/MM/DD HH:MM:SS (PID)>!' \
20 > sed -e 's!^[0-9/]* [0-9:]* ([0-9]*)>!YYYY/MM/DD HH:MM:SS (PID)>!' \
21 > -e 's!\(setprocname\|received fds\|setenv\): .*!\1: ...!' \
21 > -e 's!\(setprocname\|received fds\|setenv\): .*!\1: ...!' \
22 > -e 's!\(confighash\|mtimehash\) = [0-9a-f]*!\1 = ...!g' \
22 > -e 's!\(confighash\|mtimehash\) = [0-9a-f]*!\1 = ...!g' \
23 > -e 's!\(in \)[0-9.]*s\b!\1 ...s!g' \
23 > -e 's!\(in \)[0-9.]*s\b!\1 ...s!g' \
24 > -e 's!\(pid\)=[0-9]*!\1=...!g' \
24 > -e 's!\(pid\)=[0-9]*!\1=...!g' \
25 > -e 's!\(/server-\)[0-9a-f]*!\1...!g'
25 > -e 's!\(/server-\)[0-9a-f]*!\1...!g'
26 > }
26 > }
27
27
28 init repo
28 init repo
29
29
30 $ chg init foo
30 $ chg init foo
31 $ cd foo
31 $ cd foo
32
32
33 ill-formed config
33 ill-formed config
34
34
35 $ chg status
35 $ chg status
36 $ echo '=brokenconfig' >> $HGRCPATH
36 $ echo '=brokenconfig' >> $HGRCPATH
37 $ chg status
37 $ chg status
38 config error at * =brokenconfig (glob)
38 config error at * =brokenconfig (glob)
39 [30]
39 [30]
40
40
41 $ cp $HGRCPATH.orig $HGRCPATH
41 $ cp $HGRCPATH.orig $HGRCPATH
42
42
43 long socket path
43 long socket path
44
44
45 $ sockpath=$TESTTMP/this/path/should/be/longer/than/one-hundred-and-seven/characters/where/107/is/the/typical/size/limit/of/unix-domain-socket
45 $ sockpath=$TESTTMP/this/path/should/be/longer/than/one-hundred-and-seven/characters/where/107/is/the/typical/size/limit/of/unix-domain-socket
46 $ mkdir -p $sockpath
46 $ mkdir -p $sockpath
47 $ bakchgsockname=$CHGSOCKNAME
47 $ bakchgsockname=$CHGSOCKNAME
48 $ CHGSOCKNAME=$sockpath/server
48 $ CHGSOCKNAME=$sockpath/server
49 $ export CHGSOCKNAME
49 $ export CHGSOCKNAME
50 $ chg root
50 $ chg root
51 $TESTTMP/foo
51 $TESTTMP/foo
52 $ rm -rf $sockpath
52 $ rm -rf $sockpath
53 $ CHGSOCKNAME=$bakchgsockname
53 $ CHGSOCKNAME=$bakchgsockname
54 $ export CHGSOCKNAME
54 $ export CHGSOCKNAME
55
55
56 $ cd ..
56 $ cd ..
57
57
58 editor
58 editor
59 ------
59 ------
60
60
61 $ cat >> pushbuffer.py <<EOF
61 $ cat >> pushbuffer.py <<EOF
62 > def reposetup(ui, repo):
62 > def reposetup(ui, repo):
63 > repo.ui.pushbuffer(subproc=True)
63 > repo.ui.pushbuffer(subproc=True)
64 > EOF
64 > EOF
65
65
66 $ chg init editor
66 $ chg init editor
67 $ cd editor
67 $ cd editor
68
68
69 by default, system() should be redirected to the client:
69 by default, system() should be redirected to the client:
70
70
71 $ touch foo
71 $ touch foo
72 $ CHGDEBUG= HGEDITOR=cat chg ci -Am channeled --edit 2>&1 \
72 $ CHGDEBUG= HGEDITOR=cat chg ci -Am channeled --edit 2>&1 \
73 > | egrep "HG:|run 'cat"
73 > | egrep "HG:|run 'cat"
74 chg: debug: * run 'cat "*"' at '$TESTTMP/editor' (glob)
74 chg: debug: * run 'cat "*"' at '$TESTTMP/editor' (glob)
75 HG: Enter commit message. Lines beginning with 'HG:' are removed.
75 HG: Enter commit message. Lines beginning with 'HG:' are removed.
76 HG: Leave message empty to abort commit.
76 HG: Leave message empty to abort commit.
77 HG: --
77 HG: --
78 HG: user: test
78 HG: user: test
79 HG: branch 'default'
79 HG: branch 'default'
80 HG: added foo
80 HG: added foo
81
81
82 but no redirection should be made if output is captured:
82 but no redirection should be made if output is captured:
83
83
84 $ touch bar
84 $ touch bar
85 $ CHGDEBUG= HGEDITOR=cat chg ci -Am bufferred --edit \
85 $ CHGDEBUG= HGEDITOR=cat chg ci -Am bufferred --edit \
86 > --config extensions.pushbuffer="$TESTTMP/pushbuffer.py" 2>&1 \
86 > --config extensions.pushbuffer="$TESTTMP/pushbuffer.py" 2>&1 \
87 > | egrep "HG:|run 'cat"
87 > | egrep "HG:|run 'cat"
88 [1]
88 [1]
89
89
90 check that commit commands succeeded:
90 check that commit commands succeeded:
91
91
92 $ hg log -T '{rev}:{desc}\n'
92 $ hg log -T '{rev}:{desc}\n'
93 1:bufferred
93 1:bufferred
94 0:channeled
94 0:channeled
95
95
96 $ cd ..
96 $ cd ..
97
97
98 pager
98 pager
99 -----
99 -----
100
100
101 $ cat >> fakepager.py <<EOF
101 $ cat >> fakepager.py <<EOF
102 > import sys
102 > import sys
103 > for line in sys.stdin:
103 > for line in sys.stdin:
104 > sys.stdout.write('paged! %r\n' % line)
104 > sys.stdout.write('paged! %r\n' % line)
105 > EOF
105 > EOF
106
106
107 enable pager extension globally, but spawns the master server with no tty:
107 enable pager extension globally, but spawns the master server with no tty:
108
108
109 $ chg init pager
109 $ chg init pager
110 $ cd pager
110 $ cd pager
111 $ cat >> $HGRCPATH <<EOF
111 $ cat >> $HGRCPATH <<EOF
112 > [extensions]
112 > [extensions]
113 > pager =
113 > pager =
114 > [pager]
114 > [pager]
115 > pager = "$PYTHON" $TESTTMP/fakepager.py
115 > pager = "$PYTHON" $TESTTMP/fakepager.py
116 > EOF
116 > EOF
117 $ chg version > /dev/null
117 $ chg version > /dev/null
118 $ touch foo
118 $ touch foo
119 $ chg ci -qAm foo
119 $ chg ci -qAm foo
120
120
121 pager should be enabled if the attached client has a tty:
121 pager should be enabled if the attached client has a tty:
122
122
123 $ chg log -l1 -q --config ui.formatted=True
123 $ chg log -l1 -q --config ui.formatted=True
124 paged! '0:1f7b0de80e11\n'
124 paged! '0:1f7b0de80e11\n'
125 $ chg log -l1 -q --config ui.formatted=False
125 $ chg log -l1 -q --config ui.formatted=False
126 0:1f7b0de80e11
126 0:1f7b0de80e11
127
127
128 chg waits for pager if runcommand raises
128 chg waits for pager if runcommand raises
129
129
130 $ cat > $TESTTMP/crash.py <<EOF
130 $ cat > $TESTTMP/crash.py <<EOF
131 > from mercurial import registrar
131 > from mercurial import registrar
132 > cmdtable = {}
132 > cmdtable = {}
133 > command = registrar.command(cmdtable)
133 > command = registrar.command(cmdtable)
134 > @command(b'crash')
134 > @command(b'crash')
135 > def pagercrash(ui, repo, *pats, **opts):
135 > def pagercrash(ui, repo, *pats, **opts):
136 > ui.write(b'going to crash\n')
136 > ui.write(b'going to crash\n')
137 > raise Exception('.')
137 > raise Exception('.')
138 > EOF
138 > EOF
139
139
140 $ cat > $TESTTMP/fakepager.py <<EOF
140 $ cat > $TESTTMP/fakepager.py <<EOF
141 > import sys
141 > import sys
142 > import time
142 > import time
143 > for line in iter(sys.stdin.readline, ''):
143 > for line in iter(sys.stdin.readline, ''):
144 > if 'crash' in line: # only interested in lines containing 'crash'
144 > if 'crash' in line: # only interested in lines containing 'crash'
145 > # if chg exits when pager is sleeping (incorrectly), the output
145 > # if chg exits when pager is sleeping (incorrectly), the output
146 > # will be captured by the next test case
146 > # will be captured by the next test case
147 > time.sleep(1)
147 > time.sleep(1)
148 > sys.stdout.write('crash-pager: %s' % line)
148 > sys.stdout.write('crash-pager: %s' % line)
149 > EOF
149 > EOF
150
150
151 $ cat >> .hg/hgrc <<EOF
151 $ cat >> .hg/hgrc <<EOF
152 > [extensions]
152 > [extensions]
153 > crash = $TESTTMP/crash.py
153 > crash = $TESTTMP/crash.py
154 > EOF
154 > EOF
155
155
156 $ chg crash --pager=on --config ui.formatted=True 2>/dev/null
156 $ chg crash --pager=on --config ui.formatted=True 2>/dev/null
157 crash-pager: going to crash
157 crash-pager: going to crash
158 [255]
158 [255]
159
159
160 no stdout data should be printed after pager quits, and the buffered data
160 no stdout data should be printed after pager quits, and the buffered data
161 should never persist (issue6207)
161 should never persist (issue6207)
162
162
163 "killed!" may be printed if terminated by SIGPIPE, which isn't important
163 "killed!" may be printed if terminated by SIGPIPE, which isn't important
164 in this test.
164 in this test.
165
165
166 $ cat > $TESTTMP/bulkwrite.py <<'EOF'
166 $ cat > $TESTTMP/bulkwrite.py <<'EOF'
167 > import time
167 > import time
168 > from mercurial import error, registrar
168 > from mercurial import error, registrar
169 > cmdtable = {}
169 > cmdtable = {}
170 > command = registrar.command(cmdtable)
170 > command = registrar.command(cmdtable)
171 > @command(b'bulkwrite')
171 > @command(b'bulkwrite')
172 > def bulkwrite(ui, repo, *pats, **opts):
172 > def bulkwrite(ui, repo, *pats, **opts):
173 > ui.write(b'going to write massive data\n')
173 > ui.write(b'going to write massive data\n')
174 > ui.flush()
174 > ui.flush()
175 > t = time.time()
175 > t = time.time()
176 > while time.time() - t < 2:
176 > while time.time() - t < 2:
177 > ui.write(b'x' * 1023 + b'\n') # will be interrupted by SIGPIPE
177 > ui.write(b'x' * 1023 + b'\n') # will be interrupted by SIGPIPE
178 > raise error.Abort(b"write() doesn't block")
178 > raise error.Abort(b"write() doesn't block")
179 > EOF
179 > EOF
180
180
181 $ cat > $TESTTMP/fakepager.py <<'EOF'
181 $ cat > $TESTTMP/fakepager.py <<'EOF'
182 > import sys
182 > import sys
183 > import time
183 > import time
184 > sys.stdout.write('paged! %r\n' % sys.stdin.readline())
184 > sys.stdout.write('paged! %r\n' % sys.stdin.readline())
185 > time.sleep(1) # new data will be written
185 > time.sleep(1) # new data will be written
186 > EOF
186 > EOF
187
187
188 $ cat >> .hg/hgrc <<EOF
188 $ cat >> .hg/hgrc <<EOF
189 > [extensions]
189 > [extensions]
190 > bulkwrite = $TESTTMP/bulkwrite.py
190 > bulkwrite = $TESTTMP/bulkwrite.py
191 > EOF
191 > EOF
192
192
193 $ chg bulkwrite --pager=on --color no --config ui.formatted=True
193 $ chg bulkwrite --pager=on --color no --config ui.formatted=True
194 paged! 'going to write massive data\n'
194 paged! 'going to write massive data\n'
195 killed! (?)
195 killed! (?)
196 [255]
196 [255]
197
197
198 $ chg bulkwrite --pager=on --color no --config ui.formatted=True
198 $ chg bulkwrite --pager=on --color no --config ui.formatted=True
199 paged! 'going to write massive data\n'
199 paged! 'going to write massive data\n'
200 killed! (?)
200 killed! (?)
201 [255]
201 [255]
202
202
203 $ cd ..
203 $ cd ..
204
204
205 missing stdio
205 missing stdio
206 -------------
206 -------------
207
207
208 $ CHGDEBUG=1 chg version -q 0<&-
208 $ CHGDEBUG=1 chg version -q 0<&-
209 chg: debug: * stdio fds are missing (glob)
209 chg: debug: * stdio fds are missing (glob)
210 chg: debug: * execute original hg (glob)
210 chg: debug: * execute original hg (glob)
211 Mercurial Distributed SCM * (glob)
211 Mercurial Distributed SCM * (glob)
212
212
213 server lifecycle
213 server lifecycle
214 ----------------
214 ----------------
215
215
216 chg server should be restarted on code change, and old server will shut down
216 chg server should be restarted on code change, and old server will shut down
217 automatically. In this test, we use the following time parameters:
217 automatically. In this test, we use the following time parameters:
218
218
219 - "sleep 1" to make mtime different
219 - "sleep 1" to make mtime different
220 - "sleep 2" to notice mtime change (polling interval is 1 sec)
220 - "sleep 2" to notice mtime change (polling interval is 1 sec)
221
221
222 set up repository with an extension:
222 set up repository with an extension:
223
223
224 $ chg init extreload
224 $ chg init extreload
225 $ cd extreload
225 $ cd extreload
226 $ touch dummyext.py
226 $ touch dummyext.py
227 $ cat <<EOF >> .hg/hgrc
227 $ cat <<EOF >> .hg/hgrc
228 > [extensions]
228 > [extensions]
229 > dummyext = dummyext.py
229 > dummyext = dummyext.py
230 > EOF
230 > EOF
231
231
232 isolate socket directory for stable result:
232 isolate socket directory for stable result:
233
233
234 $ OLDCHGSOCKNAME=$CHGSOCKNAME
234 $ OLDCHGSOCKNAME=$CHGSOCKNAME
235 $ mkdir chgsock
235 $ mkdir chgsock
236 $ CHGSOCKNAME=`pwd`/chgsock/server
236 $ CHGSOCKNAME=`pwd`/chgsock/server
237
237
238 warm up server:
238 warm up server:
239
239
240 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
240 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
241 chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
241 chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
242
242
243 new server should be started if extension modified:
243 new server should be started if extension modified:
244
244
245 $ sleep 1
245 $ sleep 1
246 $ touch dummyext.py
246 $ touch dummyext.py
247 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
247 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
248 chg: debug: * instruction: unlink $TESTTMP/extreload/chgsock/server-* (glob)
248 chg: debug: * instruction: unlink $TESTTMP/extreload/chgsock/server-* (glob)
249 chg: debug: * instruction: reconnect (glob)
249 chg: debug: * instruction: reconnect (glob)
250 chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
250 chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
251
251
252 old server will shut down, while new server should still be reachable:
252 old server will shut down, while new server should still be reachable:
253
253
254 $ sleep 2
254 $ sleep 2
255 $ CHGDEBUG= chg log 2>&1 | (egrep 'instruction|start' || true)
255 $ CHGDEBUG= chg log 2>&1 | (egrep 'instruction|start' || true)
256
256
257 socket file should never be unlinked by old server:
257 socket file should never be unlinked by old server:
258 (simulates unowned socket by updating mtime, which makes sure server exits
258 (simulates unowned socket by updating mtime, which makes sure server exits
259 at polling cycle)
259 at polling cycle)
260
260
261 $ ls chgsock/server-*
261 $ ls chgsock/server-*
262 chgsock/server-* (glob)
262 chgsock/server-* (glob)
263 $ touch chgsock/server-*
263 $ touch chgsock/server-*
264 $ sleep 2
264 $ sleep 2
265 $ ls chgsock/server-*
265 $ ls chgsock/server-*
266 chgsock/server-* (glob)
266 chgsock/server-* (glob)
267
267
268 since no server is reachable from socket file, new server should be started:
268 since no server is reachable from socket file, new server should be started:
269 (this test makes sure that old server shut down automatically)
269 (this test makes sure that old server shut down automatically)
270
270
271 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
271 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
272 chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
272 chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
273
273
274 shut down servers and restore environment:
274 shut down servers and restore environment:
275
275
276 $ rm -R chgsock
276 $ rm -R chgsock
277 $ sleep 2
277 $ sleep 2
278 $ CHGSOCKNAME=$OLDCHGSOCKNAME
278 $ CHGSOCKNAME=$OLDCHGSOCKNAME
279 $ cd ..
279 $ cd ..
280
280
281 check that server events are recorded:
281 check that server events are recorded:
282
282
283 $ ls log
283 $ ls log
284 server.log
284 server.log
285 server.log.1
285 server.log.1
286
286
287 print only the last 10 lines, since we aren't sure how many records are
287 print only the last 10 lines, since we aren't sure how many records are
288 preserved (since setprocname isn't available on py3 and pure version,
288 preserved (since setprocname isn't available on py3 and pure version,
289 the 10th-most-recent line is different when using py3):
289 the 10th-most-recent line is different when using py3):
290
290
291 $ cat log/server.log.1 log/server.log | tail -10 | filterlog
291 $ cat log/server.log.1 log/server.log | tail -10 | filterlog
292 YYYY/MM/DD HH:MM:SS (PID)> confighash = ... mtimehash = ... (no-setprocname !)
292 YYYY/MM/DD HH:MM:SS (PID)> confighash = ... mtimehash = ... (no-setprocname !)
293 YYYY/MM/DD HH:MM:SS (PID)> forked worker process (pid=...)
293 YYYY/MM/DD HH:MM:SS (PID)> forked worker process (pid=...)
294 YYYY/MM/DD HH:MM:SS (PID)> setprocname: ... (setprocname !)
294 YYYY/MM/DD HH:MM:SS (PID)> setprocname: ... (setprocname !)
295 YYYY/MM/DD HH:MM:SS (PID)> received fds: ...
295 YYYY/MM/DD HH:MM:SS (PID)> received fds: ...
296 YYYY/MM/DD HH:MM:SS (PID)> chdir to '$TESTTMP/extreload'
296 YYYY/MM/DD HH:MM:SS (PID)> chdir to '$TESTTMP/extreload'
297 YYYY/MM/DD HH:MM:SS (PID)> setumask 18
297 YYYY/MM/DD HH:MM:SS (PID)> setumask 18
298 YYYY/MM/DD HH:MM:SS (PID)> setenv: ...
298 YYYY/MM/DD HH:MM:SS (PID)> setenv: ...
299 YYYY/MM/DD HH:MM:SS (PID)> confighash = ... mtimehash = ...
299 YYYY/MM/DD HH:MM:SS (PID)> confighash = ... mtimehash = ...
300 YYYY/MM/DD HH:MM:SS (PID)> validate: []
300 YYYY/MM/DD HH:MM:SS (PID)> validate: []
301 YYYY/MM/DD HH:MM:SS (PID)> worker process exited (pid=...)
301 YYYY/MM/DD HH:MM:SS (PID)> worker process exited (pid=...)
302 YYYY/MM/DD HH:MM:SS (PID)> $TESTTMP/extreload/chgsock/server-... is not owned, exiting.
302 YYYY/MM/DD HH:MM:SS (PID)> $TESTTMP/extreload/chgsock/server-... is not owned, exiting.
303
303
304 global data mutated by schems
304 global data mutated by schems
305 -----------------------------
305 -----------------------------
306
306
307 $ hg init schemes
307 $ hg init schemes
308 $ cd schemes
308 $ cd schemes
309
309
310 initial state
310 initial state
311
311
312 $ cat > .hg/hgrc <<'EOF'
312 $ cat > .hg/hgrc <<'EOF'
313 > [extensions]
313 > [extensions]
314 > schemes =
314 > schemes =
315 > [schemes]
315 > [schemes]
316 > foo = https://foo.example.org/
316 > foo = https://foo.example.org/
317 > EOF
317 > EOF
318 $ hg debugexpandscheme foo://expanded
318 $ hg debugexpandscheme foo://expanded
319 https://foo.example.org/expanded
319 https://foo.example.org/expanded
320 $ hg debugexpandscheme bar://unexpanded
320 $ hg debugexpandscheme bar://unexpanded
321 bar://unexpanded
321 bar://unexpanded
322
322
323 add bar
323 add bar
324
324
325 $ cat > .hg/hgrc <<'EOF'
325 $ cat > .hg/hgrc <<'EOF'
326 > [extensions]
326 > [extensions]
327 > schemes =
327 > schemes =
328 > [schemes]
328 > [schemes]
329 > foo = https://foo.example.org/
329 > foo = https://foo.example.org/
330 > bar = https://bar.example.org/
330 > bar = https://bar.example.org/
331 > EOF
331 > EOF
332 $ hg debugexpandscheme foo://expanded
332 $ hg debugexpandscheme foo://expanded
333 https://foo.example.org/expanded
333 https://foo.example.org/expanded
334 $ hg debugexpandscheme bar://expanded
334 $ hg debugexpandscheme bar://expanded
335 https://bar.example.org/expanded
335 https://bar.example.org/expanded
336
336
337 remove foo
337 remove foo
338
338
339 $ cat > .hg/hgrc <<'EOF'
339 $ cat > .hg/hgrc <<'EOF'
340 > [extensions]
340 > [extensions]
341 > schemes =
341 > schemes =
342 > [schemes]
342 > [schemes]
343 > bar = https://bar.example.org/
343 > bar = https://bar.example.org/
344 > EOF
344 > EOF
345 $ hg debugexpandscheme foo://unexpanded
345 $ hg debugexpandscheme foo://unexpanded
346 foo://unexpanded
346 foo://unexpanded
347 $ hg debugexpandscheme bar://expanded
347 $ hg debugexpandscheme bar://expanded
348 https://bar.example.org/expanded
348 https://bar.example.org/expanded
349
349
350 $ cd ..
350 $ cd ..
351
351
352 repository cache
352 repository cache
353 ----------------
353 ----------------
354
354
355 $ rm log/server.log*
356 $ cp $HGRCPATH.unconfigured $HGRCPATH
355 $ cp $HGRCPATH.unconfigured $HGRCPATH
357 $ cat <<'EOF' >> $HGRCPATH
356 $ cat <<'EOF' >> $HGRCPATH
358 > [cmdserver]
357 > [cmdserver]
359 > log = $TESTTMP/log/server.log
358 > log = $TESTTMP/log/server-cached.log
360 > max-repo-cache = 1
359 > max-repo-cache = 1
361 > track-log = command, repocache
360 > track-log = command, repocache
362 > EOF
361 > EOF
363
362
364 isolate socket directory for stable result:
363 isolate socket directory for stable result:
365
364
366 $ OLDCHGSOCKNAME=$CHGSOCKNAME
365 $ OLDCHGSOCKNAME=$CHGSOCKNAME
367 $ mkdir chgsock
366 $ mkdir chgsock
368 $ CHGSOCKNAME=`pwd`/chgsock/server
367 $ CHGSOCKNAME=`pwd`/chgsock/server
369
368
370 create empty repo and cache it:
369 create empty repo and cache it:
371
370
372 $ hg init cached
371 $ hg init cached
373 $ hg id -R cached
372 $ hg id -R cached
374 000000000000 tip
373 000000000000 tip
375 $ sleep 1
374 $ sleep 1
376
375
377 modify repo (and cache will be invalidated):
376 modify repo (and cache will be invalidated):
378
377
379 $ touch cached/a
378 $ touch cached/a
380 $ hg ci -R cached -Am 'add a'
379 $ hg ci -R cached -Am 'add a'
381 adding a
380 adding a
382 $ sleep 1
381 $ sleep 1
383
382
384 read cached repo:
383 read cached repo:
385
384
386 $ hg log -R cached
385 $ hg log -R cached
387 changeset: 0:ac82d8b1f7c4
386 changeset: 0:ac82d8b1f7c4
388 tag: tip
387 tag: tip
389 user: test
388 user: test
390 date: Thu Jan 01 00:00:00 1970 +0000
389 date: Thu Jan 01 00:00:00 1970 +0000
391 summary: add a
390 summary: add a
392
391
393 $ sleep 1
392 $ sleep 1
394
393
395 discard cached from LRU cache:
394 discard cached from LRU cache:
396
395
397 $ hg clone cached cached2
396 $ hg clone cached cached2
398 updating to branch default
397 updating to branch default
399 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
398 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
400 $ hg id -R cached2
399 $ hg id -R cached2
401 ac82d8b1f7c4 tip
400 ac82d8b1f7c4 tip
402 $ sleep 1
401 $ sleep 1
403
402
404 read uncached repo:
403 read uncached repo:
405
404
406 $ hg log -R cached
405 $ hg log -R cached
407 changeset: 0:ac82d8b1f7c4
406 changeset: 0:ac82d8b1f7c4
408 tag: tip
407 tag: tip
409 user: test
408 user: test
410 date: Thu Jan 01 00:00:00 1970 +0000
409 date: Thu Jan 01 00:00:00 1970 +0000
411 summary: add a
410 summary: add a
412
411
413 $ sleep 1
412 $ sleep 1
414
413
415 shut down servers and restore environment:
414 shut down servers and restore environment:
416
415
417 $ rm -R chgsock
416 $ rm -R chgsock
418 $ sleep 2
417 $ sleep 2
419 $ CHGSOCKNAME=$OLDCHGSOCKNAME
418 $ CHGSOCKNAME=$OLDCHGSOCKNAME
420
419
421 check server log:
420 check server log:
422
421
423 $ cat log/server.log | filterlog
422 $ cat log/server-cached.log | filterlog
424 YYYY/MM/DD HH:MM:SS (PID)> worker process exited (pid=...)
425 YYYY/MM/DD HH:MM:SS (PID)> worker process exited (pid=...) (?)
426 YYYY/MM/DD HH:MM:SS (PID)> init cached
423 YYYY/MM/DD HH:MM:SS (PID)> init cached
427 YYYY/MM/DD HH:MM:SS (PID)> id -R cached
424 YYYY/MM/DD HH:MM:SS (PID)> id -R cached
428 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
425 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
429 YYYY/MM/DD HH:MM:SS (PID)> repo from cache: $TESTTMP/cached
426 YYYY/MM/DD HH:MM:SS (PID)> repo from cache: $TESTTMP/cached
430 YYYY/MM/DD HH:MM:SS (PID)> ci -R cached -Am 'add a'
427 YYYY/MM/DD HH:MM:SS (PID)> ci -R cached -Am 'add a'
431 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
428 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
432 YYYY/MM/DD HH:MM:SS (PID)> repo from cache: $TESTTMP/cached
429 YYYY/MM/DD HH:MM:SS (PID)> repo from cache: $TESTTMP/cached
433 YYYY/MM/DD HH:MM:SS (PID)> log -R cached
430 YYYY/MM/DD HH:MM:SS (PID)> log -R cached
434 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
431 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
435 YYYY/MM/DD HH:MM:SS (PID)> clone cached cached2
432 YYYY/MM/DD HH:MM:SS (PID)> clone cached cached2
436 YYYY/MM/DD HH:MM:SS (PID)> id -R cached2
433 YYYY/MM/DD HH:MM:SS (PID)> id -R cached2
437 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached2 (in ...s)
434 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached2 (in ...s)
438 YYYY/MM/DD HH:MM:SS (PID)> log -R cached
435 YYYY/MM/DD HH:MM:SS (PID)> log -R cached
439 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
436 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
440
437
441 Test that -R is interpreted relative to --cwd.
438 Test that -R is interpreted relative to --cwd.
442
439
443 $ hg init repo1
440 $ hg init repo1
444 $ mkdir -p a/b
441 $ mkdir -p a/b
445 $ hg init a/b/repo2
442 $ hg init a/b/repo2
446 $ printf "[alias]\ntest=repo1\n" >> repo1/.hg/hgrc
443 $ printf "[alias]\ntest=repo1\n" >> repo1/.hg/hgrc
447 $ printf "[alias]\ntest=repo2\n" >> a/b/repo2/.hg/hgrc
444 $ printf "[alias]\ntest=repo2\n" >> a/b/repo2/.hg/hgrc
448 $ cd a
445 $ cd a
449 $ chg --cwd .. -R repo1 show alias.test
446 $ chg --cwd .. -R repo1 show alias.test
450 repo1
447 repo1
451 $ chg --cwd . -R b/repo2 show alias.test
448 $ chg --cwd . -R b/repo2 show alias.test
452 repo2
449 repo2
453 $ cd ..
450 $ cd ..
454
451
455 Test that chg works (sets to the user's actual LC_CTYPE) even when python
452 Test that chg works (sets to the user's actual LC_CTYPE) even when python
456 "coerces" the locale (py3.7+)
453 "coerces" the locale (py3.7+)
457
454
458 $ cat > $TESTTMP/debugenv.py <<EOF
455 $ cat > $TESTTMP/debugenv.py <<EOF
459 > from mercurial import encoding
456 > from mercurial import encoding
460 > from mercurial import registrar
457 > from mercurial import registrar
461 > cmdtable = {}
458 > cmdtable = {}
462 > command = registrar.command(cmdtable)
459 > command = registrar.command(cmdtable)
463 > @command(b'debugenv', [], b'', norepo=True)
460 > @command(b'debugenv', [], b'', norepo=True)
464 > def debugenv(ui):
461 > def debugenv(ui):
465 > for k in [b'LC_ALL', b'LC_CTYPE', b'LANG']:
462 > for k in [b'LC_ALL', b'LC_CTYPE', b'LANG']:
466 > v = encoding.environ.get(k)
463 > v = encoding.environ.get(k)
467 > if v is not None:
464 > if v is not None:
468 > ui.write(b'%s=%s\n' % (k, encoding.environ[k]))
465 > ui.write(b'%s=%s\n' % (k, encoding.environ[k]))
469 > EOF
466 > EOF
470 (hg keeps python's modified LC_CTYPE, chg doesn't)
467 (hg keeps python's modified LC_CTYPE, chg doesn't)
471 $ (unset LC_ALL; unset LANG; LC_CTYPE= "$CHGHG" \
468 $ (unset LC_ALL; unset LANG; LC_CTYPE= "$CHGHG" \
472 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
469 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
473 LC_CTYPE=C.UTF-8 (py37 !)
470 LC_CTYPE=C.UTF-8 (py37 !)
474 LC_CTYPE= (no-py37 !)
471 LC_CTYPE= (no-py37 !)
475 $ (unset LC_ALL; unset LANG; LC_CTYPE= chg \
472 $ (unset LC_ALL; unset LANG; LC_CTYPE= chg \
476 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
473 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
477 LC_CTYPE=
474 LC_CTYPE=
478 $ (unset LC_ALL; unset LANG; LC_CTYPE=unsupported_value chg \
475 $ (unset LC_ALL; unset LANG; LC_CTYPE=unsupported_value chg \
479 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
476 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
480 *cannot change locale* (glob) (?)
477 *cannot change locale* (glob) (?)
481 LC_CTYPE=unsupported_value
478 LC_CTYPE=unsupported_value
482 $ (unset LC_ALL; unset LANG; LC_CTYPE= chg \
479 $ (unset LC_ALL; unset LANG; LC_CTYPE= chg \
483 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
480 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
484 LC_CTYPE=
481 LC_CTYPE=
485 $ LANG= LC_ALL= LC_CTYPE= chg \
482 $ LANG= LC_ALL= LC_CTYPE= chg \
486 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv
483 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv
487 LC_ALL=
484 LC_ALL=
488 LC_CTYPE=
485 LC_CTYPE=
489 LANG=
486 LANG=
490
487
491 Profiling isn't permanently enabled or carried over between chg invocations that
488 Profiling isn't permanently enabled or carried over between chg invocations that
492 share the same server
489 share the same server
493 $ cp $HGRCPATH.orig $HGRCPATH
490 $ cp $HGRCPATH.orig $HGRCPATH
494 $ hg init $TESTTMP/profiling
491 $ hg init $TESTTMP/profiling
495 $ cd $TESTTMP/profiling
492 $ cd $TESTTMP/profiling
496 $ filteredchg() {
493 $ filteredchg() {
497 > CHGDEBUG=1 chg "$@" 2>&1 | sed -rn 's_^No samples recorded.*$_Sample count: 0_; /Sample count/p; /start cmdserver/p'
494 > CHGDEBUG=1 chg "$@" 2>&1 | sed -rn 's_^No samples recorded.*$_Sample count: 0_; /Sample count/p; /start cmdserver/p'
498 > }
495 > }
499 $ newchg() {
496 $ newchg() {
500 > chg --kill-chg-daemon
497 > chg --kill-chg-daemon
501 > filteredchg "$@" | egrep -v 'start cmdserver' || true
498 > filteredchg "$@" | egrep -v 'start cmdserver' || true
502 > }
499 > }
503 (--profile isn't permanently on just because it was specified when chg was
500 (--profile isn't permanently on just because it was specified when chg was
504 started)
501 started)
505 $ newchg log -r . --profile
502 $ newchg log -r . --profile
506 Sample count: * (glob)
503 Sample count: * (glob)
507 $ filteredchg log -r .
504 $ filteredchg log -r .
508 (enabling profiling via config works, even on the first chg command that starts
505 (enabling profiling via config works, even on the first chg command that starts
509 a cmdserver)
506 a cmdserver)
510 $ cat >> $HGRCPATH <<EOF
507 $ cat >> $HGRCPATH <<EOF
511 > [profiling]
508 > [profiling]
512 > type=stat
509 > type=stat
513 > enabled=1
510 > enabled=1
514 > EOF
511 > EOF
515 $ newchg log -r .
512 $ newchg log -r .
516 Sample count: * (glob)
513 Sample count: * (glob)
517 $ filteredchg log -r .
514 $ filteredchg log -r .
518 Sample count: * (glob)
515 Sample count: * (glob)
519 (test that we aren't accumulating more and more samples each run)
516 (test that we aren't accumulating more and more samples each run)
520 $ cat > $TESTTMP/debugsleep.py <<EOF
517 $ cat > $TESTTMP/debugsleep.py <<EOF
521 > import time
518 > import time
522 > from mercurial import registrar
519 > from mercurial import registrar
523 > cmdtable = {}
520 > cmdtable = {}
524 > command = registrar.command(cmdtable)
521 > command = registrar.command(cmdtable)
525 > @command(b'debugsleep', [], b'', norepo=True)
522 > @command(b'debugsleep', [], b'', norepo=True)
526 > def debugsleep(ui):
523 > def debugsleep(ui):
527 > start = time.time()
524 > start = time.time()
528 > x = 0
525 > x = 0
529 > while time.time() < start + 0.5:
526 > while time.time() < start + 0.5:
530 > time.sleep(.1)
527 > time.sleep(.1)
531 > x += 1
528 > x += 1
532 > ui.status(b'%d debugsleep iterations in %.03fs\n' % (x, time.time() - start))
529 > ui.status(b'%d debugsleep iterations in %.03fs\n' % (x, time.time() - start))
533 > EOF
530 > EOF
534 $ cat >> $HGRCPATH <<EOF
531 $ cat >> $HGRCPATH <<EOF
535 > [extensions]
532 > [extensions]
536 > debugsleep = $TESTTMP/debugsleep.py
533 > debugsleep = $TESTTMP/debugsleep.py
537 > EOF
534 > EOF
538 $ newchg debugsleep > run_1
535 $ newchg debugsleep > run_1
539 $ filteredchg debugsleep > run_2
536 $ filteredchg debugsleep > run_2
540 $ filteredchg debugsleep > run_3
537 $ filteredchg debugsleep > run_3
541 $ filteredchg debugsleep > run_4
538 $ filteredchg debugsleep > run_4
542 FIXME: Run 4 should not be >3x Run 1's number of samples.
539 FIXME: Run 4 should not be >3x Run 1's number of samples.
543 $ "$PYTHON" <<EOF
540 $ "$PYTHON" <<EOF
544 > r1 = int(open("run_1", "r").read().split()[-1])
541 > r1 = int(open("run_1", "r").read().split()[-1])
545 > r4 = int(open("run_4", "r").read().split()[-1])
542 > r4 = int(open("run_4", "r").read().split()[-1])
546 > print("Run 1: %d samples\nRun 4: %d samples\nRun 4 > 3 * Run 1: %s" %
543 > print("Run 1: %d samples\nRun 4: %d samples\nRun 4 > 3 * Run 1: %s" %
547 > (r1, r4, r4 > (r1 * 3)))
544 > (r1, r4, r4 > (r1 * 3)))
548 > EOF
545 > EOF
549 Run 1: * samples (glob)
546 Run 1: * samples (glob)
550 Run 4: * samples (glob)
547 Run 4: * samples (glob)
551 Run 4 > 3 * Run 1: False
548 Run 4 > 3 * Run 1: False
552 (Disabling with --no-profile on the commandline still works, but isn't permanent)
549 (Disabling with --no-profile on the commandline still works, but isn't permanent)
553 $ newchg log -r . --no-profile
550 $ newchg log -r . --no-profile
554 $ filteredchg log -r .
551 $ filteredchg log -r .
555 Sample count: * (glob)
552 Sample count: * (glob)
556 $ filteredchg log -r . --no-profile
553 $ filteredchg log -r . --no-profile
557 $ filteredchg log -r .
554 $ filteredchg log -r .
558 Sample count: * (glob)
555 Sample count: * (glob)
General Comments 0
You need to be logged in to leave comments. Login now