##// END OF EJS Templates
merge with stable
Sean Farley -
r33867:af20468e merge default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,316 +1,316 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 PYTHON=python
8 PYTHON=python
9 $(eval HGROOT := $(shell pwd))
9 $(eval HGROOT := $(shell pwd))
10 HGPYTHONS ?= $(HGROOT)/build/pythons
10 HGPYTHONS ?= $(HGROOT)/build/pythons
11 PURE=
11 PURE=
12 PYFILES:=$(shell find mercurial hgext doc -name '*.py')
12 PYFILES:=$(shell find mercurial hgext doc -name '*.py')
13 DOCFILES=mercurial/help/*.txt
13 DOCFILES=mercurial/help/*.txt
14 export LANGUAGE=C
14 export LANGUAGE=C
15 export LC_ALL=C
15 export LC_ALL=C
16 TESTFLAGS ?= $(shell echo $$HGTESTFLAGS)
16 TESTFLAGS ?= $(shell echo $$HGTESTFLAGS)
17 OSXVERSIONFLAGS ?= $(shell echo $$OSXVERSIONFLAGS)
17 OSXVERSIONFLAGS ?= $(shell echo $$OSXVERSIONFLAGS)
18
18
19 # Set this to e.g. "mingw32" to use a non-default compiler.
19 # Set this to e.g. "mingw32" to use a non-default compiler.
20 COMPILER=
20 COMPILER=
21
21
22 COMPILERFLAG_tmp_ =
22 COMPILERFLAG_tmp_ =
23 COMPILERFLAG_tmp_${COMPILER} ?= -c $(COMPILER)
23 COMPILERFLAG_tmp_${COMPILER} ?= -c $(COMPILER)
24 COMPILERFLAG=${COMPILERFLAG_tmp_${COMPILER}}
24 COMPILERFLAG=${COMPILERFLAG_tmp_${COMPILER}}
25
25
26 help:
26 help:
27 @echo 'Commonly used make targets:'
27 @echo 'Commonly used make targets:'
28 @echo ' all - build program and documentation'
28 @echo ' all - build program and documentation'
29 @echo ' install - install program and man pages to $$PREFIX ($(PREFIX))'
29 @echo ' install - install program and man pages to $$PREFIX ($(PREFIX))'
30 @echo ' install-home - install with setup.py install --home=$$HOME ($(HOME))'
30 @echo ' install-home - install with setup.py install --home=$$HOME ($(HOME))'
31 @echo ' local - build for inplace usage'
31 @echo ' local - build for inplace usage'
32 @echo ' tests - run all tests in the automatic test suite'
32 @echo ' tests - run all tests in the automatic test suite'
33 @echo ' test-foo - run only specified tests (e.g. test-merge1.t)'
33 @echo ' test-foo - run only specified tests (e.g. test-merge1.t)'
34 @echo ' dist - run all tests and create a source tarball in dist/'
34 @echo ' dist - run all tests and create a source tarball in dist/'
35 @echo ' clean - remove files created by other targets'
35 @echo ' clean - remove files created by other targets'
36 @echo ' (except installed files or dist source tarball)'
36 @echo ' (except installed files or dist source tarball)'
37 @echo ' update-pot - update i18n/hg.pot'
37 @echo ' update-pot - update i18n/hg.pot'
38 @echo
38 @echo
39 @echo 'Example for a system-wide installation under /usr/local:'
39 @echo 'Example for a system-wide installation under /usr/local:'
40 @echo ' make all && su -c "make install" && hg version'
40 @echo ' make all && su -c "make install" && hg version'
41 @echo
41 @echo
42 @echo 'Example for a local installation (usable in this directory):'
42 @echo 'Example for a local installation (usable in this directory):'
43 @echo ' make local && ./hg version'
43 @echo ' make local && ./hg version'
44
44
45 all: build doc
45 all: build doc
46
46
47 local:
47 local:
48 $(PYTHON) setup.py $(PURE) \
48 $(PYTHON) setup.py $(PURE) \
49 build_py -c -d . \
49 build_py -c -d . \
50 build_ext $(COMPILERFLAG) -i \
50 build_ext $(COMPILERFLAG) -i \
51 build_hgexe $(COMPILERFLAG) -i \
51 build_hgexe $(COMPILERFLAG) -i \
52 build_mo
52 build_mo
53 env HGRCPATH= $(PYTHON) hg version
53 env HGRCPATH= $(PYTHON) hg version
54
54
55 build:
55 build:
56 $(PYTHON) setup.py $(PURE) build $(COMPILERFLAG)
56 $(PYTHON) setup.py $(PURE) build $(COMPILERFLAG)
57
57
58 wheel:
58 wheel:
59 FORCE_SETUPTOOLS=1 $(PYTHON) setup.py $(PURE) bdist_wheel $(COMPILERFLAG)
59 FORCE_SETUPTOOLS=1 $(PYTHON) setup.py $(PURE) bdist_wheel $(COMPILERFLAG)
60
60
61 doc:
61 doc:
62 $(MAKE) -C doc
62 $(MAKE) -C doc
63
63
64 cleanbutpackages:
64 cleanbutpackages:
65 -$(PYTHON) setup.py clean --all # ignore errors from this command
65 -$(PYTHON) setup.py clean --all # ignore errors from this command
66 find contrib doc hgext hgext3rd i18n mercurial tests hgdemandimport \
66 find contrib doc hgext hgext3rd i18n mercurial tests hgdemandimport \
67 \( -name '*.py[cdo]' -o -name '*.so' \) -exec rm -f '{}' ';'
67 \( -name '*.py[cdo]' -o -name '*.so' \) -exec rm -f '{}' ';'
68 rm -f MANIFEST MANIFEST.in hgext/__index__.py tests/*.err
68 rm -f MANIFEST MANIFEST.in hgext/__index__.py tests/*.err
69 rm -f mercurial/__modulepolicy__.py
69 rm -f mercurial/__modulepolicy__.py
70 if test -d .hg; then rm -f mercurial/__version__.py; fi
70 if test -d .hg; then rm -f mercurial/__version__.py; fi
71 rm -rf build mercurial/locale
71 rm -rf build mercurial/locale
72 $(MAKE) -C doc clean
72 $(MAKE) -C doc clean
73 $(MAKE) -C contrib/chg distclean
73 $(MAKE) -C contrib/chg distclean
74
74
75 clean: cleanbutpackages
75 clean: cleanbutpackages
76 rm -rf packages
76 rm -rf packages
77
77
78 install: install-bin install-doc
78 install: install-bin install-doc
79
79
80 install-bin: build
80 install-bin: build
81 $(PYTHON) setup.py $(PURE) install --root="$(DESTDIR)/" --prefix="$(PREFIX)" --force
81 $(PYTHON) setup.py $(PURE) install --root="$(DESTDIR)/" --prefix="$(PREFIX)" --force
82
82
83 install-doc: doc
83 install-doc: doc
84 cd doc && $(MAKE) $(MFLAGS) install
84 cd doc && $(MAKE) $(MFLAGS) install
85
85
86 install-home: install-home-bin install-home-doc
86 install-home: install-home-bin install-home-doc
87
87
88 install-home-bin: build
88 install-home-bin: build
89 $(PYTHON) setup.py $(PURE) install --home="$(HOME)" --prefix="" --force
89 $(PYTHON) setup.py $(PURE) install --home="$(HOME)" --prefix="" --force
90
90
91 install-home-doc: doc
91 install-home-doc: doc
92 cd doc && $(MAKE) $(MFLAGS) PREFIX="$(HOME)" install
92 cd doc && $(MAKE) $(MFLAGS) PREFIX="$(HOME)" install
93
93
94 MANIFEST-doc:
94 MANIFEST-doc:
95 $(MAKE) -C doc MANIFEST
95 $(MAKE) -C doc MANIFEST
96
96
97 MANIFEST.in: MANIFEST-doc
97 MANIFEST.in: MANIFEST-doc
98 hg manifest | sed -e 's/^/include /' > MANIFEST.in
98 hg manifest | sed -e 's/^/include /' > MANIFEST.in
99 echo include mercurial/__version__.py >> MANIFEST.in
99 echo include mercurial/__version__.py >> MANIFEST.in
100 sed -e 's/^/include /' < doc/MANIFEST >> MANIFEST.in
100 sed -e 's/^/include /' < doc/MANIFEST >> MANIFEST.in
101
101
102 dist: tests dist-notests
102 dist: tests dist-notests
103
103
104 dist-notests: doc MANIFEST.in
104 dist-notests: doc MANIFEST.in
105 TAR_OPTIONS="--owner=root --group=root --mode=u+w,go-w,a+rX-s" $(PYTHON) setup.py -q sdist
105 TAR_OPTIONS="--owner=root --group=root --mode=u+w,go-w,a+rX-s" $(PYTHON) setup.py -q sdist
106
106
107 check: tests
107 check: tests
108
108
109 tests:
109 tests:
110 cd tests && $(PYTHON) run-tests.py $(TESTFLAGS)
110 cd tests && $(PYTHON) run-tests.py $(TESTFLAGS)
111
111
112 test-%:
112 test-%:
113 cd tests && $(PYTHON) run-tests.py $(TESTFLAGS) $@
113 cd tests && $(PYTHON) run-tests.py $(TESTFLAGS) $@
114
114
115 testpy-%:
115 testpy-%:
116 @echo Looking for Python $* in $(HGPYTHONS)
116 @echo Looking for Python $* in $(HGPYTHONS)
117 [ -e $(HGPYTHONS)/$*/bin/python ] || ( \
117 [ -e $(HGPYTHONS)/$*/bin/python ] || ( \
118 cd $$(mktemp --directory --tmpdir) && \
118 cd $$(mktemp --directory --tmpdir) && \
119 $(MAKE) -f $(HGROOT)/contrib/Makefile.python PYTHONVER=$* PREFIX=$(HGPYTHONS)/$* python )
119 $(MAKE) -f $(HGROOT)/contrib/Makefile.python PYTHONVER=$* PREFIX=$(HGPYTHONS)/$* python )
120 cd tests && $(HGPYTHONS)/$*/bin/python run-tests.py $(TESTFLAGS)
120 cd tests && $(HGPYTHONS)/$*/bin/python run-tests.py $(TESTFLAGS)
121
121
122 check-code:
122 check-code:
123 hg manifest | xargs python contrib/check-code.py
123 hg manifest | xargs python contrib/check-code.py
124
124
125 update-pot: i18n/hg.pot
125 update-pot: i18n/hg.pot
126
126
127 i18n/hg.pot: $(PYFILES) $(DOCFILES) i18n/posplit i18n/hggettext
127 i18n/hg.pot: $(PYFILES) $(DOCFILES) i18n/posplit i18n/hggettext
128 $(PYTHON) i18n/hggettext mercurial/commands.py \
128 $(PYTHON) i18n/hggettext mercurial/commands.py \
129 hgext/*.py hgext/*/__init__.py \
129 hgext/*.py hgext/*/__init__.py \
130 mercurial/fileset.py mercurial/revset.py \
130 mercurial/fileset.py mercurial/revset.py \
131 mercurial/templatefilters.py mercurial/templatekw.py \
131 mercurial/templatefilters.py mercurial/templatekw.py \
132 mercurial/templater.py \
132 mercurial/templater.py \
133 mercurial/filemerge.py \
133 mercurial/filemerge.py \
134 mercurial/hgweb/webcommands.py \
134 mercurial/hgweb/webcommands.py \
135 mercurial/util.py \
135 mercurial/util.py \
136 $(DOCFILES) > i18n/hg.pot.tmp
136 $(DOCFILES) > i18n/hg.pot.tmp
137 # All strings marked for translation in Mercurial contain
137 # All strings marked for translation in Mercurial contain
138 # ASCII characters only. But some files contain string
138 # ASCII characters only. But some files contain string
139 # literals like this '\037\213'. xgettext thinks it has to
139 # literals like this '\037\213'. xgettext thinks it has to
140 # parse them even though they are not marked for translation.
140 # parse them even though they are not marked for translation.
141 # Extracting with an explicit encoding of ISO-8859-1 will make
141 # Extracting with an explicit encoding of ISO-8859-1 will make
142 # xgettext "parse" and ignore them.
142 # xgettext "parse" and ignore them.
143 echo $(PYFILES) | xargs \
143 echo $(PYFILES) | xargs \
144 xgettext --package-name "Mercurial" \
144 xgettext --package-name "Mercurial" \
145 --msgid-bugs-address "<mercurial-devel@mercurial-scm.org>" \
145 --msgid-bugs-address "<mercurial-devel@mercurial-scm.org>" \
146 --copyright-holder "Matt Mackall <mpm@selenic.com> and others" \
146 --copyright-holder "Matt Mackall <mpm@selenic.com> and others" \
147 --from-code ISO-8859-1 --join --sort-by-file --add-comments=i18n: \
147 --from-code ISO-8859-1 --join --sort-by-file --add-comments=i18n: \
148 -d hg -p i18n -o hg.pot.tmp
148 -d hg -p i18n -o hg.pot.tmp
149 $(PYTHON) i18n/posplit i18n/hg.pot.tmp
149 $(PYTHON) i18n/posplit i18n/hg.pot.tmp
150 # The target file is not created before the last step. So it never is in
150 # The target file is not created before the last step. So it never is in
151 # an intermediate state.
151 # an intermediate state.
152 mv -f i18n/hg.pot.tmp i18n/hg.pot
152 mv -f i18n/hg.pot.tmp i18n/hg.pot
153
153
154 %.po: i18n/hg.pot
154 %.po: i18n/hg.pot
155 # work on a temporary copy for never having a half completed target
155 # work on a temporary copy for never having a half completed target
156 cp $@ $@.tmp
156 cp $@ $@.tmp
157 msgmerge --no-location --update $@.tmp $^
157 msgmerge --no-location --update $@.tmp $^
158 mv -f $@.tmp $@
158 mv -f $@.tmp $@
159
159
160 # Packaging targets
160 # Packaging targets
161
161
162 osx:
162 osx:
163 rm -rf build/mercurial
163 rm -rf build/mercurial
164 /usr/bin/python2.7 setup.py install --optimize=1 \
164 /usr/bin/python2.7 setup.py install --optimize=1 \
165 --root=build/mercurial/ --prefix=/usr/local/ \
165 --root=build/mercurial/ --prefix=/usr/local/ \
166 --install-lib=/Library/Python/2.7/site-packages/
166 --install-lib=/Library/Python/2.7/site-packages/
167 make -C doc all install DESTDIR="$(PWD)/build/mercurial/"
167 make -C doc all install DESTDIR="$(PWD)/build/mercurial/"
168 # Place a bogon .DS_Store file in the target dir so we can be
168 # Place a bogon .DS_Store file in the target dir so we can be
169 # sure it doesn't get included in the final package.
169 # sure it doesn't get included in the final package.
170 touch build/mercurial/.DS_Store
170 touch build/mercurial/.DS_Store
171 # install zsh completions - this location appears to be
171 # install zsh completions - this location appears to be
172 # searched by default as of macOS Sierra.
172 # searched by default as of macOS Sierra.
173 install -d build/mercurial/usr/local/share/zsh/site-functions/
173 install -d build/mercurial/usr/local/share/zsh/site-functions/
174 install -m 0644 contrib/zsh_completion build/mercurial/usr/local/share/zsh/site-functions/_hg
174 install -m 0644 contrib/zsh_completion build/mercurial/usr/local/share/zsh/site-functions/_hg
175 # install bash completions - there doesn't appear to be a
175 # install bash completions - there doesn't appear to be a
176 # place that's searched by default for bash, so we'll follow
176 # place that's searched by default for bash, so we'll follow
177 # the lead of Apple's git install and just put it in a
177 # the lead of Apple's git install and just put it in a
178 # location of our own.
178 # location of our own.
179 install -d build/mercurial/usr/local/hg/contrib/
179 install -d build/mercurial/usr/local/hg/contrib/
180 install -m 0644 contrib/bash_completion build/mercurial/usr/local/hg/contrib/hg-completion.bash
180 install -m 0644 contrib/bash_completion build/mercurial/usr/local/hg/contrib/hg-completion.bash
181 make -C contrib/chg \
181 make -C contrib/chg \
182 HGPATH=/usr/local/bin/hg \
182 HGPATH=/usr/local/bin/hg \
183 PYTHON=/usr/bin/python2.7 \
183 PYTHON=/usr/bin/python2.7 \
184 HG=/usr/local/bin/hg \
184 HG=/usr/local/bin/hg \
185 HGEXTDIR=/Library/Python/2.7/site-packages/hgext \
185 HGEXTDIR=/Library/Python/2.7/site-packages/hgext \
186 DESTDIR=../../build/mercurial \
186 DESTDIR=../../build/mercurial \
187 PREFIX=/usr/local \
187 PREFIX=/usr/local \
188 clean install
188 clean install
189 mkdir -p $${OUTPUTDIR:-dist}
189 mkdir -p $${OUTPUTDIR:-dist}
190 HGVER=$$(shell python contrib/genosxversion.py $(OSXVERSIONFLAGS) build/mercurial/Library/Python/2.7/site-packages/mercurial/__version__.py ) && \
190 HGVER=$$(python contrib/genosxversion.py $(OSXVERSIONFLAGS) build/mercurial/Library/Python/2.7/site-packages/mercurial/__version__.py) && \
191 OSXVER=$$(sw_vers -productVersion | cut -d. -f1,2) && \
191 OSXVER=$$(sw_vers -productVersion | cut -d. -f1,2) && \
192 pkgbuild --filter \\.DS_Store --root build/mercurial/ \
192 pkgbuild --filter \\.DS_Store --root build/mercurial/ \
193 --identifier org.mercurial-scm.mercurial \
193 --identifier org.mercurial-scm.mercurial \
194 --version "$${HGVER}" \
194 --version "$${HGVER}" \
195 build/mercurial.pkg && \
195 build/mercurial.pkg && \
196 productbuild --distribution contrib/macosx/distribution.xml \
196 productbuild --distribution contrib/macosx/distribution.xml \
197 --package-path build/ \
197 --package-path build/ \
198 --version "$${HGVER}" \
198 --version "$${HGVER}" \
199 --resources contrib/macosx/ \
199 --resources contrib/macosx/ \
200 "$${OUTPUTDIR:-dist/}"/Mercurial-"$${HGVER}"-macosx"$${OSXVER}".pkg
200 "$${OUTPUTDIR:-dist/}"/Mercurial-"$${HGVER}"-macosx"$${OSXVER}".pkg
201
201
202 deb:
202 deb:
203 contrib/builddeb
203 contrib/builddeb
204
204
205 ppa:
205 ppa:
206 contrib/builddeb --source-only
206 contrib/builddeb --source-only
207
207
208 contrib/docker/debian-%: contrib/docker/debian.template
208 contrib/docker/debian-%: contrib/docker/debian.template
209 sed "s/__CODENAME__/$*/" $< > $@
209 sed "s/__CODENAME__/$*/" $< > $@
210
210
211 docker-debian-jessie: contrib/docker/debian-jessie
211 docker-debian-jessie: contrib/docker/debian-jessie
212 mkdir -p packages/debian-jessie
212 mkdir -p packages/debian-jessie
213 contrib/dockerdeb debian jessie
213 contrib/dockerdeb debian jessie
214
214
215 docker-debian-stretch: contrib/docker/debian-stretch
215 docker-debian-stretch: contrib/docker/debian-stretch
216 mkdir -p packages/debian-stretch
216 mkdir -p packages/debian-stretch
217 contrib/dockerdeb debian stretch
217 contrib/dockerdeb debian stretch
218
218
219 contrib/docker/ubuntu-%: contrib/docker/ubuntu.template
219 contrib/docker/ubuntu-%: contrib/docker/ubuntu.template
220 sed "s/__CODENAME__/$*/" $< > $@
220 sed "s/__CODENAME__/$*/" $< > $@
221
221
222 docker-ubuntu-trusty: contrib/docker/ubuntu-trusty
222 docker-ubuntu-trusty: contrib/docker/ubuntu-trusty
223 contrib/dockerdeb ubuntu trusty
223 contrib/dockerdeb ubuntu trusty
224
224
225 docker-ubuntu-trusty-ppa: contrib/docker/ubuntu-trusty
225 docker-ubuntu-trusty-ppa: contrib/docker/ubuntu-trusty
226 contrib/dockerdeb ubuntu trusty --source-only
226 contrib/dockerdeb ubuntu trusty --source-only
227
227
228 docker-ubuntu-xenial: contrib/docker/ubuntu-xenial
228 docker-ubuntu-xenial: contrib/docker/ubuntu-xenial
229 contrib/dockerdeb ubuntu xenial
229 contrib/dockerdeb ubuntu xenial
230
230
231 docker-ubuntu-xenial-ppa: contrib/docker/ubuntu-xenial
231 docker-ubuntu-xenial-ppa: contrib/docker/ubuntu-xenial
232 contrib/dockerdeb ubuntu xenial --source-only
232 contrib/dockerdeb ubuntu xenial --source-only
233
233
234 docker-ubuntu-yakkety: contrib/docker/ubuntu-yakkety
234 docker-ubuntu-yakkety: contrib/docker/ubuntu-yakkety
235 contrib/dockerdeb ubuntu yakkety
235 contrib/dockerdeb ubuntu yakkety
236
236
237 docker-ubuntu-yakkety-ppa: contrib/docker/ubuntu-yakkety
237 docker-ubuntu-yakkety-ppa: contrib/docker/ubuntu-yakkety
238 contrib/dockerdeb ubuntu yakkety --source-only
238 contrib/dockerdeb ubuntu yakkety --source-only
239
239
240 docker-ubuntu-zesty: contrib/docker/ubuntu-zesty
240 docker-ubuntu-zesty: contrib/docker/ubuntu-zesty
241 contrib/dockerdeb ubuntu zesty
241 contrib/dockerdeb ubuntu zesty
242
242
243 docker-ubuntu-zesty-ppa: contrib/docker/ubuntu-zesty
243 docker-ubuntu-zesty-ppa: contrib/docker/ubuntu-zesty
244 contrib/dockerdeb ubuntu zesty --source-only
244 contrib/dockerdeb ubuntu zesty --source-only
245
245
246 fedora20:
246 fedora20:
247 mkdir -p packages/fedora20
247 mkdir -p packages/fedora20
248 contrib/buildrpm
248 contrib/buildrpm
249 cp rpmbuild/RPMS/*/* packages/fedora20
249 cp rpmbuild/RPMS/*/* packages/fedora20
250 cp rpmbuild/SRPMS/* packages/fedora20
250 cp rpmbuild/SRPMS/* packages/fedora20
251 rm -rf rpmbuild
251 rm -rf rpmbuild
252
252
253 docker-fedora20:
253 docker-fedora20:
254 mkdir -p packages/fedora20
254 mkdir -p packages/fedora20
255 contrib/dockerrpm fedora20
255 contrib/dockerrpm fedora20
256
256
257 fedora21:
257 fedora21:
258 mkdir -p packages/fedora21
258 mkdir -p packages/fedora21
259 contrib/buildrpm
259 contrib/buildrpm
260 cp rpmbuild/RPMS/*/* packages/fedora21
260 cp rpmbuild/RPMS/*/* packages/fedora21
261 cp rpmbuild/SRPMS/* packages/fedora21
261 cp rpmbuild/SRPMS/* packages/fedora21
262 rm -rf rpmbuild
262 rm -rf rpmbuild
263
263
264 docker-fedora21:
264 docker-fedora21:
265 mkdir -p packages/fedora21
265 mkdir -p packages/fedora21
266 contrib/dockerrpm fedora21
266 contrib/dockerrpm fedora21
267
267
268 centos5:
268 centos5:
269 mkdir -p packages/centos5
269 mkdir -p packages/centos5
270 contrib/buildrpm --withpython
270 contrib/buildrpm --withpython
271 cp rpmbuild/RPMS/*/* packages/centos5
271 cp rpmbuild/RPMS/*/* packages/centos5
272 cp rpmbuild/SRPMS/* packages/centos5
272 cp rpmbuild/SRPMS/* packages/centos5
273
273
274 docker-centos5:
274 docker-centos5:
275 mkdir -p packages/centos5
275 mkdir -p packages/centos5
276 contrib/dockerrpm centos5 --withpython
276 contrib/dockerrpm centos5 --withpython
277
277
278 centos6:
278 centos6:
279 mkdir -p packages/centos6
279 mkdir -p packages/centos6
280 contrib/buildrpm --withpython
280 contrib/buildrpm --withpython
281 cp rpmbuild/RPMS/*/* packages/centos6
281 cp rpmbuild/RPMS/*/* packages/centos6
282 cp rpmbuild/SRPMS/* packages/centos6
282 cp rpmbuild/SRPMS/* packages/centos6
283
283
284 docker-centos6:
284 docker-centos6:
285 mkdir -p packages/centos6
285 mkdir -p packages/centos6
286 contrib/dockerrpm centos6 --withpython
286 contrib/dockerrpm centos6 --withpython
287
287
288 centos7:
288 centos7:
289 mkdir -p packages/centos7
289 mkdir -p packages/centos7
290 contrib/buildrpm
290 contrib/buildrpm
291 cp rpmbuild/RPMS/*/* packages/centos7
291 cp rpmbuild/RPMS/*/* packages/centos7
292 cp rpmbuild/SRPMS/* packages/centos7
292 cp rpmbuild/SRPMS/* packages/centos7
293
293
294 docker-centos7:
294 docker-centos7:
295 mkdir -p packages/centos7
295 mkdir -p packages/centos7
296 contrib/dockerrpm centos7
296 contrib/dockerrpm centos7
297
297
298 linux-wheels: linux-wheels-x86_64 linux-wheels-i686
298 linux-wheels: linux-wheels-x86_64 linux-wheels-i686
299
299
300 linux-wheels-x86_64:
300 linux-wheels-x86_64:
301 docker run -e "HGTEST_JOBS=$(shell nproc)" --rm -ti -v `pwd`:/src quay.io/pypa/manylinux1_x86_64 /src/contrib/build-linux-wheels.sh
301 docker run -e "HGTEST_JOBS=$(shell nproc)" --rm -ti -v `pwd`:/src quay.io/pypa/manylinux1_x86_64 /src/contrib/build-linux-wheels.sh
302
302
303 linux-wheels-i686:
303 linux-wheels-i686:
304 docker run -e "HGTEST_JOBS=$(shell nproc)" --rm -ti -v `pwd`:/src quay.io/pypa/manylinux1_i686 linux32 /src/contrib/build-linux-wheels.sh
304 docker run -e "HGTEST_JOBS=$(shell nproc)" --rm -ti -v `pwd`:/src quay.io/pypa/manylinux1_i686 linux32 /src/contrib/build-linux-wheels.sh
305
305
306 .PHONY: help all local build doc cleanbutpackages clean install install-bin \
306 .PHONY: help all local build doc cleanbutpackages clean install install-bin \
307 install-doc install-home install-home-bin install-home-doc \
307 install-doc install-home install-home-bin install-home-doc \
308 dist dist-notests check tests check-code update-pot \
308 dist dist-notests check tests check-code update-pot \
309 osx deb ppa docker-debian-jessie docker-debian-stretch \
309 osx deb ppa docker-debian-jessie docker-debian-stretch \
310 docker-ubuntu-trusty docker-ubuntu-trusty-ppa \
310 docker-ubuntu-trusty docker-ubuntu-trusty-ppa \
311 docker-ubuntu-xenial docker-ubuntu-xenial-ppa \
311 docker-ubuntu-xenial docker-ubuntu-xenial-ppa \
312 docker-ubuntu-yakkety docker-ubuntu-yakkety-ppa \
312 docker-ubuntu-yakkety docker-ubuntu-yakkety-ppa \
313 docker-ubuntu-zesty docker-ubuntu-zesty-ppa \
313 docker-ubuntu-zesty docker-ubuntu-zesty-ppa \
314 fedora20 docker-fedora20 fedora21 docker-fedora21 \
314 fedora20 docker-fedora20 fedora21 docker-fedora21 \
315 centos5 docker-centos5 centos6 docker-centos6 centos7 docker-centos7 \
315 centos5 docker-centos5 centos6 docker-centos6 centos7 docker-centos7 \
316 linux-wheels
316 linux-wheels
@@ -1,519 +1,520 b''
1 # branchmap.py - logic to computes, maintain and stores branchmap for local repo
1 # branchmap.py - logic to computes, maintain and stores branchmap for local repo
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@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 struct
10 import struct
11
11
12 from .node import (
12 from .node import (
13 bin,
13 bin,
14 hex,
14 hex,
15 nullid,
15 nullid,
16 nullrev,
16 nullrev,
17 )
17 )
18 from . import (
18 from . import (
19 encoding,
19 encoding,
20 error,
20 error,
21 scmutil,
21 scmutil,
22 util,
22 util,
23 )
23 )
24
24
25 calcsize = struct.calcsize
25 calcsize = struct.calcsize
26 pack_into = struct.pack_into
26 pack_into = struct.pack_into
27 unpack_from = struct.unpack_from
27 unpack_from = struct.unpack_from
28
28
29 def _filename(repo):
29 def _filename(repo):
30 """name of a branchcache file for a given repo or repoview"""
30 """name of a branchcache file for a given repo or repoview"""
31 filename = "branch2"
31 filename = "branch2"
32 if repo.filtername:
32 if repo.filtername:
33 filename = '%s-%s' % (filename, repo.filtername)
33 filename = '%s-%s' % (filename, repo.filtername)
34 return filename
34 return filename
35
35
36 def read(repo):
36 def read(repo):
37 try:
37 try:
38 f = repo.cachevfs(_filename(repo))
38 f = repo.cachevfs(_filename(repo))
39 lines = f.read().split('\n')
39 lines = f.read().split('\n')
40 f.close()
40 f.close()
41 except (IOError, OSError):
41 except (IOError, OSError):
42 return None
42 return None
43
43
44 try:
44 try:
45 cachekey = lines.pop(0).split(" ", 2)
45 cachekey = lines.pop(0).split(" ", 2)
46 last, lrev = cachekey[:2]
46 last, lrev = cachekey[:2]
47 last, lrev = bin(last), int(lrev)
47 last, lrev = bin(last), int(lrev)
48 filteredhash = None
48 filteredhash = None
49 if len(cachekey) > 2:
49 if len(cachekey) > 2:
50 filteredhash = bin(cachekey[2])
50 filteredhash = bin(cachekey[2])
51 partial = branchcache(tipnode=last, tiprev=lrev,
51 partial = branchcache(tipnode=last, tiprev=lrev,
52 filteredhash=filteredhash)
52 filteredhash=filteredhash)
53 if not partial.validfor(repo):
53 if not partial.validfor(repo):
54 # invalidate the cache
54 # invalidate the cache
55 raise ValueError('tip differs')
55 raise ValueError('tip differs')
56 cl = repo.changelog
56 cl = repo.changelog
57 for l in lines:
57 for l in lines:
58 if not l:
58 if not l:
59 continue
59 continue
60 node, state, label = l.split(" ", 2)
60 node, state, label = l.split(" ", 2)
61 if state not in 'oc':
61 if state not in 'oc':
62 raise ValueError('invalid branch state')
62 raise ValueError('invalid branch state')
63 label = encoding.tolocal(label.strip())
63 label = encoding.tolocal(label.strip())
64 node = bin(node)
64 node = bin(node)
65 if not cl.hasnode(node):
65 if not cl.hasnode(node):
66 raise ValueError('node %s does not exist' % hex(node))
66 raise ValueError('node %s does not exist' % hex(node))
67 partial.setdefault(label, []).append(node)
67 partial.setdefault(label, []).append(node)
68 if state == 'c':
68 if state == 'c':
69 partial._closednodes.add(node)
69 partial._closednodes.add(node)
70 except Exception as inst:
70 except Exception as inst:
71 if repo.ui.debugflag:
71 if repo.ui.debugflag:
72 msg = 'invalid branchheads cache'
72 msg = 'invalid branchheads cache'
73 if repo.filtername is not None:
73 if repo.filtername is not None:
74 msg += ' (%s)' % repo.filtername
74 msg += ' (%s)' % repo.filtername
75 msg += ': %s\n'
75 msg += ': %s\n'
76 repo.ui.debug(msg % inst)
76 repo.ui.debug(msg % inst)
77 partial = None
77 partial = None
78 return partial
78 return partial
79
79
80 ### Nearest subset relation
80 ### Nearest subset relation
81 # Nearest subset of filter X is a filter Y so that:
81 # Nearest subset of filter X is a filter Y so that:
82 # * Y is included in X,
82 # * Y is included in X,
83 # * X - Y is as small as possible.
83 # * X - Y is as small as possible.
84 # This create and ordering used for branchmap purpose.
84 # This create and ordering used for branchmap purpose.
85 # the ordering may be partial
85 # the ordering may be partial
86 subsettable = {None: 'visible',
86 subsettable = {None: 'visible',
87 'visible': 'served',
87 'visible': 'served',
88 'served': 'immutable',
88 'served': 'immutable',
89 'immutable': 'base'}
89 'immutable': 'base'}
90
90
91 def updatecache(repo):
91 def updatecache(repo):
92 cl = repo.changelog
92 cl = repo.changelog
93 filtername = repo.filtername
93 filtername = repo.filtername
94 partial = repo._branchcaches.get(filtername)
94 partial = repo._branchcaches.get(filtername)
95
95
96 revs = []
96 revs = []
97 if partial is None or not partial.validfor(repo):
97 if partial is None or not partial.validfor(repo):
98 partial = read(repo)
98 partial = read(repo)
99 if partial is None:
99 if partial is None:
100 subsetname = subsettable.get(filtername)
100 subsetname = subsettable.get(filtername)
101 if subsetname is None:
101 if subsetname is None:
102 partial = branchcache()
102 partial = branchcache()
103 else:
103 else:
104 subset = repo.filtered(subsetname)
104 subset = repo.filtered(subsetname)
105 partial = subset.branchmap().copy()
105 partial = subset.branchmap().copy()
106 extrarevs = subset.changelog.filteredrevs - cl.filteredrevs
106 extrarevs = subset.changelog.filteredrevs - cl.filteredrevs
107 revs.extend(r for r in extrarevs if r <= partial.tiprev)
107 revs.extend(r for r in extrarevs if r <= partial.tiprev)
108 revs.extend(cl.revs(start=partial.tiprev + 1))
108 revs.extend(cl.revs(start=partial.tiprev + 1))
109 if revs:
109 if revs:
110 partial.update(repo, revs)
110 partial.update(repo, revs)
111 partial.write(repo)
111 partial.write(repo)
112
112
113 assert partial.validfor(repo), filtername
113 assert partial.validfor(repo), filtername
114 repo._branchcaches[repo.filtername] = partial
114 repo._branchcaches[repo.filtername] = partial
115
115
116 def replacecache(repo, bm):
116 def replacecache(repo, bm):
117 """Replace the branchmap cache for a repo with a branch mapping.
117 """Replace the branchmap cache for a repo with a branch mapping.
118
118
119 This is likely only called during clone with a branch map from a remote.
119 This is likely only called during clone with a branch map from a remote.
120 """
120 """
121 rbheads = []
121 rbheads = []
122 closed = []
122 closed = []
123 for bheads in bm.itervalues():
123 for bheads in bm.itervalues():
124 rbheads.extend(bheads)
124 rbheads.extend(bheads)
125 for h in bheads:
125 for h in bheads:
126 r = repo.changelog.rev(h)
126 r = repo.changelog.rev(h)
127 b, c = repo.changelog.branchinfo(r)
127 b, c = repo.changelog.branchinfo(r)
128 if c:
128 if c:
129 closed.append(h)
129 closed.append(h)
130
130
131 if rbheads:
131 if rbheads:
132 rtiprev = max((int(repo.changelog.rev(node))
132 rtiprev = max((int(repo.changelog.rev(node))
133 for node in rbheads))
133 for node in rbheads))
134 cache = branchcache(bm,
134 cache = branchcache(bm,
135 repo[rtiprev].node(),
135 repo[rtiprev].node(),
136 rtiprev,
136 rtiprev,
137 closednodes=closed)
137 closednodes=closed)
138
138
139 # Try to stick it as low as possible
139 # Try to stick it as low as possible
140 # filter above served are unlikely to be fetch from a clone
140 # filter above served are unlikely to be fetch from a clone
141 for candidate in ('base', 'immutable', 'served'):
141 for candidate in ('base', 'immutable', 'served'):
142 rview = repo.filtered(candidate)
142 rview = repo.filtered(candidate)
143 if cache.validfor(rview):
143 if cache.validfor(rview):
144 repo._branchcaches[candidate] = cache
144 repo._branchcaches[candidate] = cache
145 cache.write(rview)
145 cache.write(rview)
146 break
146 break
147
147
148 class branchcache(dict):
148 class branchcache(dict):
149 """A dict like object that hold branches heads cache.
149 """A dict like object that hold branches heads cache.
150
150
151 This cache is used to avoid costly computations to determine all the
151 This cache is used to avoid costly computations to determine all the
152 branch heads of a repo.
152 branch heads of a repo.
153
153
154 The cache is serialized on disk in the following format:
154 The cache is serialized on disk in the following format:
155
155
156 <tip hex node> <tip rev number> [optional filtered repo hex hash]
156 <tip hex node> <tip rev number> [optional filtered repo hex hash]
157 <branch head hex node> <open/closed state> <branch name>
157 <branch head hex node> <open/closed state> <branch name>
158 <branch head hex node> <open/closed state> <branch name>
158 <branch head hex node> <open/closed state> <branch name>
159 ...
159 ...
160
160
161 The first line is used to check if the cache is still valid. If the
161 The first line is used to check if the cache is still valid. If the
162 branch cache is for a filtered repo view, an optional third hash is
162 branch cache is for a filtered repo view, an optional third hash is
163 included that hashes the hashes of all filtered revisions.
163 included that hashes the hashes of all filtered revisions.
164
164
165 The open/closed state is represented by a single letter 'o' or 'c'.
165 The open/closed state is represented by a single letter 'o' or 'c'.
166 This field can be used to avoid changelog reads when determining if a
166 This field can be used to avoid changelog reads when determining if a
167 branch head closes a branch or not.
167 branch head closes a branch or not.
168 """
168 """
169
169
170 def __init__(self, entries=(), tipnode=nullid, tiprev=nullrev,
170 def __init__(self, entries=(), tipnode=nullid, tiprev=nullrev,
171 filteredhash=None, closednodes=None):
171 filteredhash=None, closednodes=None):
172 super(branchcache, self).__init__(entries)
172 super(branchcache, self).__init__(entries)
173 self.tipnode = tipnode
173 self.tipnode = tipnode
174 self.tiprev = tiprev
174 self.tiprev = tiprev
175 self.filteredhash = filteredhash
175 self.filteredhash = filteredhash
176 # closednodes is a set of nodes that close their branch. If the branch
176 # closednodes is a set of nodes that close their branch. If the branch
177 # cache has been updated, it may contain nodes that are no longer
177 # cache has been updated, it may contain nodes that are no longer
178 # heads.
178 # heads.
179 if closednodes is None:
179 if closednodes is None:
180 self._closednodes = set()
180 self._closednodes = set()
181 else:
181 else:
182 self._closednodes = closednodes
182 self._closednodes = closednodes
183
183
184 def validfor(self, repo):
184 def validfor(self, repo):
185 """Is the cache content valid regarding a repo
185 """Is the cache content valid regarding a repo
186
186
187 - False when cached tipnode is unknown or if we detect a strip.
187 - False when cached tipnode is unknown or if we detect a strip.
188 - True when cache is up to date or a subset of current repo."""
188 - True when cache is up to date or a subset of current repo."""
189 try:
189 try:
190 return ((self.tipnode == repo.changelog.node(self.tiprev))
190 return ((self.tipnode == repo.changelog.node(self.tiprev))
191 and (self.filteredhash == \
191 and (self.filteredhash == \
192 scmutil.filteredhash(repo, self.tiprev)))
192 scmutil.filteredhash(repo, self.tiprev)))
193 except IndexError:
193 except IndexError:
194 return False
194 return False
195
195
196 def _branchtip(self, heads):
196 def _branchtip(self, heads):
197 '''Return tuple with last open head in heads and false,
197 '''Return tuple with last open head in heads and false,
198 otherwise return last closed head and true.'''
198 otherwise return last closed head and true.'''
199 tip = heads[-1]
199 tip = heads[-1]
200 closed = True
200 closed = True
201 for h in reversed(heads):
201 for h in reversed(heads):
202 if h not in self._closednodes:
202 if h not in self._closednodes:
203 tip = h
203 tip = h
204 closed = False
204 closed = False
205 break
205 break
206 return tip, closed
206 return tip, closed
207
207
208 def branchtip(self, branch):
208 def branchtip(self, branch):
209 '''Return the tipmost open head on branch head, otherwise return the
209 '''Return the tipmost open head on branch head, otherwise return the
210 tipmost closed head on branch.
210 tipmost closed head on branch.
211 Raise KeyError for unknown branch.'''
211 Raise KeyError for unknown branch.'''
212 return self._branchtip(self[branch])[0]
212 return self._branchtip(self[branch])[0]
213
213
214 def branchheads(self, branch, closed=False):
214 def branchheads(self, branch, closed=False):
215 heads = self[branch]
215 heads = self[branch]
216 if not closed:
216 if not closed:
217 heads = [h for h in heads if h not in self._closednodes]
217 heads = [h for h in heads if h not in self._closednodes]
218 return heads
218 return heads
219
219
220 def iterbranches(self):
220 def iterbranches(self):
221 for bn, heads in self.iteritems():
221 for bn, heads in self.iteritems():
222 yield (bn, heads) + self._branchtip(heads)
222 yield (bn, heads) + self._branchtip(heads)
223
223
224 def copy(self):
224 def copy(self):
225 """return an deep copy of the branchcache object"""
225 """return an deep copy of the branchcache object"""
226 return branchcache(self, self.tipnode, self.tiprev, self.filteredhash,
226 return branchcache(self, self.tipnode, self.tiprev, self.filteredhash,
227 self._closednodes)
227 self._closednodes)
228
228
229 def write(self, repo):
229 def write(self, repo):
230 try:
230 try:
231 f = repo.cachevfs(_filename(repo), "w", atomictemp=True)
231 f = repo.cachevfs(_filename(repo), "w", atomictemp=True)
232 cachekey = [hex(self.tipnode), '%d' % self.tiprev]
232 cachekey = [hex(self.tipnode), '%d' % self.tiprev]
233 if self.filteredhash is not None:
233 if self.filteredhash is not None:
234 cachekey.append(hex(self.filteredhash))
234 cachekey.append(hex(self.filteredhash))
235 f.write(" ".join(cachekey) + '\n')
235 f.write(" ".join(cachekey) + '\n')
236 nodecount = 0
236 nodecount = 0
237 for label, nodes in sorted(self.iteritems()):
237 for label, nodes in sorted(self.iteritems()):
238 for node in nodes:
238 for node in nodes:
239 nodecount += 1
239 nodecount += 1
240 if node in self._closednodes:
240 if node in self._closednodes:
241 state = 'c'
241 state = 'c'
242 else:
242 else:
243 state = 'o'
243 state = 'o'
244 f.write("%s %s %s\n" % (hex(node), state,
244 f.write("%s %s %s\n" % (hex(node), state,
245 encoding.fromlocal(label)))
245 encoding.fromlocal(label)))
246 f.close()
246 f.close()
247 repo.ui.log('branchcache',
247 repo.ui.log('branchcache',
248 'wrote %s branch cache with %d labels and %d nodes\n',
248 'wrote %s branch cache with %d labels and %d nodes\n',
249 repo.filtername, len(self), nodecount)
249 repo.filtername, len(self), nodecount)
250 except (IOError, OSError, error.Abort) as inst:
250 except (IOError, OSError, error.Abort) as inst:
251 repo.ui.debug("couldn't write branch cache: %s\n" % inst)
251 repo.ui.debug("couldn't write branch cache: %s\n" % inst)
252 # Abort may be raise by read only opener
252 # Abort may be raise by read only opener
253 pass
253 pass
254
254
255 def update(self, repo, revgen):
255 def update(self, repo, revgen):
256 """Given a branchhead cache, self, that may have extra nodes or be
256 """Given a branchhead cache, self, that may have extra nodes or be
257 missing heads, and a generator of nodes that are strictly a superset of
257 missing heads, and a generator of nodes that are strictly a superset of
258 heads missing, this function updates self to be correct.
258 heads missing, this function updates self to be correct.
259 """
259 """
260 starttime = util.timer()
260 starttime = util.timer()
261 cl = repo.changelog
261 cl = repo.changelog
262 # collect new branch entries
262 # collect new branch entries
263 newbranches = {}
263 newbranches = {}
264 getbranchinfo = repo.revbranchcache().branchinfo
264 getbranchinfo = repo.revbranchcache().branchinfo
265 for r in revgen:
265 for r in revgen:
266 branch, closesbranch = getbranchinfo(r)
266 branch, closesbranch = getbranchinfo(r)
267 newbranches.setdefault(branch, []).append(r)
267 newbranches.setdefault(branch, []).append(r)
268 if closesbranch:
268 if closesbranch:
269 self._closednodes.add(cl.node(r))
269 self._closednodes.add(cl.node(r))
270
270
271 # fetch current topological heads to speed up filtering
271 # fetch current topological heads to speed up filtering
272 topoheads = set(cl.headrevs())
272 topoheads = set(cl.headrevs())
273
273
274 # if older branchheads are reachable from new ones, they aren't
274 # if older branchheads are reachable from new ones, they aren't
275 # really branchheads. Note checking parents is insufficient:
275 # really branchheads. Note checking parents is insufficient:
276 # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
276 # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
277 for branch, newheadrevs in newbranches.iteritems():
277 for branch, newheadrevs in newbranches.iteritems():
278 bheads = self.setdefault(branch, [])
278 bheads = self.setdefault(branch, [])
279 bheadset = set(cl.rev(node) for node in bheads)
279 bheadset = set(cl.rev(node) for node in bheads)
280
280
281 # This have been tested True on all internal usage of this function.
281 # This have been tested True on all internal usage of this function.
282 # run it again in case of doubt
282 # run it again in case of doubt
283 # assert not (set(bheadrevs) & set(newheadrevs))
283 # assert not (set(bheadrevs) & set(newheadrevs))
284 newheadrevs.sort()
284 newheadrevs.sort()
285 bheadset.update(newheadrevs)
285 bheadset.update(newheadrevs)
286
286
287 # This prunes out two kinds of heads - heads that are superseded by
287 # This prunes out two kinds of heads - heads that are superseded by
288 # a head in newheadrevs, and newheadrevs that are not heads because
288 # a head in newheadrevs, and newheadrevs that are not heads because
289 # an existing head is their descendant.
289 # an existing head is their descendant.
290 uncertain = bheadset - topoheads
290 uncertain = bheadset - topoheads
291 if uncertain:
291 if uncertain:
292 floorrev = min(uncertain)
292 floorrev = min(uncertain)
293 ancestors = set(cl.ancestors(newheadrevs, floorrev))
293 ancestors = set(cl.ancestors(newheadrevs, floorrev))
294 bheadset -= ancestors
294 bheadset -= ancestors
295 bheadrevs = sorted(bheadset)
295 bheadrevs = sorted(bheadset)
296 self[branch] = [cl.node(rev) for rev in bheadrevs]
296 self[branch] = [cl.node(rev) for rev in bheadrevs]
297 tiprev = bheadrevs[-1]
297 tiprev = bheadrevs[-1]
298 if tiprev > self.tiprev:
298 if tiprev > self.tiprev:
299 self.tipnode = cl.node(tiprev)
299 self.tipnode = cl.node(tiprev)
300 self.tiprev = tiprev
300 self.tiprev = tiprev
301
301
302 if not self.validfor(repo):
302 if not self.validfor(repo):
303 # cache key are not valid anymore
303 # cache key are not valid anymore
304 self.tipnode = nullid
304 self.tipnode = nullid
305 self.tiprev = nullrev
305 self.tiprev = nullrev
306 for heads in self.values():
306 for heads in self.values():
307 tiprev = max(cl.rev(node) for node in heads)
307 tiprev = max(cl.rev(node) for node in heads)
308 if tiprev > self.tiprev:
308 if tiprev > self.tiprev:
309 self.tipnode = cl.node(tiprev)
309 self.tipnode = cl.node(tiprev)
310 self.tiprev = tiprev
310 self.tiprev = tiprev
311 self.filteredhash = scmutil.filteredhash(repo, self.tiprev)
311 self.filteredhash = scmutil.filteredhash(repo, self.tiprev)
312
312
313 duration = util.timer() - starttime
313 duration = util.timer() - starttime
314 repo.ui.log('branchcache', 'updated %s branch cache in %.4f seconds\n',
314 repo.ui.log('branchcache', 'updated %s branch cache in %.4f seconds\n',
315 repo.filtername, duration)
315 repo.filtername, duration)
316
316
317 # Revision branch info cache
317 # Revision branch info cache
318
318
319 _rbcversion = '-v1'
319 _rbcversion = '-v1'
320 _rbcnames = 'rbc-names' + _rbcversion
320 _rbcnames = 'rbc-names' + _rbcversion
321 _rbcrevs = 'rbc-revs' + _rbcversion
321 _rbcrevs = 'rbc-revs' + _rbcversion
322 # [4 byte hash prefix][4 byte branch name number with sign bit indicating open]
322 # [4 byte hash prefix][4 byte branch name number with sign bit indicating open]
323 _rbcrecfmt = '>4sI'
323 _rbcrecfmt = '>4sI'
324 _rbcrecsize = calcsize(_rbcrecfmt)
324 _rbcrecsize = calcsize(_rbcrecfmt)
325 _rbcnodelen = 4
325 _rbcnodelen = 4
326 _rbcbranchidxmask = 0x7fffffff
326 _rbcbranchidxmask = 0x7fffffff
327 _rbccloseflag = 0x80000000
327 _rbccloseflag = 0x80000000
328
328
329 class revbranchcache(object):
329 class revbranchcache(object):
330 """Persistent cache, mapping from revision number to branch name and close.
330 """Persistent cache, mapping from revision number to branch name and close.
331 This is a low level cache, independent of filtering.
331 This is a low level cache, independent of filtering.
332
332
333 Branch names are stored in rbc-names in internal encoding separated by 0.
333 Branch names are stored in rbc-names in internal encoding separated by 0.
334 rbc-names is append-only, and each branch name is only stored once and will
334 rbc-names is append-only, and each branch name is only stored once and will
335 thus have a unique index.
335 thus have a unique index.
336
336
337 The branch info for each revision is stored in rbc-revs as constant size
337 The branch info for each revision is stored in rbc-revs as constant size
338 records. The whole file is read into memory, but it is only 'parsed' on
338 records. The whole file is read into memory, but it is only 'parsed' on
339 demand. The file is usually append-only but will be truncated if repo
339 demand. The file is usually append-only but will be truncated if repo
340 modification is detected.
340 modification is detected.
341 The record for each revision contains the first 4 bytes of the
341 The record for each revision contains the first 4 bytes of the
342 corresponding node hash, and the record is only used if it still matches.
342 corresponding node hash, and the record is only used if it still matches.
343 Even a completely trashed rbc-revs fill thus still give the right result
343 Even a completely trashed rbc-revs fill thus still give the right result
344 while converging towards full recovery ... assuming no incorrectly matching
344 while converging towards full recovery ... assuming no incorrectly matching
345 node hashes.
345 node hashes.
346 The record also contains 4 bytes where 31 bits contains the index of the
346 The record also contains 4 bytes where 31 bits contains the index of the
347 branch and the last bit indicate that it is a branch close commit.
347 branch and the last bit indicate that it is a branch close commit.
348 The usage pattern for rbc-revs is thus somewhat similar to 00changelog.i
348 The usage pattern for rbc-revs is thus somewhat similar to 00changelog.i
349 and will grow with it but be 1/8th of its size.
349 and will grow with it but be 1/8th of its size.
350 """
350 """
351
351
352 def __init__(self, repo, readonly=True):
352 def __init__(self, repo, readonly=True):
353 assert repo.filtername is None
353 assert repo.filtername is None
354 self._repo = repo
354 self._repo = repo
355 self._names = [] # branch names in local encoding with static index
355 self._names = [] # branch names in local encoding with static index
356 self._rbcrevs = bytearray()
356 self._rbcrevs = bytearray()
357 self._rbcsnameslen = 0 # length of names read at _rbcsnameslen
357 self._rbcsnameslen = 0 # length of names read at _rbcsnameslen
358 try:
358 try:
359 bndata = repo.cachevfs.read(_rbcnames)
359 bndata = repo.cachevfs.read(_rbcnames)
360 self._rbcsnameslen = len(bndata) # for verification before writing
360 self._rbcsnameslen = len(bndata) # for verification before writing
361 if bndata:
361 if bndata:
362 self._names = [encoding.tolocal(bn)
362 self._names = [encoding.tolocal(bn)
363 for bn in bndata.split('\0')]
363 for bn in bndata.split('\0')]
364 except (IOError, OSError):
364 except (IOError, OSError):
365 if readonly:
365 if readonly:
366 # don't try to use cache - fall back to the slow path
366 # don't try to use cache - fall back to the slow path
367 self.branchinfo = self._branchinfo
367 self.branchinfo = self._branchinfo
368
368
369 if self._names:
369 if self._names:
370 try:
370 try:
371 data = repo.cachevfs.read(_rbcrevs)
371 data = repo.cachevfs.read(_rbcrevs)
372 self._rbcrevs[:] = data
372 self._rbcrevs[:] = data
373 except (IOError, OSError) as inst:
373 except (IOError, OSError) as inst:
374 repo.ui.debug("couldn't read revision branch cache: %s\n" %
374 repo.ui.debug("couldn't read revision branch cache: %s\n" %
375 inst)
375 inst)
376 # remember number of good records on disk
376 # remember number of good records on disk
377 self._rbcrevslen = min(len(self._rbcrevs) // _rbcrecsize,
377 self._rbcrevslen = min(len(self._rbcrevs) // _rbcrecsize,
378 len(repo.changelog))
378 len(repo.changelog))
379 if self._rbcrevslen == 0:
379 if self._rbcrevslen == 0:
380 self._names = []
380 self._names = []
381 self._rbcnamescount = len(self._names) # number of names read at
381 self._rbcnamescount = len(self._names) # number of names read at
382 # _rbcsnameslen
382 # _rbcsnameslen
383 self._namesreverse = dict((b, r) for r, b in enumerate(self._names))
383 self._namesreverse = dict((b, r) for r, b in enumerate(self._names))
384
384
385 def _clear(self):
385 def _clear(self):
386 self._rbcsnameslen = 0
386 self._rbcsnameslen = 0
387 del self._names[:]
387 del self._names[:]
388 self._rbcnamescount = 0
388 self._rbcnamescount = 0
389 self._namesreverse.clear()
389 self._namesreverse.clear()
390 self._rbcrevslen = len(self._repo.changelog)
390 self._rbcrevslen = len(self._repo.changelog)
391 self._rbcrevs = bytearray(self._rbcrevslen * _rbcrecsize)
391 self._rbcrevs = bytearray(self._rbcrevslen * _rbcrecsize)
392
392
393 def branchinfo(self, rev):
393 def branchinfo(self, rev):
394 """Return branch name and close flag for rev, using and updating
394 """Return branch name and close flag for rev, using and updating
395 persistent cache."""
395 persistent cache."""
396 changelog = self._repo.changelog
396 changelog = self._repo.changelog
397 rbcrevidx = rev * _rbcrecsize
397 rbcrevidx = rev * _rbcrecsize
398
398
399 # avoid negative index, changelog.read(nullrev) is fast without cache
399 # avoid negative index, changelog.read(nullrev) is fast without cache
400 if rev == nullrev:
400 if rev == nullrev:
401 return changelog.branchinfo(rev)
401 return changelog.branchinfo(rev)
402
402
403 # if requested rev isn't allocated, grow and cache the rev info
403 # if requested rev isn't allocated, grow and cache the rev info
404 if len(self._rbcrevs) < rbcrevidx + _rbcrecsize:
404 if len(self._rbcrevs) < rbcrevidx + _rbcrecsize:
405 return self._branchinfo(rev)
405 return self._branchinfo(rev)
406
406
407 # fast path: extract data from cache, use it if node is matching
407 # fast path: extract data from cache, use it if node is matching
408 reponode = changelog.node(rev)[:_rbcnodelen]
408 reponode = changelog.node(rev)[:_rbcnodelen]
409 cachenode, branchidx = unpack_from(_rbcrecfmt, self._rbcrevs, rbcrevidx)
409 cachenode, branchidx = unpack_from(
410 _rbcrecfmt, util.buffer(self._rbcrevs), rbcrevidx)
410 close = bool(branchidx & _rbccloseflag)
411 close = bool(branchidx & _rbccloseflag)
411 if close:
412 if close:
412 branchidx &= _rbcbranchidxmask
413 branchidx &= _rbcbranchidxmask
413 if cachenode == '\0\0\0\0':
414 if cachenode == '\0\0\0\0':
414 pass
415 pass
415 elif cachenode == reponode:
416 elif cachenode == reponode:
416 try:
417 try:
417 return self._names[branchidx], close
418 return self._names[branchidx], close
418 except IndexError:
419 except IndexError:
419 # recover from invalid reference to unknown branch
420 # recover from invalid reference to unknown branch
420 self._repo.ui.debug("referenced branch names not found"
421 self._repo.ui.debug("referenced branch names not found"
421 " - rebuilding revision branch cache from scratch\n")
422 " - rebuilding revision branch cache from scratch\n")
422 self._clear()
423 self._clear()
423 else:
424 else:
424 # rev/node map has changed, invalidate the cache from here up
425 # rev/node map has changed, invalidate the cache from here up
425 self._repo.ui.debug("history modification detected - truncating "
426 self._repo.ui.debug("history modification detected - truncating "
426 "revision branch cache to revision %d\n" % rev)
427 "revision branch cache to revision %d\n" % rev)
427 truncate = rbcrevidx + _rbcrecsize
428 truncate = rbcrevidx + _rbcrecsize
428 del self._rbcrevs[truncate:]
429 del self._rbcrevs[truncate:]
429 self._rbcrevslen = min(self._rbcrevslen, truncate)
430 self._rbcrevslen = min(self._rbcrevslen, truncate)
430
431
431 # fall back to slow path and make sure it will be written to disk
432 # fall back to slow path and make sure it will be written to disk
432 return self._branchinfo(rev)
433 return self._branchinfo(rev)
433
434
434 def _branchinfo(self, rev):
435 def _branchinfo(self, rev):
435 """Retrieve branch info from changelog and update _rbcrevs"""
436 """Retrieve branch info from changelog and update _rbcrevs"""
436 changelog = self._repo.changelog
437 changelog = self._repo.changelog
437 b, close = changelog.branchinfo(rev)
438 b, close = changelog.branchinfo(rev)
438 if b in self._namesreverse:
439 if b in self._namesreverse:
439 branchidx = self._namesreverse[b]
440 branchidx = self._namesreverse[b]
440 else:
441 else:
441 branchidx = len(self._names)
442 branchidx = len(self._names)
442 self._names.append(b)
443 self._names.append(b)
443 self._namesreverse[b] = branchidx
444 self._namesreverse[b] = branchidx
444 reponode = changelog.node(rev)
445 reponode = changelog.node(rev)
445 if close:
446 if close:
446 branchidx |= _rbccloseflag
447 branchidx |= _rbccloseflag
447 self._setcachedata(rev, reponode, branchidx)
448 self._setcachedata(rev, reponode, branchidx)
448 return b, close
449 return b, close
449
450
450 def _setcachedata(self, rev, node, branchidx):
451 def _setcachedata(self, rev, node, branchidx):
451 """Writes the node's branch data to the in-memory cache data."""
452 """Writes the node's branch data to the in-memory cache data."""
452 if rev == nullrev:
453 if rev == nullrev:
453 return
454 return
454 rbcrevidx = rev * _rbcrecsize
455 rbcrevidx = rev * _rbcrecsize
455 if len(self._rbcrevs) < rbcrevidx + _rbcrecsize:
456 if len(self._rbcrevs) < rbcrevidx + _rbcrecsize:
456 self._rbcrevs.extend('\0' *
457 self._rbcrevs.extend('\0' *
457 (len(self._repo.changelog) * _rbcrecsize -
458 (len(self._repo.changelog) * _rbcrecsize -
458 len(self._rbcrevs)))
459 len(self._rbcrevs)))
459 pack_into(_rbcrecfmt, self._rbcrevs, rbcrevidx, node, branchidx)
460 pack_into(_rbcrecfmt, self._rbcrevs, rbcrevidx, node, branchidx)
460 self._rbcrevslen = min(self._rbcrevslen, rev)
461 self._rbcrevslen = min(self._rbcrevslen, rev)
461
462
462 tr = self._repo.currenttransaction()
463 tr = self._repo.currenttransaction()
463 if tr:
464 if tr:
464 tr.addfinalize('write-revbranchcache', self.write)
465 tr.addfinalize('write-revbranchcache', self.write)
465
466
466 def write(self, tr=None):
467 def write(self, tr=None):
467 """Save branch cache if it is dirty."""
468 """Save branch cache if it is dirty."""
468 repo = self._repo
469 repo = self._repo
469 wlock = None
470 wlock = None
470 step = ''
471 step = ''
471 try:
472 try:
472 if self._rbcnamescount < len(self._names):
473 if self._rbcnamescount < len(self._names):
473 step = ' names'
474 step = ' names'
474 wlock = repo.wlock(wait=False)
475 wlock = repo.wlock(wait=False)
475 if self._rbcnamescount != 0:
476 if self._rbcnamescount != 0:
476 f = repo.cachevfs.open(_rbcnames, 'ab')
477 f = repo.cachevfs.open(_rbcnames, 'ab')
477 if f.tell() == self._rbcsnameslen:
478 if f.tell() == self._rbcsnameslen:
478 f.write('\0')
479 f.write('\0')
479 else:
480 else:
480 f.close()
481 f.close()
481 repo.ui.debug("%s changed - rewriting it\n" % _rbcnames)
482 repo.ui.debug("%s changed - rewriting it\n" % _rbcnames)
482 self._rbcnamescount = 0
483 self._rbcnamescount = 0
483 self._rbcrevslen = 0
484 self._rbcrevslen = 0
484 if self._rbcnamescount == 0:
485 if self._rbcnamescount == 0:
485 # before rewriting names, make sure references are removed
486 # before rewriting names, make sure references are removed
486 repo.cachevfs.unlinkpath(_rbcrevs, ignoremissing=True)
487 repo.cachevfs.unlinkpath(_rbcrevs, ignoremissing=True)
487 f = repo.cachevfs.open(_rbcnames, 'wb')
488 f = repo.cachevfs.open(_rbcnames, 'wb')
488 f.write('\0'.join(encoding.fromlocal(b)
489 f.write('\0'.join(encoding.fromlocal(b)
489 for b in self._names[self._rbcnamescount:]))
490 for b in self._names[self._rbcnamescount:]))
490 self._rbcsnameslen = f.tell()
491 self._rbcsnameslen = f.tell()
491 f.close()
492 f.close()
492 self._rbcnamescount = len(self._names)
493 self._rbcnamescount = len(self._names)
493
494
494 start = self._rbcrevslen * _rbcrecsize
495 start = self._rbcrevslen * _rbcrecsize
495 if start != len(self._rbcrevs):
496 if start != len(self._rbcrevs):
496 step = ''
497 step = ''
497 if wlock is None:
498 if wlock is None:
498 wlock = repo.wlock(wait=False)
499 wlock = repo.wlock(wait=False)
499 revs = min(len(repo.changelog),
500 revs = min(len(repo.changelog),
500 len(self._rbcrevs) // _rbcrecsize)
501 len(self._rbcrevs) // _rbcrecsize)
501 f = repo.cachevfs.open(_rbcrevs, 'ab')
502 f = repo.cachevfs.open(_rbcrevs, 'ab')
502 if f.tell() != start:
503 if f.tell() != start:
503 repo.ui.debug("truncating cache/%s to %d\n"
504 repo.ui.debug("truncating cache/%s to %d\n"
504 % (_rbcrevs, start))
505 % (_rbcrevs, start))
505 f.seek(start)
506 f.seek(start)
506 if f.tell() != start:
507 if f.tell() != start:
507 start = 0
508 start = 0
508 f.seek(start)
509 f.seek(start)
509 f.truncate()
510 f.truncate()
510 end = revs * _rbcrecsize
511 end = revs * _rbcrecsize
511 f.write(self._rbcrevs[start:end])
512 f.write(self._rbcrevs[start:end])
512 f.close()
513 f.close()
513 self._rbcrevslen = revs
514 self._rbcrevslen = revs
514 except (IOError, OSError, error.Abort, error.LockError) as inst:
515 except (IOError, OSError, error.Abort, error.LockError) as inst:
515 repo.ui.debug("couldn't write revision branch cache%s: %s\n"
516 repo.ui.debug("couldn't write revision branch cache%s: %s\n"
516 % (step, inst))
517 % (step, inst))
517 finally:
518 finally:
518 if wlock is not None:
519 if wlock is not None:
519 wlock.release()
520 wlock.release()
@@ -1,5499 +1,5501 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@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 difflib
10 import difflib
11 import errno
11 import errno
12 import os
12 import os
13 import re
13 import re
14 import sys
14 import sys
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import (
17 from .node import (
18 hex,
18 hex,
19 nullid,
19 nullid,
20 nullrev,
20 nullrev,
21 short,
21 short,
22 )
22 )
23 from . import (
23 from . import (
24 archival,
24 archival,
25 bookmarks,
25 bookmarks,
26 bundle2,
26 bundle2,
27 changegroup,
27 changegroup,
28 cmdutil,
28 cmdutil,
29 copies,
29 copies,
30 debugcommands as debugcommandsmod,
30 debugcommands as debugcommandsmod,
31 destutil,
31 destutil,
32 dirstateguard,
32 dirstateguard,
33 discovery,
33 discovery,
34 encoding,
34 encoding,
35 error,
35 error,
36 exchange,
36 exchange,
37 extensions,
37 extensions,
38 formatter,
38 formatter,
39 graphmod,
39 graphmod,
40 hbisect,
40 hbisect,
41 help,
41 help,
42 hg,
42 hg,
43 lock as lockmod,
43 lock as lockmod,
44 merge as mergemod,
44 merge as mergemod,
45 obsolete,
45 obsolete,
46 patch,
46 patch,
47 phases,
47 phases,
48 pycompat,
48 pycompat,
49 rcutil,
49 rcutil,
50 registrar,
50 registrar,
51 revsetlang,
51 revsetlang,
52 scmutil,
52 scmutil,
53 server,
53 server,
54 sshserver,
54 sshserver,
55 streamclone,
55 streamclone,
56 tags as tagsmod,
56 tags as tagsmod,
57 templatekw,
57 templatekw,
58 ui as uimod,
58 ui as uimod,
59 util,
59 util,
60 )
60 )
61
61
62 release = lockmod.release
62 release = lockmod.release
63
63
64 table = {}
64 table = {}
65 table.update(debugcommandsmod.command._table)
65 table.update(debugcommandsmod.command._table)
66
66
67 command = registrar.command(table)
67 command = registrar.command(table)
68
68
69 # common command options
69 # common command options
70
70
71 globalopts = [
71 globalopts = [
72 ('R', 'repository', '',
72 ('R', 'repository', '',
73 _('repository root directory or name of overlay bundle file'),
73 _('repository root directory or name of overlay bundle file'),
74 _('REPO')),
74 _('REPO')),
75 ('', 'cwd', '',
75 ('', 'cwd', '',
76 _('change working directory'), _('DIR')),
76 _('change working directory'), _('DIR')),
77 ('y', 'noninteractive', None,
77 ('y', 'noninteractive', None,
78 _('do not prompt, automatically pick the first choice for all prompts')),
78 _('do not prompt, automatically pick the first choice for all prompts')),
79 ('q', 'quiet', None, _('suppress output')),
79 ('q', 'quiet', None, _('suppress output')),
80 ('v', 'verbose', None, _('enable additional output')),
80 ('v', 'verbose', None, _('enable additional output')),
81 ('', 'color', '',
81 ('', 'color', '',
82 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
82 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
83 # and should not be translated
83 # and should not be translated
84 _("when to colorize (boolean, always, auto, never, or debug)"),
84 _("when to colorize (boolean, always, auto, never, or debug)"),
85 _('TYPE')),
85 _('TYPE')),
86 ('', 'config', [],
86 ('', 'config', [],
87 _('set/override config option (use \'section.name=value\')'),
87 _('set/override config option (use \'section.name=value\')'),
88 _('CONFIG')),
88 _('CONFIG')),
89 ('', 'debug', None, _('enable debugging output')),
89 ('', 'debug', None, _('enable debugging output')),
90 ('', 'debugger', None, _('start debugger')),
90 ('', 'debugger', None, _('start debugger')),
91 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
91 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
92 _('ENCODE')),
92 _('ENCODE')),
93 ('', 'encodingmode', encoding.encodingmode,
93 ('', 'encodingmode', encoding.encodingmode,
94 _('set the charset encoding mode'), _('MODE')),
94 _('set the charset encoding mode'), _('MODE')),
95 ('', 'traceback', None, _('always print a traceback on exception')),
95 ('', 'traceback', None, _('always print a traceback on exception')),
96 ('', 'time', None, _('time how long the command takes')),
96 ('', 'time', None, _('time how long the command takes')),
97 ('', 'profile', None, _('print command execution profile')),
97 ('', 'profile', None, _('print command execution profile')),
98 ('', 'version', None, _('output version information and exit')),
98 ('', 'version', None, _('output version information and exit')),
99 ('h', 'help', None, _('display help and exit')),
99 ('h', 'help', None, _('display help and exit')),
100 ('', 'hidden', False, _('consider hidden changesets')),
100 ('', 'hidden', False, _('consider hidden changesets')),
101 ('', 'pager', 'auto',
101 ('', 'pager', 'auto',
102 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
102 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
103 ]
103 ]
104
104
105 dryrunopts = cmdutil.dryrunopts
105 dryrunopts = cmdutil.dryrunopts
106 remoteopts = cmdutil.remoteopts
106 remoteopts = cmdutil.remoteopts
107 walkopts = cmdutil.walkopts
107 walkopts = cmdutil.walkopts
108 commitopts = cmdutil.commitopts
108 commitopts = cmdutil.commitopts
109 commitopts2 = cmdutil.commitopts2
109 commitopts2 = cmdutil.commitopts2
110 formatteropts = cmdutil.formatteropts
110 formatteropts = cmdutil.formatteropts
111 templateopts = cmdutil.templateopts
111 templateopts = cmdutil.templateopts
112 logopts = cmdutil.logopts
112 logopts = cmdutil.logopts
113 diffopts = cmdutil.diffopts
113 diffopts = cmdutil.diffopts
114 diffwsopts = cmdutil.diffwsopts
114 diffwsopts = cmdutil.diffwsopts
115 diffopts2 = cmdutil.diffopts2
115 diffopts2 = cmdutil.diffopts2
116 mergetoolopts = cmdutil.mergetoolopts
116 mergetoolopts = cmdutil.mergetoolopts
117 similarityopts = cmdutil.similarityopts
117 similarityopts = cmdutil.similarityopts
118 subrepoopts = cmdutil.subrepoopts
118 subrepoopts = cmdutil.subrepoopts
119 debugrevlogopts = cmdutil.debugrevlogopts
119 debugrevlogopts = cmdutil.debugrevlogopts
120
120
121 # Commands start here, listed alphabetically
121 # Commands start here, listed alphabetically
122
122
123 @command('^add',
123 @command('^add',
124 walkopts + subrepoopts + dryrunopts,
124 walkopts + subrepoopts + dryrunopts,
125 _('[OPTION]... [FILE]...'),
125 _('[OPTION]... [FILE]...'),
126 inferrepo=True)
126 inferrepo=True)
127 def add(ui, repo, *pats, **opts):
127 def add(ui, repo, *pats, **opts):
128 """add the specified files on the next commit
128 """add the specified files on the next commit
129
129
130 Schedule files to be version controlled and added to the
130 Schedule files to be version controlled and added to the
131 repository.
131 repository.
132
132
133 The files will be added to the repository at the next commit. To
133 The files will be added to the repository at the next commit. To
134 undo an add before that, see :hg:`forget`.
134 undo an add before that, see :hg:`forget`.
135
135
136 If no names are given, add all files to the repository (except
136 If no names are given, add all files to the repository (except
137 files matching ``.hgignore``).
137 files matching ``.hgignore``).
138
138
139 .. container:: verbose
139 .. container:: verbose
140
140
141 Examples:
141 Examples:
142
142
143 - New (unknown) files are added
143 - New (unknown) files are added
144 automatically by :hg:`add`::
144 automatically by :hg:`add`::
145
145
146 $ ls
146 $ ls
147 foo.c
147 foo.c
148 $ hg status
148 $ hg status
149 ? foo.c
149 ? foo.c
150 $ hg add
150 $ hg add
151 adding foo.c
151 adding foo.c
152 $ hg status
152 $ hg status
153 A foo.c
153 A foo.c
154
154
155 - Specific files to be added can be specified::
155 - Specific files to be added can be specified::
156
156
157 $ ls
157 $ ls
158 bar.c foo.c
158 bar.c foo.c
159 $ hg status
159 $ hg status
160 ? bar.c
160 ? bar.c
161 ? foo.c
161 ? foo.c
162 $ hg add bar.c
162 $ hg add bar.c
163 $ hg status
163 $ hg status
164 A bar.c
164 A bar.c
165 ? foo.c
165 ? foo.c
166
166
167 Returns 0 if all files are successfully added.
167 Returns 0 if all files are successfully added.
168 """
168 """
169
169
170 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
170 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
171 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
171 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
172 return rejected and 1 or 0
172 return rejected and 1 or 0
173
173
174 @command('addremove',
174 @command('addremove',
175 similarityopts + subrepoopts + walkopts + dryrunopts,
175 similarityopts + subrepoopts + walkopts + dryrunopts,
176 _('[OPTION]... [FILE]...'),
176 _('[OPTION]... [FILE]...'),
177 inferrepo=True)
177 inferrepo=True)
178 def addremove(ui, repo, *pats, **opts):
178 def addremove(ui, repo, *pats, **opts):
179 """add all new files, delete all missing files
179 """add all new files, delete all missing files
180
180
181 Add all new files and remove all missing files from the
181 Add all new files and remove all missing files from the
182 repository.
182 repository.
183
183
184 Unless names are given, new files are ignored if they match any of
184 Unless names are given, new files are ignored if they match any of
185 the patterns in ``.hgignore``. As with add, these changes take
185 the patterns in ``.hgignore``. As with add, these changes take
186 effect at the next commit.
186 effect at the next commit.
187
187
188 Use the -s/--similarity option to detect renamed files. This
188 Use the -s/--similarity option to detect renamed files. This
189 option takes a percentage between 0 (disabled) and 100 (files must
189 option takes a percentage between 0 (disabled) and 100 (files must
190 be identical) as its parameter. With a parameter greater than 0,
190 be identical) as its parameter. With a parameter greater than 0,
191 this compares every removed file with every added file and records
191 this compares every removed file with every added file and records
192 those similar enough as renames. Detecting renamed files this way
192 those similar enough as renames. Detecting renamed files this way
193 can be expensive. After using this option, :hg:`status -C` can be
193 can be expensive. After using this option, :hg:`status -C` can be
194 used to check which files were identified as moved or renamed. If
194 used to check which files were identified as moved or renamed. If
195 not specified, -s/--similarity defaults to 100 and only renames of
195 not specified, -s/--similarity defaults to 100 and only renames of
196 identical files are detected.
196 identical files are detected.
197
197
198 .. container:: verbose
198 .. container:: verbose
199
199
200 Examples:
200 Examples:
201
201
202 - A number of files (bar.c and foo.c) are new,
202 - A number of files (bar.c and foo.c) are new,
203 while foobar.c has been removed (without using :hg:`remove`)
203 while foobar.c has been removed (without using :hg:`remove`)
204 from the repository::
204 from the repository::
205
205
206 $ ls
206 $ ls
207 bar.c foo.c
207 bar.c foo.c
208 $ hg status
208 $ hg status
209 ! foobar.c
209 ! foobar.c
210 ? bar.c
210 ? bar.c
211 ? foo.c
211 ? foo.c
212 $ hg addremove
212 $ hg addremove
213 adding bar.c
213 adding bar.c
214 adding foo.c
214 adding foo.c
215 removing foobar.c
215 removing foobar.c
216 $ hg status
216 $ hg status
217 A bar.c
217 A bar.c
218 A foo.c
218 A foo.c
219 R foobar.c
219 R foobar.c
220
220
221 - A file foobar.c was moved to foo.c without using :hg:`rename`.
221 - A file foobar.c was moved to foo.c without using :hg:`rename`.
222 Afterwards, it was edited slightly::
222 Afterwards, it was edited slightly::
223
223
224 $ ls
224 $ ls
225 foo.c
225 foo.c
226 $ hg status
226 $ hg status
227 ! foobar.c
227 ! foobar.c
228 ? foo.c
228 ? foo.c
229 $ hg addremove --similarity 90
229 $ hg addremove --similarity 90
230 removing foobar.c
230 removing foobar.c
231 adding foo.c
231 adding foo.c
232 recording removal of foobar.c as rename to foo.c (94% similar)
232 recording removal of foobar.c as rename to foo.c (94% similar)
233 $ hg status -C
233 $ hg status -C
234 A foo.c
234 A foo.c
235 foobar.c
235 foobar.c
236 R foobar.c
236 R foobar.c
237
237
238 Returns 0 if all files are successfully added.
238 Returns 0 if all files are successfully added.
239 """
239 """
240 opts = pycompat.byteskwargs(opts)
240 opts = pycompat.byteskwargs(opts)
241 try:
241 try:
242 sim = float(opts.get('similarity') or 100)
242 sim = float(opts.get('similarity') or 100)
243 except ValueError:
243 except ValueError:
244 raise error.Abort(_('similarity must be a number'))
244 raise error.Abort(_('similarity must be a number'))
245 if sim < 0 or sim > 100:
245 if sim < 0 or sim > 100:
246 raise error.Abort(_('similarity must be between 0 and 100'))
246 raise error.Abort(_('similarity must be between 0 and 100'))
247 matcher = scmutil.match(repo[None], pats, opts)
247 matcher = scmutil.match(repo[None], pats, opts)
248 return scmutil.addremove(repo, matcher, "", opts, similarity=sim / 100.0)
248 return scmutil.addremove(repo, matcher, "", opts, similarity=sim / 100.0)
249
249
250 @command('^annotate|blame',
250 @command('^annotate|blame',
251 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
251 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
252 ('', 'follow', None,
252 ('', 'follow', None,
253 _('follow copies/renames and list the filename (DEPRECATED)')),
253 _('follow copies/renames and list the filename (DEPRECATED)')),
254 ('', 'no-follow', None, _("don't follow copies and renames")),
254 ('', 'no-follow', None, _("don't follow copies and renames")),
255 ('a', 'text', None, _('treat all files as text')),
255 ('a', 'text', None, _('treat all files as text')),
256 ('u', 'user', None, _('list the author (long with -v)')),
256 ('u', 'user', None, _('list the author (long with -v)')),
257 ('f', 'file', None, _('list the filename')),
257 ('f', 'file', None, _('list the filename')),
258 ('d', 'date', None, _('list the date (short with -q)')),
258 ('d', 'date', None, _('list the date (short with -q)')),
259 ('n', 'number', None, _('list the revision number (default)')),
259 ('n', 'number', None, _('list the revision number (default)')),
260 ('c', 'changeset', None, _('list the changeset')),
260 ('c', 'changeset', None, _('list the changeset')),
261 ('l', 'line-number', None, _('show line number at the first appearance')),
261 ('l', 'line-number', None, _('show line number at the first appearance')),
262 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
262 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
263 ] + diffwsopts + walkopts + formatteropts,
263 ] + diffwsopts + walkopts + formatteropts,
264 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
264 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
265 inferrepo=True)
265 inferrepo=True)
266 def annotate(ui, repo, *pats, **opts):
266 def annotate(ui, repo, *pats, **opts):
267 """show changeset information by line for each file
267 """show changeset information by line for each file
268
268
269 List changes in files, showing the revision id responsible for
269 List changes in files, showing the revision id responsible for
270 each line.
270 each line.
271
271
272 This command is useful for discovering when a change was made and
272 This command is useful for discovering when a change was made and
273 by whom.
273 by whom.
274
274
275 If you include --file, --user, or --date, the revision number is
275 If you include --file, --user, or --date, the revision number is
276 suppressed unless you also include --number.
276 suppressed unless you also include --number.
277
277
278 Without the -a/--text option, annotate will avoid processing files
278 Without the -a/--text option, annotate will avoid processing files
279 it detects as binary. With -a, annotate will annotate the file
279 it detects as binary. With -a, annotate will annotate the file
280 anyway, although the results will probably be neither useful
280 anyway, although the results will probably be neither useful
281 nor desirable.
281 nor desirable.
282
282
283 Returns 0 on success.
283 Returns 0 on success.
284 """
284 """
285 opts = pycompat.byteskwargs(opts)
285 opts = pycompat.byteskwargs(opts)
286 if not pats:
286 if not pats:
287 raise error.Abort(_('at least one filename or pattern is required'))
287 raise error.Abort(_('at least one filename or pattern is required'))
288
288
289 if opts.get('follow'):
289 if opts.get('follow'):
290 # --follow is deprecated and now just an alias for -f/--file
290 # --follow is deprecated and now just an alias for -f/--file
291 # to mimic the behavior of Mercurial before version 1.5
291 # to mimic the behavior of Mercurial before version 1.5
292 opts['file'] = True
292 opts['file'] = True
293
293
294 ctx = scmutil.revsingle(repo, opts.get('rev'))
294 ctx = scmutil.revsingle(repo, opts.get('rev'))
295
295
296 rootfm = ui.formatter('annotate', opts)
296 rootfm = ui.formatter('annotate', opts)
297 if ui.quiet:
297 if ui.quiet:
298 datefunc = util.shortdate
298 datefunc = util.shortdate
299 else:
299 else:
300 datefunc = util.datestr
300 datefunc = util.datestr
301 if ctx.rev() is None:
301 if ctx.rev() is None:
302 def hexfn(node):
302 def hexfn(node):
303 if node is None:
303 if node is None:
304 return None
304 return None
305 else:
305 else:
306 return rootfm.hexfunc(node)
306 return rootfm.hexfunc(node)
307 if opts.get('changeset'):
307 if opts.get('changeset'):
308 # omit "+" suffix which is appended to node hex
308 # omit "+" suffix which is appended to node hex
309 def formatrev(rev):
309 def formatrev(rev):
310 if rev is None:
310 if rev is None:
311 return '%d' % ctx.p1().rev()
311 return '%d' % ctx.p1().rev()
312 else:
312 else:
313 return '%d' % rev
313 return '%d' % rev
314 else:
314 else:
315 def formatrev(rev):
315 def formatrev(rev):
316 if rev is None:
316 if rev is None:
317 return '%d+' % ctx.p1().rev()
317 return '%d+' % ctx.p1().rev()
318 else:
318 else:
319 return '%d ' % rev
319 return '%d ' % rev
320 def formathex(hex):
320 def formathex(hex):
321 if hex is None:
321 if hex is None:
322 return '%s+' % rootfm.hexfunc(ctx.p1().node())
322 return '%s+' % rootfm.hexfunc(ctx.p1().node())
323 else:
323 else:
324 return '%s ' % hex
324 return '%s ' % hex
325 else:
325 else:
326 hexfn = rootfm.hexfunc
326 hexfn = rootfm.hexfunc
327 formatrev = formathex = pycompat.bytestr
327 formatrev = formathex = pycompat.bytestr
328
328
329 opmap = [('user', ' ', lambda x: x[0].user(), ui.shortuser),
329 opmap = [('user', ' ', lambda x: x[0].user(), ui.shortuser),
330 ('number', ' ', lambda x: x[0].rev(), formatrev),
330 ('number', ' ', lambda x: x[0].rev(), formatrev),
331 ('changeset', ' ', lambda x: hexfn(x[0].node()), formathex),
331 ('changeset', ' ', lambda x: hexfn(x[0].node()), formathex),
332 ('date', ' ', lambda x: x[0].date(), util.cachefunc(datefunc)),
332 ('date', ' ', lambda x: x[0].date(), util.cachefunc(datefunc)),
333 ('file', ' ', lambda x: x[0].path(), str),
333 ('file', ' ', lambda x: x[0].path(), str),
334 ('line_number', ':', lambda x: x[1], str),
334 ('line_number', ':', lambda x: x[1], str),
335 ]
335 ]
336 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
336 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
337
337
338 if (not opts.get('user') and not opts.get('changeset')
338 if (not opts.get('user') and not opts.get('changeset')
339 and not opts.get('date') and not opts.get('file')):
339 and not opts.get('date') and not opts.get('file')):
340 opts['number'] = True
340 opts['number'] = True
341
341
342 linenumber = opts.get('line_number') is not None
342 linenumber = opts.get('line_number') is not None
343 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
343 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
344 raise error.Abort(_('at least one of -n/-c is required for -l'))
344 raise error.Abort(_('at least one of -n/-c is required for -l'))
345
345
346 ui.pager('annotate')
346 ui.pager('annotate')
347
347
348 if rootfm.isplain():
348 if rootfm.isplain():
349 def makefunc(get, fmt):
349 def makefunc(get, fmt):
350 return lambda x: fmt(get(x))
350 return lambda x: fmt(get(x))
351 else:
351 else:
352 def makefunc(get, fmt):
352 def makefunc(get, fmt):
353 return get
353 return get
354 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
354 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
355 if opts.get(op)]
355 if opts.get(op)]
356 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
356 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
357 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
357 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
358 if opts.get(op))
358 if opts.get(op))
359
359
360 def bad(x, y):
360 def bad(x, y):
361 raise error.Abort("%s: %s" % (x, y))
361 raise error.Abort("%s: %s" % (x, y))
362
362
363 m = scmutil.match(ctx, pats, opts, badfn=bad)
363 m = scmutil.match(ctx, pats, opts, badfn=bad)
364
364
365 follow = not opts.get('no_follow')
365 follow = not opts.get('no_follow')
366 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
366 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
367 whitespace=True)
367 whitespace=True)
368 skiprevs = opts.get('skip')
368 skiprevs = opts.get('skip')
369 if skiprevs:
369 if skiprevs:
370 skiprevs = scmutil.revrange(repo, skiprevs)
370 skiprevs = scmutil.revrange(repo, skiprevs)
371
371
372 for abs in ctx.walk(m):
372 for abs in ctx.walk(m):
373 fctx = ctx[abs]
373 fctx = ctx[abs]
374 rootfm.startitem()
374 rootfm.startitem()
375 rootfm.data(abspath=abs, path=m.rel(abs))
375 rootfm.data(abspath=abs, path=m.rel(abs))
376 if not opts.get('text') and fctx.isbinary():
376 if not opts.get('text') and fctx.isbinary():
377 rootfm.plain(_("%s: binary file\n")
377 rootfm.plain(_("%s: binary file\n")
378 % ((pats and m.rel(abs)) or abs))
378 % ((pats and m.rel(abs)) or abs))
379 continue
379 continue
380
380
381 fm = rootfm.nested('lines')
381 fm = rootfm.nested('lines')
382 lines = fctx.annotate(follow=follow, linenumber=linenumber,
382 lines = fctx.annotate(follow=follow, linenumber=linenumber,
383 skiprevs=skiprevs, diffopts=diffopts)
383 skiprevs=skiprevs, diffopts=diffopts)
384 if not lines:
384 if not lines:
385 fm.end()
385 fm.end()
386 continue
386 continue
387 formats = []
387 formats = []
388 pieces = []
388 pieces = []
389
389
390 for f, sep in funcmap:
390 for f, sep in funcmap:
391 l = [f(n) for n, dummy in lines]
391 l = [f(n) for n, dummy in lines]
392 if fm.isplain():
392 if fm.isplain():
393 sizes = [encoding.colwidth(x) for x in l]
393 sizes = [encoding.colwidth(x) for x in l]
394 ml = max(sizes)
394 ml = max(sizes)
395 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
395 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
396 else:
396 else:
397 formats.append(['%s' for x in l])
397 formats.append(['%s' for x in l])
398 pieces.append(l)
398 pieces.append(l)
399
399
400 for f, p, l in zip(zip(*formats), zip(*pieces), lines):
400 for f, p, l in zip(zip(*formats), zip(*pieces), lines):
401 fm.startitem()
401 fm.startitem()
402 fm.write(fields, "".join(f), *p)
402 fm.write(fields, "".join(f), *p)
403 fm.write('line', ": %s", l[1])
403 fm.write('line', ": %s", l[1])
404
404
405 if not lines[-1][1].endswith('\n'):
405 if not lines[-1][1].endswith('\n'):
406 fm.plain('\n')
406 fm.plain('\n')
407 fm.end()
407 fm.end()
408
408
409 rootfm.end()
409 rootfm.end()
410
410
411 @command('archive',
411 @command('archive',
412 [('', 'no-decode', None, _('do not pass files through decoders')),
412 [('', 'no-decode', None, _('do not pass files through decoders')),
413 ('p', 'prefix', '', _('directory prefix for files in archive'),
413 ('p', 'prefix', '', _('directory prefix for files in archive'),
414 _('PREFIX')),
414 _('PREFIX')),
415 ('r', 'rev', '', _('revision to distribute'), _('REV')),
415 ('r', 'rev', '', _('revision to distribute'), _('REV')),
416 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
416 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
417 ] + subrepoopts + walkopts,
417 ] + subrepoopts + walkopts,
418 _('[OPTION]... DEST'))
418 _('[OPTION]... DEST'))
419 def archive(ui, repo, dest, **opts):
419 def archive(ui, repo, dest, **opts):
420 '''create an unversioned archive of a repository revision
420 '''create an unversioned archive of a repository revision
421
421
422 By default, the revision used is the parent of the working
422 By default, the revision used is the parent of the working
423 directory; use -r/--rev to specify a different revision.
423 directory; use -r/--rev to specify a different revision.
424
424
425 The archive type is automatically detected based on file
425 The archive type is automatically detected based on file
426 extension (to override, use -t/--type).
426 extension (to override, use -t/--type).
427
427
428 .. container:: verbose
428 .. container:: verbose
429
429
430 Examples:
430 Examples:
431
431
432 - create a zip file containing the 1.0 release::
432 - create a zip file containing the 1.0 release::
433
433
434 hg archive -r 1.0 project-1.0.zip
434 hg archive -r 1.0 project-1.0.zip
435
435
436 - create a tarball excluding .hg files::
436 - create a tarball excluding .hg files::
437
437
438 hg archive project.tar.gz -X ".hg*"
438 hg archive project.tar.gz -X ".hg*"
439
439
440 Valid types are:
440 Valid types are:
441
441
442 :``files``: a directory full of files (default)
442 :``files``: a directory full of files (default)
443 :``tar``: tar archive, uncompressed
443 :``tar``: tar archive, uncompressed
444 :``tbz2``: tar archive, compressed using bzip2
444 :``tbz2``: tar archive, compressed using bzip2
445 :``tgz``: tar archive, compressed using gzip
445 :``tgz``: tar archive, compressed using gzip
446 :``uzip``: zip archive, uncompressed
446 :``uzip``: zip archive, uncompressed
447 :``zip``: zip archive, compressed using deflate
447 :``zip``: zip archive, compressed using deflate
448
448
449 The exact name of the destination archive or directory is given
449 The exact name of the destination archive or directory is given
450 using a format string; see :hg:`help export` for details.
450 using a format string; see :hg:`help export` for details.
451
451
452 Each member added to an archive file has a directory prefix
452 Each member added to an archive file has a directory prefix
453 prepended. Use -p/--prefix to specify a format string for the
453 prepended. Use -p/--prefix to specify a format string for the
454 prefix. The default is the basename of the archive, with suffixes
454 prefix. The default is the basename of the archive, with suffixes
455 removed.
455 removed.
456
456
457 Returns 0 on success.
457 Returns 0 on success.
458 '''
458 '''
459
459
460 opts = pycompat.byteskwargs(opts)
460 opts = pycompat.byteskwargs(opts)
461 ctx = scmutil.revsingle(repo, opts.get('rev'))
461 ctx = scmutil.revsingle(repo, opts.get('rev'))
462 if not ctx:
462 if not ctx:
463 raise error.Abort(_('no working directory: please specify a revision'))
463 raise error.Abort(_('no working directory: please specify a revision'))
464 node = ctx.node()
464 node = ctx.node()
465 dest = cmdutil.makefilename(repo, dest, node)
465 dest = cmdutil.makefilename(repo, dest, node)
466 if os.path.realpath(dest) == repo.root:
466 if os.path.realpath(dest) == repo.root:
467 raise error.Abort(_('repository root cannot be destination'))
467 raise error.Abort(_('repository root cannot be destination'))
468
468
469 kind = opts.get('type') or archival.guesskind(dest) or 'files'
469 kind = opts.get('type') or archival.guesskind(dest) or 'files'
470 prefix = opts.get('prefix')
470 prefix = opts.get('prefix')
471
471
472 if dest == '-':
472 if dest == '-':
473 if kind == 'files':
473 if kind == 'files':
474 raise error.Abort(_('cannot archive plain files to stdout'))
474 raise error.Abort(_('cannot archive plain files to stdout'))
475 dest = cmdutil.makefileobj(repo, dest)
475 dest = cmdutil.makefileobj(repo, dest)
476 if not prefix:
476 if not prefix:
477 prefix = os.path.basename(repo.root) + '-%h'
477 prefix = os.path.basename(repo.root) + '-%h'
478
478
479 prefix = cmdutil.makefilename(repo, prefix, node)
479 prefix = cmdutil.makefilename(repo, prefix, node)
480 matchfn = scmutil.match(ctx, [], opts)
480 matchfn = scmutil.match(ctx, [], opts)
481 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
481 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
482 matchfn, prefix, subrepos=opts.get('subrepos'))
482 matchfn, prefix, subrepos=opts.get('subrepos'))
483
483
484 @command('backout',
484 @command('backout',
485 [('', 'merge', None, _('merge with old dirstate parent after backout')),
485 [('', 'merge', None, _('merge with old dirstate parent after backout')),
486 ('', 'commit', None,
486 ('', 'commit', None,
487 _('commit if no conflicts were encountered (DEPRECATED)')),
487 _('commit if no conflicts were encountered (DEPRECATED)')),
488 ('', 'no-commit', None, _('do not commit')),
488 ('', 'no-commit', None, _('do not commit')),
489 ('', 'parent', '',
489 ('', 'parent', '',
490 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
490 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
491 ('r', 'rev', '', _('revision to backout'), _('REV')),
491 ('r', 'rev', '', _('revision to backout'), _('REV')),
492 ('e', 'edit', False, _('invoke editor on commit messages')),
492 ('e', 'edit', False, _('invoke editor on commit messages')),
493 ] + mergetoolopts + walkopts + commitopts + commitopts2,
493 ] + mergetoolopts + walkopts + commitopts + commitopts2,
494 _('[OPTION]... [-r] REV'))
494 _('[OPTION]... [-r] REV'))
495 def backout(ui, repo, node=None, rev=None, **opts):
495 def backout(ui, repo, node=None, rev=None, **opts):
496 '''reverse effect of earlier changeset
496 '''reverse effect of earlier changeset
497
497
498 Prepare a new changeset with the effect of REV undone in the
498 Prepare a new changeset with the effect of REV undone in the
499 current working directory. If no conflicts were encountered,
499 current working directory. If no conflicts were encountered,
500 it will be committed immediately.
500 it will be committed immediately.
501
501
502 If REV is the parent of the working directory, then this new changeset
502 If REV is the parent of the working directory, then this new changeset
503 is committed automatically (unless --no-commit is specified).
503 is committed automatically (unless --no-commit is specified).
504
504
505 .. note::
505 .. note::
506
506
507 :hg:`backout` cannot be used to fix either an unwanted or
507 :hg:`backout` cannot be used to fix either an unwanted or
508 incorrect merge.
508 incorrect merge.
509
509
510 .. container:: verbose
510 .. container:: verbose
511
511
512 Examples:
512 Examples:
513
513
514 - Reverse the effect of the parent of the working directory.
514 - Reverse the effect of the parent of the working directory.
515 This backout will be committed immediately::
515 This backout will be committed immediately::
516
516
517 hg backout -r .
517 hg backout -r .
518
518
519 - Reverse the effect of previous bad revision 23::
519 - Reverse the effect of previous bad revision 23::
520
520
521 hg backout -r 23
521 hg backout -r 23
522
522
523 - Reverse the effect of previous bad revision 23 and
523 - Reverse the effect of previous bad revision 23 and
524 leave changes uncommitted::
524 leave changes uncommitted::
525
525
526 hg backout -r 23 --no-commit
526 hg backout -r 23 --no-commit
527 hg commit -m "Backout revision 23"
527 hg commit -m "Backout revision 23"
528
528
529 By default, the pending changeset will have one parent,
529 By default, the pending changeset will have one parent,
530 maintaining a linear history. With --merge, the pending
530 maintaining a linear history. With --merge, the pending
531 changeset will instead have two parents: the old parent of the
531 changeset will instead have two parents: the old parent of the
532 working directory and a new child of REV that simply undoes REV.
532 working directory and a new child of REV that simply undoes REV.
533
533
534 Before version 1.7, the behavior without --merge was equivalent
534 Before version 1.7, the behavior without --merge was equivalent
535 to specifying --merge followed by :hg:`update --clean .` to
535 to specifying --merge followed by :hg:`update --clean .` to
536 cancel the merge and leave the child of REV as a head to be
536 cancel the merge and leave the child of REV as a head to be
537 merged separately.
537 merged separately.
538
538
539 See :hg:`help dates` for a list of formats valid for -d/--date.
539 See :hg:`help dates` for a list of formats valid for -d/--date.
540
540
541 See :hg:`help revert` for a way to restore files to the state
541 See :hg:`help revert` for a way to restore files to the state
542 of another revision.
542 of another revision.
543
543
544 Returns 0 on success, 1 if nothing to backout or there are unresolved
544 Returns 0 on success, 1 if nothing to backout or there are unresolved
545 files.
545 files.
546 '''
546 '''
547 wlock = lock = None
547 wlock = lock = None
548 try:
548 try:
549 wlock = repo.wlock()
549 wlock = repo.wlock()
550 lock = repo.lock()
550 lock = repo.lock()
551 return _dobackout(ui, repo, node, rev, **opts)
551 return _dobackout(ui, repo, node, rev, **opts)
552 finally:
552 finally:
553 release(lock, wlock)
553 release(lock, wlock)
554
554
555 def _dobackout(ui, repo, node=None, rev=None, **opts):
555 def _dobackout(ui, repo, node=None, rev=None, **opts):
556 opts = pycompat.byteskwargs(opts)
556 opts = pycompat.byteskwargs(opts)
557 if opts.get('commit') and opts.get('no_commit'):
557 if opts.get('commit') and opts.get('no_commit'):
558 raise error.Abort(_("cannot use --commit with --no-commit"))
558 raise error.Abort(_("cannot use --commit with --no-commit"))
559 if opts.get('merge') and opts.get('no_commit'):
559 if opts.get('merge') and opts.get('no_commit'):
560 raise error.Abort(_("cannot use --merge with --no-commit"))
560 raise error.Abort(_("cannot use --merge with --no-commit"))
561
561
562 if rev and node:
562 if rev and node:
563 raise error.Abort(_("please specify just one revision"))
563 raise error.Abort(_("please specify just one revision"))
564
564
565 if not rev:
565 if not rev:
566 rev = node
566 rev = node
567
567
568 if not rev:
568 if not rev:
569 raise error.Abort(_("please specify a revision to backout"))
569 raise error.Abort(_("please specify a revision to backout"))
570
570
571 date = opts.get('date')
571 date = opts.get('date')
572 if date:
572 if date:
573 opts['date'] = util.parsedate(date)
573 opts['date'] = util.parsedate(date)
574
574
575 cmdutil.checkunfinished(repo)
575 cmdutil.checkunfinished(repo)
576 cmdutil.bailifchanged(repo)
576 cmdutil.bailifchanged(repo)
577 node = scmutil.revsingle(repo, rev).node()
577 node = scmutil.revsingle(repo, rev).node()
578
578
579 op1, op2 = repo.dirstate.parents()
579 op1, op2 = repo.dirstate.parents()
580 if not repo.changelog.isancestor(node, op1):
580 if not repo.changelog.isancestor(node, op1):
581 raise error.Abort(_('cannot backout change that is not an ancestor'))
581 raise error.Abort(_('cannot backout change that is not an ancestor'))
582
582
583 p1, p2 = repo.changelog.parents(node)
583 p1, p2 = repo.changelog.parents(node)
584 if p1 == nullid:
584 if p1 == nullid:
585 raise error.Abort(_('cannot backout a change with no parents'))
585 raise error.Abort(_('cannot backout a change with no parents'))
586 if p2 != nullid:
586 if p2 != nullid:
587 if not opts.get('parent'):
587 if not opts.get('parent'):
588 raise error.Abort(_('cannot backout a merge changeset'))
588 raise error.Abort(_('cannot backout a merge changeset'))
589 p = repo.lookup(opts['parent'])
589 p = repo.lookup(opts['parent'])
590 if p not in (p1, p2):
590 if p not in (p1, p2):
591 raise error.Abort(_('%s is not a parent of %s') %
591 raise error.Abort(_('%s is not a parent of %s') %
592 (short(p), short(node)))
592 (short(p), short(node)))
593 parent = p
593 parent = p
594 else:
594 else:
595 if opts.get('parent'):
595 if opts.get('parent'):
596 raise error.Abort(_('cannot use --parent on non-merge changeset'))
596 raise error.Abort(_('cannot use --parent on non-merge changeset'))
597 parent = p1
597 parent = p1
598
598
599 # the backout should appear on the same branch
599 # the backout should appear on the same branch
600 branch = repo.dirstate.branch()
600 branch = repo.dirstate.branch()
601 bheads = repo.branchheads(branch)
601 bheads = repo.branchheads(branch)
602 rctx = scmutil.revsingle(repo, hex(parent))
602 rctx = scmutil.revsingle(repo, hex(parent))
603 if not opts.get('merge') and op1 != node:
603 if not opts.get('merge') and op1 != node:
604 dsguard = dirstateguard.dirstateguard(repo, 'backout')
604 dsguard = dirstateguard.dirstateguard(repo, 'backout')
605 try:
605 try:
606 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
606 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
607 'backout')
607 'backout')
608 stats = mergemod.update(repo, parent, True, True, node, False)
608 stats = mergemod.update(repo, parent, True, True, node, False)
609 repo.setparents(op1, op2)
609 repo.setparents(op1, op2)
610 dsguard.close()
610 dsguard.close()
611 hg._showstats(repo, stats)
611 hg._showstats(repo, stats)
612 if stats[3]:
612 if stats[3]:
613 repo.ui.status(_("use 'hg resolve' to retry unresolved "
613 repo.ui.status(_("use 'hg resolve' to retry unresolved "
614 "file merges\n"))
614 "file merges\n"))
615 return 1
615 return 1
616 finally:
616 finally:
617 ui.setconfig('ui', 'forcemerge', '', '')
617 ui.setconfig('ui', 'forcemerge', '', '')
618 lockmod.release(dsguard)
618 lockmod.release(dsguard)
619 else:
619 else:
620 hg.clean(repo, node, show_stats=False)
620 hg.clean(repo, node, show_stats=False)
621 repo.dirstate.setbranch(branch)
621 repo.dirstate.setbranch(branch)
622 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
622 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
623
623
624 if opts.get('no_commit'):
624 if opts.get('no_commit'):
625 msg = _("changeset %s backed out, "
625 msg = _("changeset %s backed out, "
626 "don't forget to commit.\n")
626 "don't forget to commit.\n")
627 ui.status(msg % short(node))
627 ui.status(msg % short(node))
628 return 0
628 return 0
629
629
630 def commitfunc(ui, repo, message, match, opts):
630 def commitfunc(ui, repo, message, match, opts):
631 editform = 'backout'
631 editform = 'backout'
632 e = cmdutil.getcommiteditor(editform=editform,
632 e = cmdutil.getcommiteditor(editform=editform,
633 **pycompat.strkwargs(opts))
633 **pycompat.strkwargs(opts))
634 if not message:
634 if not message:
635 # we don't translate commit messages
635 # we don't translate commit messages
636 message = "Backed out changeset %s" % short(node)
636 message = "Backed out changeset %s" % short(node)
637 e = cmdutil.getcommiteditor(edit=True, editform=editform)
637 e = cmdutil.getcommiteditor(edit=True, editform=editform)
638 return repo.commit(message, opts.get('user'), opts.get('date'),
638 return repo.commit(message, opts.get('user'), opts.get('date'),
639 match, editor=e)
639 match, editor=e)
640 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
640 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
641 if not newnode:
641 if not newnode:
642 ui.status(_("nothing changed\n"))
642 ui.status(_("nothing changed\n"))
643 return 1
643 return 1
644 cmdutil.commitstatus(repo, newnode, branch, bheads)
644 cmdutil.commitstatus(repo, newnode, branch, bheads)
645
645
646 def nice(node):
646 def nice(node):
647 return '%d:%s' % (repo.changelog.rev(node), short(node))
647 return '%d:%s' % (repo.changelog.rev(node), short(node))
648 ui.status(_('changeset %s backs out changeset %s\n') %
648 ui.status(_('changeset %s backs out changeset %s\n') %
649 (nice(repo.changelog.tip()), nice(node)))
649 (nice(repo.changelog.tip()), nice(node)))
650 if opts.get('merge') and op1 != node:
650 if opts.get('merge') and op1 != node:
651 hg.clean(repo, op1, show_stats=False)
651 hg.clean(repo, op1, show_stats=False)
652 ui.status(_('merging with changeset %s\n')
652 ui.status(_('merging with changeset %s\n')
653 % nice(repo.changelog.tip()))
653 % nice(repo.changelog.tip()))
654 try:
654 try:
655 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
655 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
656 'backout')
656 'backout')
657 return hg.merge(repo, hex(repo.changelog.tip()))
657 return hg.merge(repo, hex(repo.changelog.tip()))
658 finally:
658 finally:
659 ui.setconfig('ui', 'forcemerge', '', '')
659 ui.setconfig('ui', 'forcemerge', '', '')
660 return 0
660 return 0
661
661
662 @command('bisect',
662 @command('bisect',
663 [('r', 'reset', False, _('reset bisect state')),
663 [('r', 'reset', False, _('reset bisect state')),
664 ('g', 'good', False, _('mark changeset good')),
664 ('g', 'good', False, _('mark changeset good')),
665 ('b', 'bad', False, _('mark changeset bad')),
665 ('b', 'bad', False, _('mark changeset bad')),
666 ('s', 'skip', False, _('skip testing changeset')),
666 ('s', 'skip', False, _('skip testing changeset')),
667 ('e', 'extend', False, _('extend the bisect range')),
667 ('e', 'extend', False, _('extend the bisect range')),
668 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
668 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
669 ('U', 'noupdate', False, _('do not update to target'))],
669 ('U', 'noupdate', False, _('do not update to target'))],
670 _("[-gbsr] [-U] [-c CMD] [REV]"))
670 _("[-gbsr] [-U] [-c CMD] [REV]"))
671 def bisect(ui, repo, rev=None, extra=None, command=None,
671 def bisect(ui, repo, rev=None, extra=None, command=None,
672 reset=None, good=None, bad=None, skip=None, extend=None,
672 reset=None, good=None, bad=None, skip=None, extend=None,
673 noupdate=None):
673 noupdate=None):
674 """subdivision search of changesets
674 """subdivision search of changesets
675
675
676 This command helps to find changesets which introduce problems. To
676 This command helps to find changesets which introduce problems. To
677 use, mark the earliest changeset you know exhibits the problem as
677 use, mark the earliest changeset you know exhibits the problem as
678 bad, then mark the latest changeset which is free from the problem
678 bad, then mark the latest changeset which is free from the problem
679 as good. Bisect will update your working directory to a revision
679 as good. Bisect will update your working directory to a revision
680 for testing (unless the -U/--noupdate option is specified). Once
680 for testing (unless the -U/--noupdate option is specified). Once
681 you have performed tests, mark the working directory as good or
681 you have performed tests, mark the working directory as good or
682 bad, and bisect will either update to another candidate changeset
682 bad, and bisect will either update to another candidate changeset
683 or announce that it has found the bad revision.
683 or announce that it has found the bad revision.
684
684
685 As a shortcut, you can also use the revision argument to mark a
685 As a shortcut, you can also use the revision argument to mark a
686 revision as good or bad without checking it out first.
686 revision as good or bad without checking it out first.
687
687
688 If you supply a command, it will be used for automatic bisection.
688 If you supply a command, it will be used for automatic bisection.
689 The environment variable HG_NODE will contain the ID of the
689 The environment variable HG_NODE will contain the ID of the
690 changeset being tested. The exit status of the command will be
690 changeset being tested. The exit status of the command will be
691 used to mark revisions as good or bad: status 0 means good, 125
691 used to mark revisions as good or bad: status 0 means good, 125
692 means to skip the revision, 127 (command not found) will abort the
692 means to skip the revision, 127 (command not found) will abort the
693 bisection, and any other non-zero exit status means the revision
693 bisection, and any other non-zero exit status means the revision
694 is bad.
694 is bad.
695
695
696 .. container:: verbose
696 .. container:: verbose
697
697
698 Some examples:
698 Some examples:
699
699
700 - start a bisection with known bad revision 34, and good revision 12::
700 - start a bisection with known bad revision 34, and good revision 12::
701
701
702 hg bisect --bad 34
702 hg bisect --bad 34
703 hg bisect --good 12
703 hg bisect --good 12
704
704
705 - advance the current bisection by marking current revision as good or
705 - advance the current bisection by marking current revision as good or
706 bad::
706 bad::
707
707
708 hg bisect --good
708 hg bisect --good
709 hg bisect --bad
709 hg bisect --bad
710
710
711 - mark the current revision, or a known revision, to be skipped (e.g. if
711 - mark the current revision, or a known revision, to be skipped (e.g. if
712 that revision is not usable because of another issue)::
712 that revision is not usable because of another issue)::
713
713
714 hg bisect --skip
714 hg bisect --skip
715 hg bisect --skip 23
715 hg bisect --skip 23
716
716
717 - skip all revisions that do not touch directories ``foo`` or ``bar``::
717 - skip all revisions that do not touch directories ``foo`` or ``bar``::
718
718
719 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
719 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
720
720
721 - forget the current bisection::
721 - forget the current bisection::
722
722
723 hg bisect --reset
723 hg bisect --reset
724
724
725 - use 'make && make tests' to automatically find the first broken
725 - use 'make && make tests' to automatically find the first broken
726 revision::
726 revision::
727
727
728 hg bisect --reset
728 hg bisect --reset
729 hg bisect --bad 34
729 hg bisect --bad 34
730 hg bisect --good 12
730 hg bisect --good 12
731 hg bisect --command "make && make tests"
731 hg bisect --command "make && make tests"
732
732
733 - see all changesets whose states are already known in the current
733 - see all changesets whose states are already known in the current
734 bisection::
734 bisection::
735
735
736 hg log -r "bisect(pruned)"
736 hg log -r "bisect(pruned)"
737
737
738 - see the changeset currently being bisected (especially useful
738 - see the changeset currently being bisected (especially useful
739 if running with -U/--noupdate)::
739 if running with -U/--noupdate)::
740
740
741 hg log -r "bisect(current)"
741 hg log -r "bisect(current)"
742
742
743 - see all changesets that took part in the current bisection::
743 - see all changesets that took part in the current bisection::
744
744
745 hg log -r "bisect(range)"
745 hg log -r "bisect(range)"
746
746
747 - you can even get a nice graph::
747 - you can even get a nice graph::
748
748
749 hg log --graph -r "bisect(range)"
749 hg log --graph -r "bisect(range)"
750
750
751 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
751 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
752
752
753 Returns 0 on success.
753 Returns 0 on success.
754 """
754 """
755 # backward compatibility
755 # backward compatibility
756 if rev in "good bad reset init".split():
756 if rev in "good bad reset init".split():
757 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
757 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
758 cmd, rev, extra = rev, extra, None
758 cmd, rev, extra = rev, extra, None
759 if cmd == "good":
759 if cmd == "good":
760 good = True
760 good = True
761 elif cmd == "bad":
761 elif cmd == "bad":
762 bad = True
762 bad = True
763 else:
763 else:
764 reset = True
764 reset = True
765 elif extra:
765 elif extra:
766 raise error.Abort(_('incompatible arguments'))
766 raise error.Abort(_('incompatible arguments'))
767
767
768 incompatibles = {
768 incompatibles = {
769 '--bad': bad,
769 '--bad': bad,
770 '--command': bool(command),
770 '--command': bool(command),
771 '--extend': extend,
771 '--extend': extend,
772 '--good': good,
772 '--good': good,
773 '--reset': reset,
773 '--reset': reset,
774 '--skip': skip,
774 '--skip': skip,
775 }
775 }
776
776
777 enabled = [x for x in incompatibles if incompatibles[x]]
777 enabled = [x for x in incompatibles if incompatibles[x]]
778
778
779 if len(enabled) > 1:
779 if len(enabled) > 1:
780 raise error.Abort(_('%s and %s are incompatible') %
780 raise error.Abort(_('%s and %s are incompatible') %
781 tuple(sorted(enabled)[0:2]))
781 tuple(sorted(enabled)[0:2]))
782
782
783 if reset:
783 if reset:
784 hbisect.resetstate(repo)
784 hbisect.resetstate(repo)
785 return
785 return
786
786
787 state = hbisect.load_state(repo)
787 state = hbisect.load_state(repo)
788
788
789 # update state
789 # update state
790 if good or bad or skip:
790 if good or bad or skip:
791 if rev:
791 if rev:
792 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
792 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
793 else:
793 else:
794 nodes = [repo.lookup('.')]
794 nodes = [repo.lookup('.')]
795 if good:
795 if good:
796 state['good'] += nodes
796 state['good'] += nodes
797 elif bad:
797 elif bad:
798 state['bad'] += nodes
798 state['bad'] += nodes
799 elif skip:
799 elif skip:
800 state['skip'] += nodes
800 state['skip'] += nodes
801 hbisect.save_state(repo, state)
801 hbisect.save_state(repo, state)
802 if not (state['good'] and state['bad']):
802 if not (state['good'] and state['bad']):
803 return
803 return
804
804
805 def mayupdate(repo, node, show_stats=True):
805 def mayupdate(repo, node, show_stats=True):
806 """common used update sequence"""
806 """common used update sequence"""
807 if noupdate:
807 if noupdate:
808 return
808 return
809 cmdutil.checkunfinished(repo)
809 cmdutil.checkunfinished(repo)
810 cmdutil.bailifchanged(repo)
810 cmdutil.bailifchanged(repo)
811 return hg.clean(repo, node, show_stats=show_stats)
811 return hg.clean(repo, node, show_stats=show_stats)
812
812
813 displayer = cmdutil.show_changeset(ui, repo, {})
813 displayer = cmdutil.show_changeset(ui, repo, {})
814
814
815 if command:
815 if command:
816 changesets = 1
816 changesets = 1
817 if noupdate:
817 if noupdate:
818 try:
818 try:
819 node = state['current'][0]
819 node = state['current'][0]
820 except LookupError:
820 except LookupError:
821 raise error.Abort(_('current bisect revision is unknown - '
821 raise error.Abort(_('current bisect revision is unknown - '
822 'start a new bisect to fix'))
822 'start a new bisect to fix'))
823 else:
823 else:
824 node, p2 = repo.dirstate.parents()
824 node, p2 = repo.dirstate.parents()
825 if p2 != nullid:
825 if p2 != nullid:
826 raise error.Abort(_('current bisect revision is a merge'))
826 raise error.Abort(_('current bisect revision is a merge'))
827 if rev:
827 if rev:
828 node = repo[scmutil.revsingle(repo, rev, node)].node()
828 node = repo[scmutil.revsingle(repo, rev, node)].node()
829 try:
829 try:
830 while changesets:
830 while changesets:
831 # update state
831 # update state
832 state['current'] = [node]
832 state['current'] = [node]
833 hbisect.save_state(repo, state)
833 hbisect.save_state(repo, state)
834 status = ui.system(command, environ={'HG_NODE': hex(node)},
834 status = ui.system(command, environ={'HG_NODE': hex(node)},
835 blockedtag='bisect_check')
835 blockedtag='bisect_check')
836 if status == 125:
836 if status == 125:
837 transition = "skip"
837 transition = "skip"
838 elif status == 0:
838 elif status == 0:
839 transition = "good"
839 transition = "good"
840 # status < 0 means process was killed
840 # status < 0 means process was killed
841 elif status == 127:
841 elif status == 127:
842 raise error.Abort(_("failed to execute %s") % command)
842 raise error.Abort(_("failed to execute %s") % command)
843 elif status < 0:
843 elif status < 0:
844 raise error.Abort(_("%s killed") % command)
844 raise error.Abort(_("%s killed") % command)
845 else:
845 else:
846 transition = "bad"
846 transition = "bad"
847 state[transition].append(node)
847 state[transition].append(node)
848 ctx = repo[node]
848 ctx = repo[node]
849 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
849 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
850 hbisect.checkstate(state)
850 hbisect.checkstate(state)
851 # bisect
851 # bisect
852 nodes, changesets, bgood = hbisect.bisect(repo.changelog, state)
852 nodes, changesets, bgood = hbisect.bisect(repo.changelog, state)
853 # update to next check
853 # update to next check
854 node = nodes[0]
854 node = nodes[0]
855 mayupdate(repo, node, show_stats=False)
855 mayupdate(repo, node, show_stats=False)
856 finally:
856 finally:
857 state['current'] = [node]
857 state['current'] = [node]
858 hbisect.save_state(repo, state)
858 hbisect.save_state(repo, state)
859 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
859 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
860 return
860 return
861
861
862 hbisect.checkstate(state)
862 hbisect.checkstate(state)
863
863
864 # actually bisect
864 # actually bisect
865 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
865 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
866 if extend:
866 if extend:
867 if not changesets:
867 if not changesets:
868 extendnode = hbisect.extendrange(repo, state, nodes, good)
868 extendnode = hbisect.extendrange(repo, state, nodes, good)
869 if extendnode is not None:
869 if extendnode is not None:
870 ui.write(_("Extending search to changeset %d:%s\n")
870 ui.write(_("Extending search to changeset %d:%s\n")
871 % (extendnode.rev(), extendnode))
871 % (extendnode.rev(), extendnode))
872 state['current'] = [extendnode.node()]
872 state['current'] = [extendnode.node()]
873 hbisect.save_state(repo, state)
873 hbisect.save_state(repo, state)
874 return mayupdate(repo, extendnode.node())
874 return mayupdate(repo, extendnode.node())
875 raise error.Abort(_("nothing to extend"))
875 raise error.Abort(_("nothing to extend"))
876
876
877 if changesets == 0:
877 if changesets == 0:
878 hbisect.printresult(ui, repo, state, displayer, nodes, good)
878 hbisect.printresult(ui, repo, state, displayer, nodes, good)
879 else:
879 else:
880 assert len(nodes) == 1 # only a single node can be tested next
880 assert len(nodes) == 1 # only a single node can be tested next
881 node = nodes[0]
881 node = nodes[0]
882 # compute the approximate number of remaining tests
882 # compute the approximate number of remaining tests
883 tests, size = 0, 2
883 tests, size = 0, 2
884 while size <= changesets:
884 while size <= changesets:
885 tests, size = tests + 1, size * 2
885 tests, size = tests + 1, size * 2
886 rev = repo.changelog.rev(node)
886 rev = repo.changelog.rev(node)
887 ui.write(_("Testing changeset %d:%s "
887 ui.write(_("Testing changeset %d:%s "
888 "(%d changesets remaining, ~%d tests)\n")
888 "(%d changesets remaining, ~%d tests)\n")
889 % (rev, short(node), changesets, tests))
889 % (rev, short(node), changesets, tests))
890 state['current'] = [node]
890 state['current'] = [node]
891 hbisect.save_state(repo, state)
891 hbisect.save_state(repo, state)
892 return mayupdate(repo, node)
892 return mayupdate(repo, node)
893
893
894 @command('bookmarks|bookmark',
894 @command('bookmarks|bookmark',
895 [('f', 'force', False, _('force')),
895 [('f', 'force', False, _('force')),
896 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
896 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
897 ('d', 'delete', False, _('delete a given bookmark')),
897 ('d', 'delete', False, _('delete a given bookmark')),
898 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
898 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
899 ('i', 'inactive', False, _('mark a bookmark inactive')),
899 ('i', 'inactive', False, _('mark a bookmark inactive')),
900 ] + formatteropts,
900 ] + formatteropts,
901 _('hg bookmarks [OPTIONS]... [NAME]...'))
901 _('hg bookmarks [OPTIONS]... [NAME]...'))
902 def bookmark(ui, repo, *names, **opts):
902 def bookmark(ui, repo, *names, **opts):
903 '''create a new bookmark or list existing bookmarks
903 '''create a new bookmark or list existing bookmarks
904
904
905 Bookmarks are labels on changesets to help track lines of development.
905 Bookmarks are labels on changesets to help track lines of development.
906 Bookmarks are unversioned and can be moved, renamed and deleted.
906 Bookmarks are unversioned and can be moved, renamed and deleted.
907 Deleting or moving a bookmark has no effect on the associated changesets.
907 Deleting or moving a bookmark has no effect on the associated changesets.
908
908
909 Creating or updating to a bookmark causes it to be marked as 'active'.
909 Creating or updating to a bookmark causes it to be marked as 'active'.
910 The active bookmark is indicated with a '*'.
910 The active bookmark is indicated with a '*'.
911 When a commit is made, the active bookmark will advance to the new commit.
911 When a commit is made, the active bookmark will advance to the new commit.
912 A plain :hg:`update` will also advance an active bookmark, if possible.
912 A plain :hg:`update` will also advance an active bookmark, if possible.
913 Updating away from a bookmark will cause it to be deactivated.
913 Updating away from a bookmark will cause it to be deactivated.
914
914
915 Bookmarks can be pushed and pulled between repositories (see
915 Bookmarks can be pushed and pulled between repositories (see
916 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
916 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
917 diverged, a new 'divergent bookmark' of the form 'name@path' will
917 diverged, a new 'divergent bookmark' of the form 'name@path' will
918 be created. Using :hg:`merge` will resolve the divergence.
918 be created. Using :hg:`merge` will resolve the divergence.
919
919
920 A bookmark named '@' has the special property that :hg:`clone` will
920 A bookmark named '@' has the special property that :hg:`clone` will
921 check it out by default if it exists.
921 check it out by default if it exists.
922
922
923 .. container:: verbose
923 .. container:: verbose
924
924
925 Examples:
925 Examples:
926
926
927 - create an active bookmark for a new line of development::
927 - create an active bookmark for a new line of development::
928
928
929 hg book new-feature
929 hg book new-feature
930
930
931 - create an inactive bookmark as a place marker::
931 - create an inactive bookmark as a place marker::
932
932
933 hg book -i reviewed
933 hg book -i reviewed
934
934
935 - create an inactive bookmark on another changeset::
935 - create an inactive bookmark on another changeset::
936
936
937 hg book -r .^ tested
937 hg book -r .^ tested
938
938
939 - rename bookmark turkey to dinner::
939 - rename bookmark turkey to dinner::
940
940
941 hg book -m turkey dinner
941 hg book -m turkey dinner
942
942
943 - move the '@' bookmark from another branch::
943 - move the '@' bookmark from another branch::
944
944
945 hg book -f @
945 hg book -f @
946 '''
946 '''
947 force = opts.get(r'force')
947 force = opts.get(r'force')
948 rev = opts.get(r'rev')
948 rev = opts.get(r'rev')
949 delete = opts.get(r'delete')
949 delete = opts.get(r'delete')
950 rename = opts.get(r'rename')
950 rename = opts.get(r'rename')
951 inactive = opts.get(r'inactive')
951 inactive = opts.get(r'inactive')
952
952
953 if delete and rename:
953 if delete and rename:
954 raise error.Abort(_("--delete and --rename are incompatible"))
954 raise error.Abort(_("--delete and --rename are incompatible"))
955 if delete and rev:
955 if delete and rev:
956 raise error.Abort(_("--rev is incompatible with --delete"))
956 raise error.Abort(_("--rev is incompatible with --delete"))
957 if rename and rev:
957 if rename and rev:
958 raise error.Abort(_("--rev is incompatible with --rename"))
958 raise error.Abort(_("--rev is incompatible with --rename"))
959 if not names and (delete or rev):
959 if not names and (delete or rev):
960 raise error.Abort(_("bookmark name required"))
960 raise error.Abort(_("bookmark name required"))
961
961
962 if delete or rename or names or inactive:
962 if delete or rename or names or inactive:
963 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
963 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
964 if delete:
964 if delete:
965 bookmarks.delete(repo, tr, names)
965 bookmarks.delete(repo, tr, names)
966 elif rename:
966 elif rename:
967 if not names:
967 if not names:
968 raise error.Abort(_("new bookmark name required"))
968 raise error.Abort(_("new bookmark name required"))
969 elif len(names) > 1:
969 elif len(names) > 1:
970 raise error.Abort(_("only one new bookmark name allowed"))
970 raise error.Abort(_("only one new bookmark name allowed"))
971 bookmarks.rename(repo, tr, rename, names[0], force, inactive)
971 bookmarks.rename(repo, tr, rename, names[0], force, inactive)
972 elif names:
972 elif names:
973 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
973 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
974 elif inactive:
974 elif inactive:
975 if len(repo._bookmarks) == 0:
975 if len(repo._bookmarks) == 0:
976 ui.status(_("no bookmarks set\n"))
976 ui.status(_("no bookmarks set\n"))
977 elif not repo._activebookmark:
977 elif not repo._activebookmark:
978 ui.status(_("no active bookmark\n"))
978 ui.status(_("no active bookmark\n"))
979 else:
979 else:
980 bookmarks.deactivate(repo)
980 bookmarks.deactivate(repo)
981 else: # show bookmarks
981 else: # show bookmarks
982 bookmarks.printbookmarks(ui, repo, **opts)
982 bookmarks.printbookmarks(ui, repo, **opts)
983
983
984 @command('branch',
984 @command('branch',
985 [('f', 'force', None,
985 [('f', 'force', None,
986 _('set branch name even if it shadows an existing branch')),
986 _('set branch name even if it shadows an existing branch')),
987 ('C', 'clean', None, _('reset branch name to parent branch name'))],
987 ('C', 'clean', None, _('reset branch name to parent branch name'))],
988 _('[-fC] [NAME]'))
988 _('[-fC] [NAME]'))
989 def branch(ui, repo, label=None, **opts):
989 def branch(ui, repo, label=None, **opts):
990 """set or show the current branch name
990 """set or show the current branch name
991
991
992 .. note::
992 .. note::
993
993
994 Branch names are permanent and global. Use :hg:`bookmark` to create a
994 Branch names are permanent and global. Use :hg:`bookmark` to create a
995 light-weight bookmark instead. See :hg:`help glossary` for more
995 light-weight bookmark instead. See :hg:`help glossary` for more
996 information about named branches and bookmarks.
996 information about named branches and bookmarks.
997
997
998 With no argument, show the current branch name. With one argument,
998 With no argument, show the current branch name. With one argument,
999 set the working directory branch name (the branch will not exist
999 set the working directory branch name (the branch will not exist
1000 in the repository until the next commit). Standard practice
1000 in the repository until the next commit). Standard practice
1001 recommends that primary development take place on the 'default'
1001 recommends that primary development take place on the 'default'
1002 branch.
1002 branch.
1003
1003
1004 Unless -f/--force is specified, branch will not let you set a
1004 Unless -f/--force is specified, branch will not let you set a
1005 branch name that already exists.
1005 branch name that already exists.
1006
1006
1007 Use -C/--clean to reset the working directory branch to that of
1007 Use -C/--clean to reset the working directory branch to that of
1008 the parent of the working directory, negating a previous branch
1008 the parent of the working directory, negating a previous branch
1009 change.
1009 change.
1010
1010
1011 Use the command :hg:`update` to switch to an existing branch. Use
1011 Use the command :hg:`update` to switch to an existing branch. Use
1012 :hg:`commit --close-branch` to mark this branch head as closed.
1012 :hg:`commit --close-branch` to mark this branch head as closed.
1013 When all heads of a branch are closed, the branch will be
1013 When all heads of a branch are closed, the branch will be
1014 considered closed.
1014 considered closed.
1015
1015
1016 Returns 0 on success.
1016 Returns 0 on success.
1017 """
1017 """
1018 opts = pycompat.byteskwargs(opts)
1018 opts = pycompat.byteskwargs(opts)
1019 if label:
1019 if label:
1020 label = label.strip()
1020 label = label.strip()
1021
1021
1022 if not opts.get('clean') and not label:
1022 if not opts.get('clean') and not label:
1023 ui.write("%s\n" % repo.dirstate.branch())
1023 ui.write("%s\n" % repo.dirstate.branch())
1024 return
1024 return
1025
1025
1026 with repo.wlock():
1026 with repo.wlock():
1027 if opts.get('clean'):
1027 if opts.get('clean'):
1028 label = repo[None].p1().branch()
1028 label = repo[None].p1().branch()
1029 repo.dirstate.setbranch(label)
1029 repo.dirstate.setbranch(label)
1030 ui.status(_('reset working directory to branch %s\n') % label)
1030 ui.status(_('reset working directory to branch %s\n') % label)
1031 elif label:
1031 elif label:
1032 if not opts.get('force') and label in repo.branchmap():
1032 if not opts.get('force') and label in repo.branchmap():
1033 if label not in [p.branch() for p in repo[None].parents()]:
1033 if label not in [p.branch() for p in repo[None].parents()]:
1034 raise error.Abort(_('a branch of the same name already'
1034 raise error.Abort(_('a branch of the same name already'
1035 ' exists'),
1035 ' exists'),
1036 # i18n: "it" refers to an existing branch
1036 # i18n: "it" refers to an existing branch
1037 hint=_("use 'hg update' to switch to it"))
1037 hint=_("use 'hg update' to switch to it"))
1038 scmutil.checknewlabel(repo, label, 'branch')
1038 scmutil.checknewlabel(repo, label, 'branch')
1039 repo.dirstate.setbranch(label)
1039 repo.dirstate.setbranch(label)
1040 ui.status(_('marked working directory as branch %s\n') % label)
1040 ui.status(_('marked working directory as branch %s\n') % label)
1041
1041
1042 # find any open named branches aside from default
1042 # find any open named branches aside from default
1043 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1043 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1044 if n != "default" and not c]
1044 if n != "default" and not c]
1045 if not others:
1045 if not others:
1046 ui.status(_('(branches are permanent and global, '
1046 ui.status(_('(branches are permanent and global, '
1047 'did you want a bookmark?)\n'))
1047 'did you want a bookmark?)\n'))
1048
1048
1049 @command('branches',
1049 @command('branches',
1050 [('a', 'active', False,
1050 [('a', 'active', False,
1051 _('show only branches that have unmerged heads (DEPRECATED)')),
1051 _('show only branches that have unmerged heads (DEPRECATED)')),
1052 ('c', 'closed', False, _('show normal and closed branches')),
1052 ('c', 'closed', False, _('show normal and closed branches')),
1053 ] + formatteropts,
1053 ] + formatteropts,
1054 _('[-c]'))
1054 _('[-c]'))
1055 def branches(ui, repo, active=False, closed=False, **opts):
1055 def branches(ui, repo, active=False, closed=False, **opts):
1056 """list repository named branches
1056 """list repository named branches
1057
1057
1058 List the repository's named branches, indicating which ones are
1058 List the repository's named branches, indicating which ones are
1059 inactive. If -c/--closed is specified, also list branches which have
1059 inactive. If -c/--closed is specified, also list branches which have
1060 been marked closed (see :hg:`commit --close-branch`).
1060 been marked closed (see :hg:`commit --close-branch`).
1061
1061
1062 Use the command :hg:`update` to switch to an existing branch.
1062 Use the command :hg:`update` to switch to an existing branch.
1063
1063
1064 Returns 0.
1064 Returns 0.
1065 """
1065 """
1066
1066
1067 opts = pycompat.byteskwargs(opts)
1067 opts = pycompat.byteskwargs(opts)
1068 ui.pager('branches')
1068 ui.pager('branches')
1069 fm = ui.formatter('branches', opts)
1069 fm = ui.formatter('branches', opts)
1070 hexfunc = fm.hexfunc
1070 hexfunc = fm.hexfunc
1071
1071
1072 allheads = set(repo.heads())
1072 allheads = set(repo.heads())
1073 branches = []
1073 branches = []
1074 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1074 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1075 isactive = not isclosed and bool(set(heads) & allheads)
1075 isactive = not isclosed and bool(set(heads) & allheads)
1076 branches.append((tag, repo[tip], isactive, not isclosed))
1076 branches.append((tag, repo[tip], isactive, not isclosed))
1077 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1077 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1078 reverse=True)
1078 reverse=True)
1079
1079
1080 for tag, ctx, isactive, isopen in branches:
1080 for tag, ctx, isactive, isopen in branches:
1081 if active and not isactive:
1081 if active and not isactive:
1082 continue
1082 continue
1083 if isactive:
1083 if isactive:
1084 label = 'branches.active'
1084 label = 'branches.active'
1085 notice = ''
1085 notice = ''
1086 elif not isopen:
1086 elif not isopen:
1087 if not closed:
1087 if not closed:
1088 continue
1088 continue
1089 label = 'branches.closed'
1089 label = 'branches.closed'
1090 notice = _(' (closed)')
1090 notice = _(' (closed)')
1091 else:
1091 else:
1092 label = 'branches.inactive'
1092 label = 'branches.inactive'
1093 notice = _(' (inactive)')
1093 notice = _(' (inactive)')
1094 current = (tag == repo.dirstate.branch())
1094 current = (tag == repo.dirstate.branch())
1095 if current:
1095 if current:
1096 label = 'branches.current'
1096 label = 'branches.current'
1097
1097
1098 fm.startitem()
1098 fm.startitem()
1099 fm.write('branch', '%s', tag, label=label)
1099 fm.write('branch', '%s', tag, label=label)
1100 rev = ctx.rev()
1100 rev = ctx.rev()
1101 padsize = max(31 - len(str(rev)) - encoding.colwidth(tag), 0)
1101 padsize = max(31 - len(str(rev)) - encoding.colwidth(tag), 0)
1102 fmt = ' ' * padsize + ' %d:%s'
1102 fmt = ' ' * padsize + ' %d:%s'
1103 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1103 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1104 label='log.changeset changeset.%s' % ctx.phasestr())
1104 label='log.changeset changeset.%s' % ctx.phasestr())
1105 fm.context(ctx=ctx)
1105 fm.context(ctx=ctx)
1106 fm.data(active=isactive, closed=not isopen, current=current)
1106 fm.data(active=isactive, closed=not isopen, current=current)
1107 if not ui.quiet:
1107 if not ui.quiet:
1108 fm.plain(notice)
1108 fm.plain(notice)
1109 fm.plain('\n')
1109 fm.plain('\n')
1110 fm.end()
1110 fm.end()
1111
1111
1112 @command('bundle',
1112 @command('bundle',
1113 [('f', 'force', None, _('run even when the destination is unrelated')),
1113 [('f', 'force', None, _('run even when the destination is unrelated')),
1114 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1114 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1115 _('REV')),
1115 _('REV')),
1116 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1116 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1117 _('BRANCH')),
1117 _('BRANCH')),
1118 ('', 'base', [],
1118 ('', 'base', [],
1119 _('a base changeset assumed to be available at the destination'),
1119 _('a base changeset assumed to be available at the destination'),
1120 _('REV')),
1120 _('REV')),
1121 ('a', 'all', None, _('bundle all changesets in the repository')),
1121 ('a', 'all', None, _('bundle all changesets in the repository')),
1122 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1122 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1123 ] + remoteopts,
1123 ] + remoteopts,
1124 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1124 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1125 def bundle(ui, repo, fname, dest=None, **opts):
1125 def bundle(ui, repo, fname, dest=None, **opts):
1126 """create a bundle file
1126 """create a bundle file
1127
1127
1128 Generate a bundle file containing data to be added to a repository.
1128 Generate a bundle file containing data to be added to a repository.
1129
1129
1130 To create a bundle containing all changesets, use -a/--all
1130 To create a bundle containing all changesets, use -a/--all
1131 (or --base null). Otherwise, hg assumes the destination will have
1131 (or --base null). Otherwise, hg assumes the destination will have
1132 all the nodes you specify with --base parameters. Otherwise, hg
1132 all the nodes you specify with --base parameters. Otherwise, hg
1133 will assume the repository has all the nodes in destination, or
1133 will assume the repository has all the nodes in destination, or
1134 default-push/default if no destination is specified.
1134 default-push/default if no destination is specified.
1135
1135
1136 You can change bundle format with the -t/--type option. See
1136 You can change bundle format with the -t/--type option. See
1137 :hg:`help bundlespec` for documentation on this format. By default,
1137 :hg:`help bundlespec` for documentation on this format. By default,
1138 the most appropriate format is used and compression defaults to
1138 the most appropriate format is used and compression defaults to
1139 bzip2.
1139 bzip2.
1140
1140
1141 The bundle file can then be transferred using conventional means
1141 The bundle file can then be transferred using conventional means
1142 and applied to another repository with the unbundle or pull
1142 and applied to another repository with the unbundle or pull
1143 command. This is useful when direct push and pull are not
1143 command. This is useful when direct push and pull are not
1144 available or when exporting an entire repository is undesirable.
1144 available or when exporting an entire repository is undesirable.
1145
1145
1146 Applying bundles preserves all changeset contents including
1146 Applying bundles preserves all changeset contents including
1147 permissions, copy/rename information, and revision history.
1147 permissions, copy/rename information, and revision history.
1148
1148
1149 Returns 0 on success, 1 if no changes found.
1149 Returns 0 on success, 1 if no changes found.
1150 """
1150 """
1151 opts = pycompat.byteskwargs(opts)
1151 opts = pycompat.byteskwargs(opts)
1152 revs = None
1152 revs = None
1153 if 'rev' in opts:
1153 if 'rev' in opts:
1154 revstrings = opts['rev']
1154 revstrings = opts['rev']
1155 revs = scmutil.revrange(repo, revstrings)
1155 revs = scmutil.revrange(repo, revstrings)
1156 if revstrings and not revs:
1156 if revstrings and not revs:
1157 raise error.Abort(_('no commits to bundle'))
1157 raise error.Abort(_('no commits to bundle'))
1158
1158
1159 bundletype = opts.get('type', 'bzip2').lower()
1159 bundletype = opts.get('type', 'bzip2').lower()
1160 try:
1160 try:
1161 bcompression, cgversion, params = exchange.parsebundlespec(
1161 bcompression, cgversion, params = exchange.parsebundlespec(
1162 repo, bundletype, strict=False)
1162 repo, bundletype, strict=False)
1163 except error.UnsupportedBundleSpecification as e:
1163 except error.UnsupportedBundleSpecification as e:
1164 raise error.Abort(str(e),
1164 raise error.Abort(str(e),
1165 hint=_("see 'hg help bundlespec' for supported "
1165 hint=_("see 'hg help bundlespec' for supported "
1166 "values for --type"))
1166 "values for --type"))
1167
1167
1168 # Packed bundles are a pseudo bundle format for now.
1168 # Packed bundles are a pseudo bundle format for now.
1169 if cgversion == 's1':
1169 if cgversion == 's1':
1170 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1170 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1171 hint=_("use 'hg debugcreatestreamclonebundle'"))
1171 hint=_("use 'hg debugcreatestreamclonebundle'"))
1172
1172
1173 if opts.get('all'):
1173 if opts.get('all'):
1174 if dest:
1174 if dest:
1175 raise error.Abort(_("--all is incompatible with specifying "
1175 raise error.Abort(_("--all is incompatible with specifying "
1176 "a destination"))
1176 "a destination"))
1177 if opts.get('base'):
1177 if opts.get('base'):
1178 ui.warn(_("ignoring --base because --all was specified\n"))
1178 ui.warn(_("ignoring --base because --all was specified\n"))
1179 base = ['null']
1179 base = ['null']
1180 else:
1180 else:
1181 base = scmutil.revrange(repo, opts.get('base'))
1181 base = scmutil.revrange(repo, opts.get('base'))
1182 if cgversion not in changegroup.supportedoutgoingversions(repo):
1182 if cgversion not in changegroup.supportedoutgoingversions(repo):
1183 raise error.Abort(_("repository does not support bundle version %s") %
1183 raise error.Abort(_("repository does not support bundle version %s") %
1184 cgversion)
1184 cgversion)
1185
1185
1186 if base:
1186 if base:
1187 if dest:
1187 if dest:
1188 raise error.Abort(_("--base is incompatible with specifying "
1188 raise error.Abort(_("--base is incompatible with specifying "
1189 "a destination"))
1189 "a destination"))
1190 common = [repo.lookup(rev) for rev in base]
1190 common = [repo.lookup(rev) for rev in base]
1191 heads = revs and map(repo.lookup, revs) or None
1191 heads = revs and map(repo.lookup, revs) or None
1192 outgoing = discovery.outgoing(repo, common, heads)
1192 outgoing = discovery.outgoing(repo, common, heads)
1193 else:
1193 else:
1194 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1194 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1195 dest, branches = hg.parseurl(dest, opts.get('branch'))
1195 dest, branches = hg.parseurl(dest, opts.get('branch'))
1196 other = hg.peer(repo, opts, dest)
1196 other = hg.peer(repo, opts, dest)
1197 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1197 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1198 heads = revs and map(repo.lookup, revs) or revs
1198 heads = revs and map(repo.lookup, revs) or revs
1199 outgoing = discovery.findcommonoutgoing(repo, other,
1199 outgoing = discovery.findcommonoutgoing(repo, other,
1200 onlyheads=heads,
1200 onlyheads=heads,
1201 force=opts.get('force'),
1201 force=opts.get('force'),
1202 portable=True)
1202 portable=True)
1203
1203
1204 if not outgoing.missing:
1204 if not outgoing.missing:
1205 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1205 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1206 return 1
1206 return 1
1207
1207
1208 if cgversion == '01': #bundle1
1208 if cgversion == '01': #bundle1
1209 if bcompression is None:
1209 if bcompression is None:
1210 bcompression = 'UN'
1210 bcompression = 'UN'
1211 bversion = 'HG10' + bcompression
1211 bversion = 'HG10' + bcompression
1212 bcompression = None
1212 bcompression = None
1213 elif cgversion in ('02', '03'):
1213 elif cgversion in ('02', '03'):
1214 bversion = 'HG20'
1214 bversion = 'HG20'
1215 else:
1215 else:
1216 raise error.ProgrammingError(
1216 raise error.ProgrammingError(
1217 'bundle: unexpected changegroup version %s' % cgversion)
1217 'bundle: unexpected changegroup version %s' % cgversion)
1218
1218
1219 # TODO compression options should be derived from bundlespec parsing.
1219 # TODO compression options should be derived from bundlespec parsing.
1220 # This is a temporary hack to allow adjusting bundle compression
1220 # This is a temporary hack to allow adjusting bundle compression
1221 # level without a) formalizing the bundlespec changes to declare it
1221 # level without a) formalizing the bundlespec changes to declare it
1222 # b) introducing a command flag.
1222 # b) introducing a command flag.
1223 compopts = {}
1223 compopts = {}
1224 complevel = ui.configint('experimental', 'bundlecomplevel')
1224 complevel = ui.configint('experimental', 'bundlecomplevel')
1225 if complevel is not None:
1225 if complevel is not None:
1226 compopts['level'] = complevel
1226 compopts['level'] = complevel
1227
1227
1228
1228
1229 contentopts = {'cg.version': cgversion}
1229 contentopts = {'cg.version': cgversion}
1230 if repo.ui.configbool('experimental', 'stabilization.bundle-obsmarker'):
1230 if repo.ui.configbool('experimental', 'stabilization.bundle-obsmarker'):
1231 contentopts['obsolescence'] = True
1231 contentopts['obsolescence'] = True
1232 if repo.ui.configbool('experimental', 'bundle-phases'):
1232 if repo.ui.configbool('experimental', 'bundle-phases'):
1233 contentopts['phases'] = True
1233 contentopts['phases'] = True
1234 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1234 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1235 contentopts, compression=bcompression,
1235 contentopts, compression=bcompression,
1236 compopts=compopts)
1236 compopts=compopts)
1237
1237
1238 @command('cat',
1238 @command('cat',
1239 [('o', 'output', '',
1239 [('o', 'output', '',
1240 _('print output to file with formatted name'), _('FORMAT')),
1240 _('print output to file with formatted name'), _('FORMAT')),
1241 ('r', 'rev', '', _('print the given revision'), _('REV')),
1241 ('r', 'rev', '', _('print the given revision'), _('REV')),
1242 ('', 'decode', None, _('apply any matching decode filter')),
1242 ('', 'decode', None, _('apply any matching decode filter')),
1243 ] + walkopts + formatteropts,
1243 ] + walkopts + formatteropts,
1244 _('[OPTION]... FILE...'),
1244 _('[OPTION]... FILE...'),
1245 inferrepo=True)
1245 inferrepo=True)
1246 def cat(ui, repo, file1, *pats, **opts):
1246 def cat(ui, repo, file1, *pats, **opts):
1247 """output the current or given revision of files
1247 """output the current or given revision of files
1248
1248
1249 Print the specified files as they were at the given revision. If
1249 Print the specified files as they were at the given revision. If
1250 no revision is given, the parent of the working directory is used.
1250 no revision is given, the parent of the working directory is used.
1251
1251
1252 Output may be to a file, in which case the name of the file is
1252 Output may be to a file, in which case the name of the file is
1253 given using a format string. The formatting rules as follows:
1253 given using a format string. The formatting rules as follows:
1254
1254
1255 :``%%``: literal "%" character
1255 :``%%``: literal "%" character
1256 :``%s``: basename of file being printed
1256 :``%s``: basename of file being printed
1257 :``%d``: dirname of file being printed, or '.' if in repository root
1257 :``%d``: dirname of file being printed, or '.' if in repository root
1258 :``%p``: root-relative path name of file being printed
1258 :``%p``: root-relative path name of file being printed
1259 :``%H``: changeset hash (40 hexadecimal digits)
1259 :``%H``: changeset hash (40 hexadecimal digits)
1260 :``%R``: changeset revision number
1260 :``%R``: changeset revision number
1261 :``%h``: short-form changeset hash (12 hexadecimal digits)
1261 :``%h``: short-form changeset hash (12 hexadecimal digits)
1262 :``%r``: zero-padded changeset revision number
1262 :``%r``: zero-padded changeset revision number
1263 :``%b``: basename of the exporting repository
1263 :``%b``: basename of the exporting repository
1264
1264
1265 Returns 0 on success.
1265 Returns 0 on success.
1266 """
1266 """
1267 ctx = scmutil.revsingle(repo, opts.get('rev'))
1267 ctx = scmutil.revsingle(repo, opts.get('rev'))
1268 m = scmutil.match(ctx, (file1,) + pats, opts)
1268 m = scmutil.match(ctx, (file1,) + pats, opts)
1269 fntemplate = opts.pop('output', '')
1269 fntemplate = opts.pop('output', '')
1270 if cmdutil.isstdiofilename(fntemplate):
1270 if cmdutil.isstdiofilename(fntemplate):
1271 fntemplate = ''
1271 fntemplate = ''
1272
1272
1273 if fntemplate:
1273 if fntemplate:
1274 fm = formatter.nullformatter(ui, 'cat')
1274 fm = formatter.nullformatter(ui, 'cat')
1275 else:
1275 else:
1276 ui.pager('cat')
1276 ui.pager('cat')
1277 fm = ui.formatter('cat', opts)
1277 fm = ui.formatter('cat', opts)
1278 with fm:
1278 with fm:
1279 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '', **opts)
1279 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '', **opts)
1280
1280
1281 @command('^clone',
1281 @command('^clone',
1282 [('U', 'noupdate', None, _('the clone will include an empty working '
1282 [('U', 'noupdate', None, _('the clone will include an empty working '
1283 'directory (only a repository)')),
1283 'directory (only a repository)')),
1284 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1284 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1285 _('REV')),
1285 _('REV')),
1286 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1286 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1287 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1287 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1288 ('', 'pull', None, _('use pull protocol to copy metadata')),
1288 ('', 'pull', None, _('use pull protocol to copy metadata')),
1289 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1289 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1290 ] + remoteopts,
1290 ] + remoteopts,
1291 _('[OPTION]... SOURCE [DEST]'),
1291 _('[OPTION]... SOURCE [DEST]'),
1292 norepo=True)
1292 norepo=True)
1293 def clone(ui, source, dest=None, **opts):
1293 def clone(ui, source, dest=None, **opts):
1294 """make a copy of an existing repository
1294 """make a copy of an existing repository
1295
1295
1296 Create a copy of an existing repository in a new directory.
1296 Create a copy of an existing repository in a new directory.
1297
1297
1298 If no destination directory name is specified, it defaults to the
1298 If no destination directory name is specified, it defaults to the
1299 basename of the source.
1299 basename of the source.
1300
1300
1301 The location of the source is added to the new repository's
1301 The location of the source is added to the new repository's
1302 ``.hg/hgrc`` file, as the default to be used for future pulls.
1302 ``.hg/hgrc`` file, as the default to be used for future pulls.
1303
1303
1304 Only local paths and ``ssh://`` URLs are supported as
1304 Only local paths and ``ssh://`` URLs are supported as
1305 destinations. For ``ssh://`` destinations, no working directory or
1305 destinations. For ``ssh://`` destinations, no working directory or
1306 ``.hg/hgrc`` will be created on the remote side.
1306 ``.hg/hgrc`` will be created on the remote side.
1307
1307
1308 If the source repository has a bookmark called '@' set, that
1308 If the source repository has a bookmark called '@' set, that
1309 revision will be checked out in the new repository by default.
1309 revision will be checked out in the new repository by default.
1310
1310
1311 To check out a particular version, use -u/--update, or
1311 To check out a particular version, use -u/--update, or
1312 -U/--noupdate to create a clone with no working directory.
1312 -U/--noupdate to create a clone with no working directory.
1313
1313
1314 To pull only a subset of changesets, specify one or more revisions
1314 To pull only a subset of changesets, specify one or more revisions
1315 identifiers with -r/--rev or branches with -b/--branch. The
1315 identifiers with -r/--rev or branches with -b/--branch. The
1316 resulting clone will contain only the specified changesets and
1316 resulting clone will contain only the specified changesets and
1317 their ancestors. These options (or 'clone src#rev dest') imply
1317 their ancestors. These options (or 'clone src#rev dest') imply
1318 --pull, even for local source repositories.
1318 --pull, even for local source repositories.
1319
1319
1320 .. note::
1320 .. note::
1321
1321
1322 Specifying a tag will include the tagged changeset but not the
1322 Specifying a tag will include the tagged changeset but not the
1323 changeset containing the tag.
1323 changeset containing the tag.
1324
1324
1325 .. container:: verbose
1325 .. container:: verbose
1326
1326
1327 For efficiency, hardlinks are used for cloning whenever the
1327 For efficiency, hardlinks are used for cloning whenever the
1328 source and destination are on the same filesystem (note this
1328 source and destination are on the same filesystem (note this
1329 applies only to the repository data, not to the working
1329 applies only to the repository data, not to the working
1330 directory). Some filesystems, such as AFS, implement hardlinking
1330 directory). Some filesystems, such as AFS, implement hardlinking
1331 incorrectly, but do not report errors. In these cases, use the
1331 incorrectly, but do not report errors. In these cases, use the
1332 --pull option to avoid hardlinking.
1332 --pull option to avoid hardlinking.
1333
1333
1334 In some cases, you can clone repositories and the working
1334 In some cases, you can clone repositories and the working
1335 directory using full hardlinks with ::
1335 directory using full hardlinks with ::
1336
1336
1337 $ cp -al REPO REPOCLONE
1337 $ cp -al REPO REPOCLONE
1338
1338
1339 This is the fastest way to clone, but it is not always safe. The
1339 This is the fastest way to clone, but it is not always safe. The
1340 operation is not atomic (making sure REPO is not modified during
1340 operation is not atomic (making sure REPO is not modified during
1341 the operation is up to you) and you have to make sure your
1341 the operation is up to you) and you have to make sure your
1342 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1342 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1343 so). Also, this is not compatible with certain extensions that
1343 so). Also, this is not compatible with certain extensions that
1344 place their metadata under the .hg directory, such as mq.
1344 place their metadata under the .hg directory, such as mq.
1345
1345
1346 Mercurial will update the working directory to the first applicable
1346 Mercurial will update the working directory to the first applicable
1347 revision from this list:
1347 revision from this list:
1348
1348
1349 a) null if -U or the source repository has no changesets
1349 a) null if -U or the source repository has no changesets
1350 b) if -u . and the source repository is local, the first parent of
1350 b) if -u . and the source repository is local, the first parent of
1351 the source repository's working directory
1351 the source repository's working directory
1352 c) the changeset specified with -u (if a branch name, this means the
1352 c) the changeset specified with -u (if a branch name, this means the
1353 latest head of that branch)
1353 latest head of that branch)
1354 d) the changeset specified with -r
1354 d) the changeset specified with -r
1355 e) the tipmost head specified with -b
1355 e) the tipmost head specified with -b
1356 f) the tipmost head specified with the url#branch source syntax
1356 f) the tipmost head specified with the url#branch source syntax
1357 g) the revision marked with the '@' bookmark, if present
1357 g) the revision marked with the '@' bookmark, if present
1358 h) the tipmost head of the default branch
1358 h) the tipmost head of the default branch
1359 i) tip
1359 i) tip
1360
1360
1361 When cloning from servers that support it, Mercurial may fetch
1361 When cloning from servers that support it, Mercurial may fetch
1362 pre-generated data from a server-advertised URL. When this is done,
1362 pre-generated data from a server-advertised URL. When this is done,
1363 hooks operating on incoming changesets and changegroups may fire twice,
1363 hooks operating on incoming changesets and changegroups may fire twice,
1364 once for the bundle fetched from the URL and another for any additional
1364 once for the bundle fetched from the URL and another for any additional
1365 data not fetched from this URL. In addition, if an error occurs, the
1365 data not fetched from this URL. In addition, if an error occurs, the
1366 repository may be rolled back to a partial clone. This behavior may
1366 repository may be rolled back to a partial clone. This behavior may
1367 change in future releases. See :hg:`help -e clonebundles` for more.
1367 change in future releases. See :hg:`help -e clonebundles` for more.
1368
1368
1369 Examples:
1369 Examples:
1370
1370
1371 - clone a remote repository to a new directory named hg/::
1371 - clone a remote repository to a new directory named hg/::
1372
1372
1373 hg clone https://www.mercurial-scm.org/repo/hg/
1373 hg clone https://www.mercurial-scm.org/repo/hg/
1374
1374
1375 - create a lightweight local clone::
1375 - create a lightweight local clone::
1376
1376
1377 hg clone project/ project-feature/
1377 hg clone project/ project-feature/
1378
1378
1379 - clone from an absolute path on an ssh server (note double-slash)::
1379 - clone from an absolute path on an ssh server (note double-slash)::
1380
1380
1381 hg clone ssh://user@server//home/projects/alpha/
1381 hg clone ssh://user@server//home/projects/alpha/
1382
1382
1383 - do a high-speed clone over a LAN while checking out a
1383 - do a high-speed clone over a LAN while checking out a
1384 specified version::
1384 specified version::
1385
1385
1386 hg clone --uncompressed http://server/repo -u 1.5
1386 hg clone --uncompressed http://server/repo -u 1.5
1387
1387
1388 - create a repository without changesets after a particular revision::
1388 - create a repository without changesets after a particular revision::
1389
1389
1390 hg clone -r 04e544 experimental/ good/
1390 hg clone -r 04e544 experimental/ good/
1391
1391
1392 - clone (and track) a particular named branch::
1392 - clone (and track) a particular named branch::
1393
1393
1394 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1394 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1395
1395
1396 See :hg:`help urls` for details on specifying URLs.
1396 See :hg:`help urls` for details on specifying URLs.
1397
1397
1398 Returns 0 on success.
1398 Returns 0 on success.
1399 """
1399 """
1400 opts = pycompat.byteskwargs(opts)
1400 opts = pycompat.byteskwargs(opts)
1401 if opts.get('noupdate') and opts.get('updaterev'):
1401 if opts.get('noupdate') and opts.get('updaterev'):
1402 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1402 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1403
1403
1404 r = hg.clone(ui, opts, source, dest,
1404 r = hg.clone(ui, opts, source, dest,
1405 pull=opts.get('pull'),
1405 pull=opts.get('pull'),
1406 stream=opts.get('uncompressed'),
1406 stream=opts.get('uncompressed'),
1407 rev=opts.get('rev'),
1407 rev=opts.get('rev'),
1408 update=opts.get('updaterev') or not opts.get('noupdate'),
1408 update=opts.get('updaterev') or not opts.get('noupdate'),
1409 branch=opts.get('branch'),
1409 branch=opts.get('branch'),
1410 shareopts=opts.get('shareopts'))
1410 shareopts=opts.get('shareopts'))
1411
1411
1412 return r is None
1412 return r is None
1413
1413
1414 @command('^commit|ci',
1414 @command('^commit|ci',
1415 [('A', 'addremove', None,
1415 [('A', 'addremove', None,
1416 _('mark new/missing files as added/removed before committing')),
1416 _('mark new/missing files as added/removed before committing')),
1417 ('', 'close-branch', None,
1417 ('', 'close-branch', None,
1418 _('mark a branch head as closed')),
1418 _('mark a branch head as closed')),
1419 ('', 'amend', None, _('amend the parent of the working directory')),
1419 ('', 'amend', None, _('amend the parent of the working directory')),
1420 ('s', 'secret', None, _('use the secret phase for committing')),
1420 ('s', 'secret', None, _('use the secret phase for committing')),
1421 ('e', 'edit', None, _('invoke editor on commit messages')),
1421 ('e', 'edit', None, _('invoke editor on commit messages')),
1422 ('i', 'interactive', None, _('use interactive mode')),
1422 ('i', 'interactive', None, _('use interactive mode')),
1423 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1423 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1424 _('[OPTION]... [FILE]...'),
1424 _('[OPTION]... [FILE]...'),
1425 inferrepo=True)
1425 inferrepo=True)
1426 def commit(ui, repo, *pats, **opts):
1426 def commit(ui, repo, *pats, **opts):
1427 """commit the specified files or all outstanding changes
1427 """commit the specified files or all outstanding changes
1428
1428
1429 Commit changes to the given files into the repository. Unlike a
1429 Commit changes to the given files into the repository. Unlike a
1430 centralized SCM, this operation is a local operation. See
1430 centralized SCM, this operation is a local operation. See
1431 :hg:`push` for a way to actively distribute your changes.
1431 :hg:`push` for a way to actively distribute your changes.
1432
1432
1433 If a list of files is omitted, all changes reported by :hg:`status`
1433 If a list of files is omitted, all changes reported by :hg:`status`
1434 will be committed.
1434 will be committed.
1435
1435
1436 If you are committing the result of a merge, do not provide any
1436 If you are committing the result of a merge, do not provide any
1437 filenames or -I/-X filters.
1437 filenames or -I/-X filters.
1438
1438
1439 If no commit message is specified, Mercurial starts your
1439 If no commit message is specified, Mercurial starts your
1440 configured editor where you can enter a message. In case your
1440 configured editor where you can enter a message. In case your
1441 commit fails, you will find a backup of your message in
1441 commit fails, you will find a backup of your message in
1442 ``.hg/last-message.txt``.
1442 ``.hg/last-message.txt``.
1443
1443
1444 The --close-branch flag can be used to mark the current branch
1444 The --close-branch flag can be used to mark the current branch
1445 head closed. When all heads of a branch are closed, the branch
1445 head closed. When all heads of a branch are closed, the branch
1446 will be considered closed and no longer listed.
1446 will be considered closed and no longer listed.
1447
1447
1448 The --amend flag can be used to amend the parent of the
1448 The --amend flag can be used to amend the parent of the
1449 working directory with a new commit that contains the changes
1449 working directory with a new commit that contains the changes
1450 in the parent in addition to those currently reported by :hg:`status`,
1450 in the parent in addition to those currently reported by :hg:`status`,
1451 if there are any. The old commit is stored in a backup bundle in
1451 if there are any. The old commit is stored in a backup bundle in
1452 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1452 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1453 on how to restore it).
1453 on how to restore it).
1454
1454
1455 Message, user and date are taken from the amended commit unless
1455 Message, user and date are taken from the amended commit unless
1456 specified. When a message isn't specified on the command line,
1456 specified. When a message isn't specified on the command line,
1457 the editor will open with the message of the amended commit.
1457 the editor will open with the message of the amended commit.
1458
1458
1459 It is not possible to amend public changesets (see :hg:`help phases`)
1459 It is not possible to amend public changesets (see :hg:`help phases`)
1460 or changesets that have children.
1460 or changesets that have children.
1461
1461
1462 See :hg:`help dates` for a list of formats valid for -d/--date.
1462 See :hg:`help dates` for a list of formats valid for -d/--date.
1463
1463
1464 Returns 0 on success, 1 if nothing changed.
1464 Returns 0 on success, 1 if nothing changed.
1465
1465
1466 .. container:: verbose
1466 .. container:: verbose
1467
1467
1468 Examples:
1468 Examples:
1469
1469
1470 - commit all files ending in .py::
1470 - commit all files ending in .py::
1471
1471
1472 hg commit --include "set:**.py"
1472 hg commit --include "set:**.py"
1473
1473
1474 - commit all non-binary files::
1474 - commit all non-binary files::
1475
1475
1476 hg commit --exclude "set:binary()"
1476 hg commit --exclude "set:binary()"
1477
1477
1478 - amend the current commit and set the date to now::
1478 - amend the current commit and set the date to now::
1479
1479
1480 hg commit --amend --date now
1480 hg commit --amend --date now
1481 """
1481 """
1482 wlock = lock = None
1482 wlock = lock = None
1483 try:
1483 try:
1484 wlock = repo.wlock()
1484 wlock = repo.wlock()
1485 lock = repo.lock()
1485 lock = repo.lock()
1486 return _docommit(ui, repo, *pats, **opts)
1486 return _docommit(ui, repo, *pats, **opts)
1487 finally:
1487 finally:
1488 release(lock, wlock)
1488 release(lock, wlock)
1489
1489
1490 def _docommit(ui, repo, *pats, **opts):
1490 def _docommit(ui, repo, *pats, **opts):
1491 if opts.get(r'interactive'):
1491 if opts.get(r'interactive'):
1492 opts.pop(r'interactive')
1492 opts.pop(r'interactive')
1493 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1493 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1494 cmdutil.recordfilter, *pats,
1494 cmdutil.recordfilter, *pats,
1495 **opts)
1495 **opts)
1496 # ret can be 0 (no changes to record) or the value returned by
1496 # ret can be 0 (no changes to record) or the value returned by
1497 # commit(), 1 if nothing changed or None on success.
1497 # commit(), 1 if nothing changed or None on success.
1498 return 1 if ret == 0 else ret
1498 return 1 if ret == 0 else ret
1499
1499
1500 opts = pycompat.byteskwargs(opts)
1500 opts = pycompat.byteskwargs(opts)
1501 if opts.get('subrepos'):
1501 if opts.get('subrepos'):
1502 if opts.get('amend'):
1502 if opts.get('amend'):
1503 raise error.Abort(_('cannot amend with --subrepos'))
1503 raise error.Abort(_('cannot amend with --subrepos'))
1504 # Let --subrepos on the command line override config setting.
1504 # Let --subrepos on the command line override config setting.
1505 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1505 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1506
1506
1507 cmdutil.checkunfinished(repo, commit=True)
1507 cmdutil.checkunfinished(repo, commit=True)
1508
1508
1509 branch = repo[None].branch()
1509 branch = repo[None].branch()
1510 bheads = repo.branchheads(branch)
1510 bheads = repo.branchheads(branch)
1511
1511
1512 extra = {}
1512 extra = {}
1513 if opts.get('close_branch'):
1513 if opts.get('close_branch'):
1514 extra['close'] = 1
1514 extra['close'] = 1
1515
1515
1516 if not bheads:
1516 if not bheads:
1517 raise error.Abort(_('can only close branch heads'))
1517 raise error.Abort(_('can only close branch heads'))
1518 elif opts.get('amend'):
1518 elif opts.get('amend'):
1519 if repo[None].parents()[0].p1().branch() != branch and \
1519 if repo[None].parents()[0].p1().branch() != branch and \
1520 repo[None].parents()[0].p2().branch() != branch:
1520 repo[None].parents()[0].p2().branch() != branch:
1521 raise error.Abort(_('can only close branch heads'))
1521 raise error.Abort(_('can only close branch heads'))
1522
1522
1523 if opts.get('amend'):
1523 if opts.get('amend'):
1524 if ui.configbool('ui', 'commitsubrepos'):
1524 if ui.configbool('ui', 'commitsubrepos'):
1525 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1525 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1526
1526
1527 old = repo['.']
1527 old = repo['.']
1528 if not old.mutable():
1528 if not old.mutable():
1529 raise error.Abort(_('cannot amend public changesets'))
1529 raise error.Abort(_('cannot amend public changesets'))
1530 if len(repo[None].parents()) > 1:
1530 if len(repo[None].parents()) > 1:
1531 raise error.Abort(_('cannot amend while merging'))
1531 raise error.Abort(_('cannot amend while merging'))
1532 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
1532 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
1533 if not allowunstable and old.children():
1533 if not allowunstable and old.children():
1534 raise error.Abort(_('cannot amend changeset with children'))
1534 raise error.Abort(_('cannot amend changeset with children'))
1535
1535
1536 # Currently histedit gets confused if an amend happens while histedit
1536 # Currently histedit gets confused if an amend happens while histedit
1537 # is in progress. Since we have a checkunfinished command, we are
1537 # is in progress. Since we have a checkunfinished command, we are
1538 # temporarily honoring it.
1538 # temporarily honoring it.
1539 #
1539 #
1540 # Note: eventually this guard will be removed. Please do not expect
1540 # Note: eventually this guard will be removed. Please do not expect
1541 # this behavior to remain.
1541 # this behavior to remain.
1542 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1542 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1543 cmdutil.checkunfinished(repo)
1543 cmdutil.checkunfinished(repo)
1544
1544
1545 # commitfunc is used only for temporary amend commit by cmdutil.amend
1545 # commitfunc is used only for temporary amend commit by cmdutil.amend
1546 def commitfunc(ui, repo, message, match, opts):
1546 def commitfunc(ui, repo, message, match, opts):
1547 return repo.commit(message,
1547 return repo.commit(message,
1548 opts.get('user') or old.user(),
1548 opts.get('user') or old.user(),
1549 opts.get('date') or old.date(),
1549 opts.get('date') or old.date(),
1550 match,
1550 match,
1551 extra=extra)
1551 extra=extra)
1552
1552
1553 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1553 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1554 if node == old.node():
1554 if node == old.node():
1555 ui.status(_("nothing changed\n"))
1555 ui.status(_("nothing changed\n"))
1556 return 1
1556 return 1
1557 else:
1557 else:
1558 def commitfunc(ui, repo, message, match, opts):
1558 def commitfunc(ui, repo, message, match, opts):
1559 overrides = {}
1559 overrides = {}
1560 if opts.get('secret'):
1560 if opts.get('secret'):
1561 overrides[('phases', 'new-commit')] = 'secret'
1561 overrides[('phases', 'new-commit')] = 'secret'
1562
1562
1563 baseui = repo.baseui
1563 baseui = repo.baseui
1564 with baseui.configoverride(overrides, 'commit'):
1564 with baseui.configoverride(overrides, 'commit'):
1565 with ui.configoverride(overrides, 'commit'):
1565 with ui.configoverride(overrides, 'commit'):
1566 editform = cmdutil.mergeeditform(repo[None],
1566 editform = cmdutil.mergeeditform(repo[None],
1567 'commit.normal')
1567 'commit.normal')
1568 editor = cmdutil.getcommiteditor(
1568 editor = cmdutil.getcommiteditor(
1569 editform=editform, **pycompat.strkwargs(opts))
1569 editform=editform, **pycompat.strkwargs(opts))
1570 return repo.commit(message,
1570 return repo.commit(message,
1571 opts.get('user'),
1571 opts.get('user'),
1572 opts.get('date'),
1572 opts.get('date'),
1573 match,
1573 match,
1574 editor=editor,
1574 editor=editor,
1575 extra=extra)
1575 extra=extra)
1576
1576
1577 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1577 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1578
1578
1579 if not node:
1579 if not node:
1580 stat = cmdutil.postcommitstatus(repo, pats, opts)
1580 stat = cmdutil.postcommitstatus(repo, pats, opts)
1581 if stat[3]:
1581 if stat[3]:
1582 ui.status(_("nothing changed (%d missing files, see "
1582 ui.status(_("nothing changed (%d missing files, see "
1583 "'hg status')\n") % len(stat[3]))
1583 "'hg status')\n") % len(stat[3]))
1584 else:
1584 else:
1585 ui.status(_("nothing changed\n"))
1585 ui.status(_("nothing changed\n"))
1586 return 1
1586 return 1
1587
1587
1588 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1588 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1589
1589
1590 @command('config|showconfig|debugconfig',
1590 @command('config|showconfig|debugconfig',
1591 [('u', 'untrusted', None, _('show untrusted configuration options')),
1591 [('u', 'untrusted', None, _('show untrusted configuration options')),
1592 ('e', 'edit', None, _('edit user config')),
1592 ('e', 'edit', None, _('edit user config')),
1593 ('l', 'local', None, _('edit repository config')),
1593 ('l', 'local', None, _('edit repository config')),
1594 ('g', 'global', None, _('edit global config'))] + formatteropts,
1594 ('g', 'global', None, _('edit global config'))] + formatteropts,
1595 _('[-u] [NAME]...'),
1595 _('[-u] [NAME]...'),
1596 optionalrepo=True)
1596 optionalrepo=True)
1597 def config(ui, repo, *values, **opts):
1597 def config(ui, repo, *values, **opts):
1598 """show combined config settings from all hgrc files
1598 """show combined config settings from all hgrc files
1599
1599
1600 With no arguments, print names and values of all config items.
1600 With no arguments, print names and values of all config items.
1601
1601
1602 With one argument of the form section.name, print just the value
1602 With one argument of the form section.name, print just the value
1603 of that config item.
1603 of that config item.
1604
1604
1605 With multiple arguments, print names and values of all config
1605 With multiple arguments, print names and values of all config
1606 items with matching section names.
1606 items with matching section names.
1607
1607
1608 With --edit, start an editor on the user-level config file. With
1608 With --edit, start an editor on the user-level config file. With
1609 --global, edit the system-wide config file. With --local, edit the
1609 --global, edit the system-wide config file. With --local, edit the
1610 repository-level config file.
1610 repository-level config file.
1611
1611
1612 With --debug, the source (filename and line number) is printed
1612 With --debug, the source (filename and line number) is printed
1613 for each config item.
1613 for each config item.
1614
1614
1615 See :hg:`help config` for more information about config files.
1615 See :hg:`help config` for more information about config files.
1616
1616
1617 Returns 0 on success, 1 if NAME does not exist.
1617 Returns 0 on success, 1 if NAME does not exist.
1618
1618
1619 """
1619 """
1620
1620
1621 opts = pycompat.byteskwargs(opts)
1621 opts = pycompat.byteskwargs(opts)
1622 if opts.get('edit') or opts.get('local') or opts.get('global'):
1622 if opts.get('edit') or opts.get('local') or opts.get('global'):
1623 if opts.get('local') and opts.get('global'):
1623 if opts.get('local') and opts.get('global'):
1624 raise error.Abort(_("can't use --local and --global together"))
1624 raise error.Abort(_("can't use --local and --global together"))
1625
1625
1626 if opts.get('local'):
1626 if opts.get('local'):
1627 if not repo:
1627 if not repo:
1628 raise error.Abort(_("can't use --local outside a repository"))
1628 raise error.Abort(_("can't use --local outside a repository"))
1629 paths = [repo.vfs.join('hgrc')]
1629 paths = [repo.vfs.join('hgrc')]
1630 elif opts.get('global'):
1630 elif opts.get('global'):
1631 paths = rcutil.systemrcpath()
1631 paths = rcutil.systemrcpath()
1632 else:
1632 else:
1633 paths = rcutil.userrcpath()
1633 paths = rcutil.userrcpath()
1634
1634
1635 for f in paths:
1635 for f in paths:
1636 if os.path.exists(f):
1636 if os.path.exists(f):
1637 break
1637 break
1638 else:
1638 else:
1639 if opts.get('global'):
1639 if opts.get('global'):
1640 samplehgrc = uimod.samplehgrcs['global']
1640 samplehgrc = uimod.samplehgrcs['global']
1641 elif opts.get('local'):
1641 elif opts.get('local'):
1642 samplehgrc = uimod.samplehgrcs['local']
1642 samplehgrc = uimod.samplehgrcs['local']
1643 else:
1643 else:
1644 samplehgrc = uimod.samplehgrcs['user']
1644 samplehgrc = uimod.samplehgrcs['user']
1645
1645
1646 f = paths[0]
1646 f = paths[0]
1647 fp = open(f, "wb")
1647 fp = open(f, "wb")
1648 fp.write(util.tonativeeol(samplehgrc))
1648 fp.write(util.tonativeeol(samplehgrc))
1649 fp.close()
1649 fp.close()
1650
1650
1651 editor = ui.geteditor()
1651 editor = ui.geteditor()
1652 ui.system("%s \"%s\"" % (editor, f),
1652 ui.system("%s \"%s\"" % (editor, f),
1653 onerr=error.Abort, errprefix=_("edit failed"),
1653 onerr=error.Abort, errprefix=_("edit failed"),
1654 blockedtag='config_edit')
1654 blockedtag='config_edit')
1655 return
1655 return
1656 ui.pager('config')
1656 ui.pager('config')
1657 fm = ui.formatter('config', opts)
1657 fm = ui.formatter('config', opts)
1658 for t, f in rcutil.rccomponents():
1658 for t, f in rcutil.rccomponents():
1659 if t == 'path':
1659 if t == 'path':
1660 ui.debug('read config from: %s\n' % f)
1660 ui.debug('read config from: %s\n' % f)
1661 elif t == 'items':
1661 elif t == 'items':
1662 for section, name, value, source in f:
1662 for section, name, value, source in f:
1663 ui.debug('set config by: %s\n' % source)
1663 ui.debug('set config by: %s\n' % source)
1664 else:
1664 else:
1665 raise error.ProgrammingError('unknown rctype: %s' % t)
1665 raise error.ProgrammingError('unknown rctype: %s' % t)
1666 untrusted = bool(opts.get('untrusted'))
1666 untrusted = bool(opts.get('untrusted'))
1667 if values:
1667 if values:
1668 sections = [v for v in values if '.' not in v]
1668 sections = [v for v in values if '.' not in v]
1669 items = [v for v in values if '.' in v]
1669 items = [v for v in values if '.' in v]
1670 if len(items) > 1 or items and sections:
1670 if len(items) > 1 or items and sections:
1671 raise error.Abort(_('only one config item permitted'))
1671 raise error.Abort(_('only one config item permitted'))
1672 matched = False
1672 matched = False
1673 for section, name, value in ui.walkconfig(untrusted=untrusted):
1673 for section, name, value in ui.walkconfig(untrusted=untrusted):
1674 source = ui.configsource(section, name, untrusted)
1674 source = ui.configsource(section, name, untrusted)
1675 value = pycompat.bytestr(value)
1675 value = pycompat.bytestr(value)
1676 if fm.isplain():
1676 if fm.isplain():
1677 source = source or 'none'
1677 source = source or 'none'
1678 value = value.replace('\n', '\\n')
1678 value = value.replace('\n', '\\n')
1679 entryname = section + '.' + name
1679 entryname = section + '.' + name
1680 if values:
1680 if values:
1681 for v in values:
1681 for v in values:
1682 if v == section:
1682 if v == section:
1683 fm.startitem()
1683 fm.startitem()
1684 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1684 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1685 fm.write('name value', '%s=%s\n', entryname, value)
1685 fm.write('name value', '%s=%s\n', entryname, value)
1686 matched = True
1686 matched = True
1687 elif v == entryname:
1687 elif v == entryname:
1688 fm.startitem()
1688 fm.startitem()
1689 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1689 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1690 fm.write('value', '%s\n', value)
1690 fm.write('value', '%s\n', value)
1691 fm.data(name=entryname)
1691 fm.data(name=entryname)
1692 matched = True
1692 matched = True
1693 else:
1693 else:
1694 fm.startitem()
1694 fm.startitem()
1695 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1695 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1696 fm.write('name value', '%s=%s\n', entryname, value)
1696 fm.write('name value', '%s=%s\n', entryname, value)
1697 matched = True
1697 matched = True
1698 fm.end()
1698 fm.end()
1699 if matched:
1699 if matched:
1700 return 0
1700 return 0
1701 return 1
1701 return 1
1702
1702
1703 @command('copy|cp',
1703 @command('copy|cp',
1704 [('A', 'after', None, _('record a copy that has already occurred')),
1704 [('A', 'after', None, _('record a copy that has already occurred')),
1705 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1705 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1706 ] + walkopts + dryrunopts,
1706 ] + walkopts + dryrunopts,
1707 _('[OPTION]... [SOURCE]... DEST'))
1707 _('[OPTION]... [SOURCE]... DEST'))
1708 def copy(ui, repo, *pats, **opts):
1708 def copy(ui, repo, *pats, **opts):
1709 """mark files as copied for the next commit
1709 """mark files as copied for the next commit
1710
1710
1711 Mark dest as having copies of source files. If dest is a
1711 Mark dest as having copies of source files. If dest is a
1712 directory, copies are put in that directory. If dest is a file,
1712 directory, copies are put in that directory. If dest is a file,
1713 the source must be a single file.
1713 the source must be a single file.
1714
1714
1715 By default, this command copies the contents of files as they
1715 By default, this command copies the contents of files as they
1716 exist in the working directory. If invoked with -A/--after, the
1716 exist in the working directory. If invoked with -A/--after, the
1717 operation is recorded, but no copying is performed.
1717 operation is recorded, but no copying is performed.
1718
1718
1719 This command takes effect with the next commit. To undo a copy
1719 This command takes effect with the next commit. To undo a copy
1720 before that, see :hg:`revert`.
1720 before that, see :hg:`revert`.
1721
1721
1722 Returns 0 on success, 1 if errors are encountered.
1722 Returns 0 on success, 1 if errors are encountered.
1723 """
1723 """
1724 opts = pycompat.byteskwargs(opts)
1724 opts = pycompat.byteskwargs(opts)
1725 with repo.wlock(False):
1725 with repo.wlock(False):
1726 return cmdutil.copy(ui, repo, pats, opts)
1726 return cmdutil.copy(ui, repo, pats, opts)
1727
1727
1728 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
1728 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
1729 def debugcommands(ui, cmd='', *args):
1729 def debugcommands(ui, cmd='', *args):
1730 """list all available commands and options"""
1730 """list all available commands and options"""
1731 for cmd, vals in sorted(table.iteritems()):
1731 for cmd, vals in sorted(table.iteritems()):
1732 cmd = cmd.split('|')[0].strip('^')
1732 cmd = cmd.split('|')[0].strip('^')
1733 opts = ', '.join([i[1] for i in vals[1]])
1733 opts = ', '.join([i[1] for i in vals[1]])
1734 ui.write('%s: %s\n' % (cmd, opts))
1734 ui.write('%s: %s\n' % (cmd, opts))
1735
1735
1736 @command('debugcomplete',
1736 @command('debugcomplete',
1737 [('o', 'options', None, _('show the command options'))],
1737 [('o', 'options', None, _('show the command options'))],
1738 _('[-o] CMD'),
1738 _('[-o] CMD'),
1739 norepo=True)
1739 norepo=True)
1740 def debugcomplete(ui, cmd='', **opts):
1740 def debugcomplete(ui, cmd='', **opts):
1741 """returns the completion list associated with the given command"""
1741 """returns the completion list associated with the given command"""
1742
1742
1743 if opts.get('options'):
1743 if opts.get('options'):
1744 options = []
1744 options = []
1745 otables = [globalopts]
1745 otables = [globalopts]
1746 if cmd:
1746 if cmd:
1747 aliases, entry = cmdutil.findcmd(cmd, table, False)
1747 aliases, entry = cmdutil.findcmd(cmd, table, False)
1748 otables.append(entry[1])
1748 otables.append(entry[1])
1749 for t in otables:
1749 for t in otables:
1750 for o in t:
1750 for o in t:
1751 if "(DEPRECATED)" in o[3]:
1751 if "(DEPRECATED)" in o[3]:
1752 continue
1752 continue
1753 if o[0]:
1753 if o[0]:
1754 options.append('-%s' % o[0])
1754 options.append('-%s' % o[0])
1755 options.append('--%s' % o[1])
1755 options.append('--%s' % o[1])
1756 ui.write("%s\n" % "\n".join(options))
1756 ui.write("%s\n" % "\n".join(options))
1757 return
1757 return
1758
1758
1759 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1759 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1760 if ui.verbose:
1760 if ui.verbose:
1761 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1761 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1762 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1762 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1763
1763
1764 @command('^diff',
1764 @command('^diff',
1765 [('r', 'rev', [], _('revision'), _('REV')),
1765 [('r', 'rev', [], _('revision'), _('REV')),
1766 ('c', 'change', '', _('change made by revision'), _('REV'))
1766 ('c', 'change', '', _('change made by revision'), _('REV'))
1767 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1767 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1768 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1768 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1769 inferrepo=True)
1769 inferrepo=True)
1770 def diff(ui, repo, *pats, **opts):
1770 def diff(ui, repo, *pats, **opts):
1771 """diff repository (or selected files)
1771 """diff repository (or selected files)
1772
1772
1773 Show differences between revisions for the specified files.
1773 Show differences between revisions for the specified files.
1774
1774
1775 Differences between files are shown using the unified diff format.
1775 Differences between files are shown using the unified diff format.
1776
1776
1777 .. note::
1777 .. note::
1778
1778
1779 :hg:`diff` may generate unexpected results for merges, as it will
1779 :hg:`diff` may generate unexpected results for merges, as it will
1780 default to comparing against the working directory's first
1780 default to comparing against the working directory's first
1781 parent changeset if no revisions are specified.
1781 parent changeset if no revisions are specified.
1782
1782
1783 When two revision arguments are given, then changes are shown
1783 When two revision arguments are given, then changes are shown
1784 between those revisions. If only one revision is specified then
1784 between those revisions. If only one revision is specified then
1785 that revision is compared to the working directory, and, when no
1785 that revision is compared to the working directory, and, when no
1786 revisions are specified, the working directory files are compared
1786 revisions are specified, the working directory files are compared
1787 to its first parent.
1787 to its first parent.
1788
1788
1789 Alternatively you can specify -c/--change with a revision to see
1789 Alternatively you can specify -c/--change with a revision to see
1790 the changes in that changeset relative to its first parent.
1790 the changes in that changeset relative to its first parent.
1791
1791
1792 Without the -a/--text option, diff will avoid generating diffs of
1792 Without the -a/--text option, diff will avoid generating diffs of
1793 files it detects as binary. With -a, diff will generate a diff
1793 files it detects as binary. With -a, diff will generate a diff
1794 anyway, probably with undesirable results.
1794 anyway, probably with undesirable results.
1795
1795
1796 Use the -g/--git option to generate diffs in the git extended diff
1796 Use the -g/--git option to generate diffs in the git extended diff
1797 format. For more information, read :hg:`help diffs`.
1797 format. For more information, read :hg:`help diffs`.
1798
1798
1799 .. container:: verbose
1799 .. container:: verbose
1800
1800
1801 Examples:
1801 Examples:
1802
1802
1803 - compare a file in the current working directory to its parent::
1803 - compare a file in the current working directory to its parent::
1804
1804
1805 hg diff foo.c
1805 hg diff foo.c
1806
1806
1807 - compare two historical versions of a directory, with rename info::
1807 - compare two historical versions of a directory, with rename info::
1808
1808
1809 hg diff --git -r 1.0:1.2 lib/
1809 hg diff --git -r 1.0:1.2 lib/
1810
1810
1811 - get change stats relative to the last change on some date::
1811 - get change stats relative to the last change on some date::
1812
1812
1813 hg diff --stat -r "date('may 2')"
1813 hg diff --stat -r "date('may 2')"
1814
1814
1815 - diff all newly-added files that contain a keyword::
1815 - diff all newly-added files that contain a keyword::
1816
1816
1817 hg diff "set:added() and grep(GNU)"
1817 hg diff "set:added() and grep(GNU)"
1818
1818
1819 - compare a revision and its parents::
1819 - compare a revision and its parents::
1820
1820
1821 hg diff -c 9353 # compare against first parent
1821 hg diff -c 9353 # compare against first parent
1822 hg diff -r 9353^:9353 # same using revset syntax
1822 hg diff -r 9353^:9353 # same using revset syntax
1823 hg diff -r 9353^2:9353 # compare against the second parent
1823 hg diff -r 9353^2:9353 # compare against the second parent
1824
1824
1825 Returns 0 on success.
1825 Returns 0 on success.
1826 """
1826 """
1827
1827
1828 opts = pycompat.byteskwargs(opts)
1828 opts = pycompat.byteskwargs(opts)
1829 revs = opts.get('rev')
1829 revs = opts.get('rev')
1830 change = opts.get('change')
1830 change = opts.get('change')
1831 stat = opts.get('stat')
1831 stat = opts.get('stat')
1832 reverse = opts.get('reverse')
1832 reverse = opts.get('reverse')
1833
1833
1834 if revs and change:
1834 if revs and change:
1835 msg = _('cannot specify --rev and --change at the same time')
1835 msg = _('cannot specify --rev and --change at the same time')
1836 raise error.Abort(msg)
1836 raise error.Abort(msg)
1837 elif change:
1837 elif change:
1838 node2 = scmutil.revsingle(repo, change, None).node()
1838 node2 = scmutil.revsingle(repo, change, None).node()
1839 node1 = repo[node2].p1().node()
1839 node1 = repo[node2].p1().node()
1840 else:
1840 else:
1841 node1, node2 = scmutil.revpair(repo, revs)
1841 node1, node2 = scmutil.revpair(repo, revs)
1842
1842
1843 if reverse:
1843 if reverse:
1844 node1, node2 = node2, node1
1844 node1, node2 = node2, node1
1845
1845
1846 diffopts = patch.diffallopts(ui, opts)
1846 diffopts = patch.diffallopts(ui, opts)
1847 m = scmutil.match(repo[node2], pats, opts)
1847 m = scmutil.match(repo[node2], pats, opts)
1848 ui.pager('diff')
1848 ui.pager('diff')
1849 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1849 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1850 listsubrepos=opts.get('subrepos'),
1850 listsubrepos=opts.get('subrepos'),
1851 root=opts.get('root'))
1851 root=opts.get('root'))
1852
1852
1853 @command('^export',
1853 @command('^export',
1854 [('o', 'output', '',
1854 [('o', 'output', '',
1855 _('print output to file with formatted name'), _('FORMAT')),
1855 _('print output to file with formatted name'), _('FORMAT')),
1856 ('', 'switch-parent', None, _('diff against the second parent')),
1856 ('', 'switch-parent', None, _('diff against the second parent')),
1857 ('r', 'rev', [], _('revisions to export'), _('REV')),
1857 ('r', 'rev', [], _('revisions to export'), _('REV')),
1858 ] + diffopts,
1858 ] + diffopts,
1859 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'))
1859 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'))
1860 def export(ui, repo, *changesets, **opts):
1860 def export(ui, repo, *changesets, **opts):
1861 """dump the header and diffs for one or more changesets
1861 """dump the header and diffs for one or more changesets
1862
1862
1863 Print the changeset header and diffs for one or more revisions.
1863 Print the changeset header and diffs for one or more revisions.
1864 If no revision is given, the parent of the working directory is used.
1864 If no revision is given, the parent of the working directory is used.
1865
1865
1866 The information shown in the changeset header is: author, date,
1866 The information shown in the changeset header is: author, date,
1867 branch name (if non-default), changeset hash, parent(s) and commit
1867 branch name (if non-default), changeset hash, parent(s) and commit
1868 comment.
1868 comment.
1869
1869
1870 .. note::
1870 .. note::
1871
1871
1872 :hg:`export` may generate unexpected diff output for merge
1872 :hg:`export` may generate unexpected diff output for merge
1873 changesets, as it will compare the merge changeset against its
1873 changesets, as it will compare the merge changeset against its
1874 first parent only.
1874 first parent only.
1875
1875
1876 Output may be to a file, in which case the name of the file is
1876 Output may be to a file, in which case the name of the file is
1877 given using a format string. The formatting rules are as follows:
1877 given using a format string. The formatting rules are as follows:
1878
1878
1879 :``%%``: literal "%" character
1879 :``%%``: literal "%" character
1880 :``%H``: changeset hash (40 hexadecimal digits)
1880 :``%H``: changeset hash (40 hexadecimal digits)
1881 :``%N``: number of patches being generated
1881 :``%N``: number of patches being generated
1882 :``%R``: changeset revision number
1882 :``%R``: changeset revision number
1883 :``%b``: basename of the exporting repository
1883 :``%b``: basename of the exporting repository
1884 :``%h``: short-form changeset hash (12 hexadecimal digits)
1884 :``%h``: short-form changeset hash (12 hexadecimal digits)
1885 :``%m``: first line of the commit message (only alphanumeric characters)
1885 :``%m``: first line of the commit message (only alphanumeric characters)
1886 :``%n``: zero-padded sequence number, starting at 1
1886 :``%n``: zero-padded sequence number, starting at 1
1887 :``%r``: zero-padded changeset revision number
1887 :``%r``: zero-padded changeset revision number
1888
1888
1889 Without the -a/--text option, export will avoid generating diffs
1889 Without the -a/--text option, export will avoid generating diffs
1890 of files it detects as binary. With -a, export will generate a
1890 of files it detects as binary. With -a, export will generate a
1891 diff anyway, probably with undesirable results.
1891 diff anyway, probably with undesirable results.
1892
1892
1893 Use the -g/--git option to generate diffs in the git extended diff
1893 Use the -g/--git option to generate diffs in the git extended diff
1894 format. See :hg:`help diffs` for more information.
1894 format. See :hg:`help diffs` for more information.
1895
1895
1896 With the --switch-parent option, the diff will be against the
1896 With the --switch-parent option, the diff will be against the
1897 second parent. It can be useful to review a merge.
1897 second parent. It can be useful to review a merge.
1898
1898
1899 .. container:: verbose
1899 .. container:: verbose
1900
1900
1901 Examples:
1901 Examples:
1902
1902
1903 - use export and import to transplant a bugfix to the current
1903 - use export and import to transplant a bugfix to the current
1904 branch::
1904 branch::
1905
1905
1906 hg export -r 9353 | hg import -
1906 hg export -r 9353 | hg import -
1907
1907
1908 - export all the changesets between two revisions to a file with
1908 - export all the changesets between two revisions to a file with
1909 rename information::
1909 rename information::
1910
1910
1911 hg export --git -r 123:150 > changes.txt
1911 hg export --git -r 123:150 > changes.txt
1912
1912
1913 - split outgoing changes into a series of patches with
1913 - split outgoing changes into a series of patches with
1914 descriptive names::
1914 descriptive names::
1915
1915
1916 hg export -r "outgoing()" -o "%n-%m.patch"
1916 hg export -r "outgoing()" -o "%n-%m.patch"
1917
1917
1918 Returns 0 on success.
1918 Returns 0 on success.
1919 """
1919 """
1920 opts = pycompat.byteskwargs(opts)
1920 opts = pycompat.byteskwargs(opts)
1921 changesets += tuple(opts.get('rev', []))
1921 changesets += tuple(opts.get('rev', []))
1922 if not changesets:
1922 if not changesets:
1923 changesets = ['.']
1923 changesets = ['.']
1924 revs = scmutil.revrange(repo, changesets)
1924 revs = scmutil.revrange(repo, changesets)
1925 if not revs:
1925 if not revs:
1926 raise error.Abort(_("export requires at least one changeset"))
1926 raise error.Abort(_("export requires at least one changeset"))
1927 if len(revs) > 1:
1927 if len(revs) > 1:
1928 ui.note(_('exporting patches:\n'))
1928 ui.note(_('exporting patches:\n'))
1929 else:
1929 else:
1930 ui.note(_('exporting patch:\n'))
1930 ui.note(_('exporting patch:\n'))
1931 ui.pager('export')
1931 ui.pager('export')
1932 cmdutil.export(repo, revs, fntemplate=opts.get('output'),
1932 cmdutil.export(repo, revs, fntemplate=opts.get('output'),
1933 switch_parent=opts.get('switch_parent'),
1933 switch_parent=opts.get('switch_parent'),
1934 opts=patch.diffallopts(ui, opts))
1934 opts=patch.diffallopts(ui, opts))
1935
1935
1936 @command('files',
1936 @command('files',
1937 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
1937 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
1938 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
1938 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
1939 ] + walkopts + formatteropts + subrepoopts,
1939 ] + walkopts + formatteropts + subrepoopts,
1940 _('[OPTION]... [FILE]...'))
1940 _('[OPTION]... [FILE]...'))
1941 def files(ui, repo, *pats, **opts):
1941 def files(ui, repo, *pats, **opts):
1942 """list tracked files
1942 """list tracked files
1943
1943
1944 Print files under Mercurial control in the working directory or
1944 Print files under Mercurial control in the working directory or
1945 specified revision for given files (excluding removed files).
1945 specified revision for given files (excluding removed files).
1946 Files can be specified as filenames or filesets.
1946 Files can be specified as filenames or filesets.
1947
1947
1948 If no files are given to match, this command prints the names
1948 If no files are given to match, this command prints the names
1949 of all files under Mercurial control.
1949 of all files under Mercurial control.
1950
1950
1951 .. container:: verbose
1951 .. container:: verbose
1952
1952
1953 Examples:
1953 Examples:
1954
1954
1955 - list all files under the current directory::
1955 - list all files under the current directory::
1956
1956
1957 hg files .
1957 hg files .
1958
1958
1959 - shows sizes and flags for current revision::
1959 - shows sizes and flags for current revision::
1960
1960
1961 hg files -vr .
1961 hg files -vr .
1962
1962
1963 - list all files named README::
1963 - list all files named README::
1964
1964
1965 hg files -I "**/README"
1965 hg files -I "**/README"
1966
1966
1967 - list all binary files::
1967 - list all binary files::
1968
1968
1969 hg files "set:binary()"
1969 hg files "set:binary()"
1970
1970
1971 - find files containing a regular expression::
1971 - find files containing a regular expression::
1972
1972
1973 hg files "set:grep('bob')"
1973 hg files "set:grep('bob')"
1974
1974
1975 - search tracked file contents with xargs and grep::
1975 - search tracked file contents with xargs and grep::
1976
1976
1977 hg files -0 | xargs -0 grep foo
1977 hg files -0 | xargs -0 grep foo
1978
1978
1979 See :hg:`help patterns` and :hg:`help filesets` for more information
1979 See :hg:`help patterns` and :hg:`help filesets` for more information
1980 on specifying file patterns.
1980 on specifying file patterns.
1981
1981
1982 Returns 0 if a match is found, 1 otherwise.
1982 Returns 0 if a match is found, 1 otherwise.
1983
1983
1984 """
1984 """
1985
1985
1986 opts = pycompat.byteskwargs(opts)
1986 opts = pycompat.byteskwargs(opts)
1987 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
1987 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
1988
1988
1989 end = '\n'
1989 end = '\n'
1990 if opts.get('print0'):
1990 if opts.get('print0'):
1991 end = '\0'
1991 end = '\0'
1992 fmt = '%s' + end
1992 fmt = '%s' + end
1993
1993
1994 m = scmutil.match(ctx, pats, opts)
1994 m = scmutil.match(ctx, pats, opts)
1995 ui.pager('files')
1995 ui.pager('files')
1996 with ui.formatter('files', opts) as fm:
1996 with ui.formatter('files', opts) as fm:
1997 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
1997 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
1998
1998
1999 @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True)
1999 @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True)
2000 def forget(ui, repo, *pats, **opts):
2000 def forget(ui, repo, *pats, **opts):
2001 """forget the specified files on the next commit
2001 """forget the specified files on the next commit
2002
2002
2003 Mark the specified files so they will no longer be tracked
2003 Mark the specified files so they will no longer be tracked
2004 after the next commit.
2004 after the next commit.
2005
2005
2006 This only removes files from the current branch, not from the
2006 This only removes files from the current branch, not from the
2007 entire project history, and it does not delete them from the
2007 entire project history, and it does not delete them from the
2008 working directory.
2008 working directory.
2009
2009
2010 To delete the file from the working directory, see :hg:`remove`.
2010 To delete the file from the working directory, see :hg:`remove`.
2011
2011
2012 To undo a forget before the next commit, see :hg:`add`.
2012 To undo a forget before the next commit, see :hg:`add`.
2013
2013
2014 .. container:: verbose
2014 .. container:: verbose
2015
2015
2016 Examples:
2016 Examples:
2017
2017
2018 - forget newly-added binary files::
2018 - forget newly-added binary files::
2019
2019
2020 hg forget "set:added() and binary()"
2020 hg forget "set:added() and binary()"
2021
2021
2022 - forget files that would be excluded by .hgignore::
2022 - forget files that would be excluded by .hgignore::
2023
2023
2024 hg forget "set:hgignore()"
2024 hg forget "set:hgignore()"
2025
2025
2026 Returns 0 on success.
2026 Returns 0 on success.
2027 """
2027 """
2028
2028
2029 opts = pycompat.byteskwargs(opts)
2029 opts = pycompat.byteskwargs(opts)
2030 if not pats:
2030 if not pats:
2031 raise error.Abort(_('no files specified'))
2031 raise error.Abort(_('no files specified'))
2032
2032
2033 m = scmutil.match(repo[None], pats, opts)
2033 m = scmutil.match(repo[None], pats, opts)
2034 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2034 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2035 return rejected and 1 or 0
2035 return rejected and 1 or 0
2036
2036
2037 @command(
2037 @command(
2038 'graft',
2038 'graft',
2039 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2039 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2040 ('c', 'continue', False, _('resume interrupted graft')),
2040 ('c', 'continue', False, _('resume interrupted graft')),
2041 ('e', 'edit', False, _('invoke editor on commit messages')),
2041 ('e', 'edit', False, _('invoke editor on commit messages')),
2042 ('', 'log', None, _('append graft info to log message')),
2042 ('', 'log', None, _('append graft info to log message')),
2043 ('f', 'force', False, _('force graft')),
2043 ('f', 'force', False, _('force graft')),
2044 ('D', 'currentdate', False,
2044 ('D', 'currentdate', False,
2045 _('record the current date as commit date')),
2045 _('record the current date as commit date')),
2046 ('U', 'currentuser', False,
2046 ('U', 'currentuser', False,
2047 _('record the current user as committer'), _('DATE'))]
2047 _('record the current user as committer'), _('DATE'))]
2048 + commitopts2 + mergetoolopts + dryrunopts,
2048 + commitopts2 + mergetoolopts + dryrunopts,
2049 _('[OPTION]... [-r REV]... REV...'))
2049 _('[OPTION]... [-r REV]... REV...'))
2050 def graft(ui, repo, *revs, **opts):
2050 def graft(ui, repo, *revs, **opts):
2051 '''copy changes from other branches onto the current branch
2051 '''copy changes from other branches onto the current branch
2052
2052
2053 This command uses Mercurial's merge logic to copy individual
2053 This command uses Mercurial's merge logic to copy individual
2054 changes from other branches without merging branches in the
2054 changes from other branches without merging branches in the
2055 history graph. This is sometimes known as 'backporting' or
2055 history graph. This is sometimes known as 'backporting' or
2056 'cherry-picking'. By default, graft will copy user, date, and
2056 'cherry-picking'. By default, graft will copy user, date, and
2057 description from the source changesets.
2057 description from the source changesets.
2058
2058
2059 Changesets that are ancestors of the current revision, that have
2059 Changesets that are ancestors of the current revision, that have
2060 already been grafted, or that are merges will be skipped.
2060 already been grafted, or that are merges will be skipped.
2061
2061
2062 If --log is specified, log messages will have a comment appended
2062 If --log is specified, log messages will have a comment appended
2063 of the form::
2063 of the form::
2064
2064
2065 (grafted from CHANGESETHASH)
2065 (grafted from CHANGESETHASH)
2066
2066
2067 If --force is specified, revisions will be grafted even if they
2067 If --force is specified, revisions will be grafted even if they
2068 are already ancestors of or have been grafted to the destination.
2068 are already ancestors of or have been grafted to the destination.
2069 This is useful when the revisions have since been backed out.
2069 This is useful when the revisions have since been backed out.
2070
2070
2071 If a graft merge results in conflicts, the graft process is
2071 If a graft merge results in conflicts, the graft process is
2072 interrupted so that the current merge can be manually resolved.
2072 interrupted so that the current merge can be manually resolved.
2073 Once all conflicts are addressed, the graft process can be
2073 Once all conflicts are addressed, the graft process can be
2074 continued with the -c/--continue option.
2074 continued with the -c/--continue option.
2075
2075
2076 .. note::
2076 .. note::
2077
2077
2078 The -c/--continue option does not reapply earlier options, except
2078 The -c/--continue option does not reapply earlier options, except
2079 for --force.
2079 for --force.
2080
2080
2081 .. container:: verbose
2081 .. container:: verbose
2082
2082
2083 Examples:
2083 Examples:
2084
2084
2085 - copy a single change to the stable branch and edit its description::
2085 - copy a single change to the stable branch and edit its description::
2086
2086
2087 hg update stable
2087 hg update stable
2088 hg graft --edit 9393
2088 hg graft --edit 9393
2089
2089
2090 - graft a range of changesets with one exception, updating dates::
2090 - graft a range of changesets with one exception, updating dates::
2091
2091
2092 hg graft -D "2085::2093 and not 2091"
2092 hg graft -D "2085::2093 and not 2091"
2093
2093
2094 - continue a graft after resolving conflicts::
2094 - continue a graft after resolving conflicts::
2095
2095
2096 hg graft -c
2096 hg graft -c
2097
2097
2098 - show the source of a grafted changeset::
2098 - show the source of a grafted changeset::
2099
2099
2100 hg log --debug -r .
2100 hg log --debug -r .
2101
2101
2102 - show revisions sorted by date::
2102 - show revisions sorted by date::
2103
2103
2104 hg log -r "sort(all(), date)"
2104 hg log -r "sort(all(), date)"
2105
2105
2106 See :hg:`help revisions` for more about specifying revisions.
2106 See :hg:`help revisions` for more about specifying revisions.
2107
2107
2108 Returns 0 on successful completion.
2108 Returns 0 on successful completion.
2109 '''
2109 '''
2110 with repo.wlock():
2110 with repo.wlock():
2111 return _dograft(ui, repo, *revs, **opts)
2111 return _dograft(ui, repo, *revs, **opts)
2112
2112
2113 def _dograft(ui, repo, *revs, **opts):
2113 def _dograft(ui, repo, *revs, **opts):
2114 opts = pycompat.byteskwargs(opts)
2114 opts = pycompat.byteskwargs(opts)
2115 if revs and opts.get('rev'):
2115 if revs and opts.get('rev'):
2116 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2116 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2117 'revision ordering!\n'))
2117 'revision ordering!\n'))
2118
2118
2119 revs = list(revs)
2119 revs = list(revs)
2120 revs.extend(opts.get('rev'))
2120 revs.extend(opts.get('rev'))
2121
2121
2122 if not opts.get('user') and opts.get('currentuser'):
2122 if not opts.get('user') and opts.get('currentuser'):
2123 opts['user'] = ui.username()
2123 opts['user'] = ui.username()
2124 if not opts.get('date') and opts.get('currentdate'):
2124 if not opts.get('date') and opts.get('currentdate'):
2125 opts['date'] = "%d %d" % util.makedate()
2125 opts['date'] = "%d %d" % util.makedate()
2126
2126
2127 editor = cmdutil.getcommiteditor(editform='graft',
2127 editor = cmdutil.getcommiteditor(editform='graft',
2128 **pycompat.strkwargs(opts))
2128 **pycompat.strkwargs(opts))
2129
2129
2130 cont = False
2130 cont = False
2131 if opts.get('continue'):
2131 if opts.get('continue'):
2132 cont = True
2132 cont = True
2133 if revs:
2133 if revs:
2134 raise error.Abort(_("can't specify --continue and revisions"))
2134 raise error.Abort(_("can't specify --continue and revisions"))
2135 # read in unfinished revisions
2135 # read in unfinished revisions
2136 try:
2136 try:
2137 nodes = repo.vfs.read('graftstate').splitlines()
2137 nodes = repo.vfs.read('graftstate').splitlines()
2138 revs = [repo[node].rev() for node in nodes]
2138 revs = [repo[node].rev() for node in nodes]
2139 except IOError as inst:
2139 except IOError as inst:
2140 if inst.errno != errno.ENOENT:
2140 if inst.errno != errno.ENOENT:
2141 raise
2141 raise
2142 cmdutil.wrongtooltocontinue(repo, _('graft'))
2142 cmdutil.wrongtooltocontinue(repo, _('graft'))
2143 else:
2143 else:
2144 cmdutil.checkunfinished(repo)
2144 cmdutil.checkunfinished(repo)
2145 cmdutil.bailifchanged(repo)
2145 cmdutil.bailifchanged(repo)
2146 if not revs:
2146 if not revs:
2147 raise error.Abort(_('no revisions specified'))
2147 raise error.Abort(_('no revisions specified'))
2148 revs = scmutil.revrange(repo, revs)
2148 revs = scmutil.revrange(repo, revs)
2149
2149
2150 skipped = set()
2150 skipped = set()
2151 # check for merges
2151 # check for merges
2152 for rev in repo.revs('%ld and merge()', revs):
2152 for rev in repo.revs('%ld and merge()', revs):
2153 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
2153 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
2154 skipped.add(rev)
2154 skipped.add(rev)
2155 revs = [r for r in revs if r not in skipped]
2155 revs = [r for r in revs if r not in skipped]
2156 if not revs:
2156 if not revs:
2157 return -1
2157 return -1
2158
2158
2159 # Don't check in the --continue case, in effect retaining --force across
2159 # Don't check in the --continue case, in effect retaining --force across
2160 # --continues. That's because without --force, any revisions we decided to
2160 # --continues. That's because without --force, any revisions we decided to
2161 # skip would have been filtered out here, so they wouldn't have made their
2161 # skip would have been filtered out here, so they wouldn't have made their
2162 # way to the graftstate. With --force, any revisions we would have otherwise
2162 # way to the graftstate. With --force, any revisions we would have otherwise
2163 # skipped would not have been filtered out, and if they hadn't been applied
2163 # skipped would not have been filtered out, and if they hadn't been applied
2164 # already, they'd have been in the graftstate.
2164 # already, they'd have been in the graftstate.
2165 if not (cont or opts.get('force')):
2165 if not (cont or opts.get('force')):
2166 # check for ancestors of dest branch
2166 # check for ancestors of dest branch
2167 crev = repo['.'].rev()
2167 crev = repo['.'].rev()
2168 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2168 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2169 # XXX make this lazy in the future
2169 # XXX make this lazy in the future
2170 # don't mutate while iterating, create a copy
2170 # don't mutate while iterating, create a copy
2171 for rev in list(revs):
2171 for rev in list(revs):
2172 if rev in ancestors:
2172 if rev in ancestors:
2173 ui.warn(_('skipping ancestor revision %d:%s\n') %
2173 ui.warn(_('skipping ancestor revision %d:%s\n') %
2174 (rev, repo[rev]))
2174 (rev, repo[rev]))
2175 # XXX remove on list is slow
2175 # XXX remove on list is slow
2176 revs.remove(rev)
2176 revs.remove(rev)
2177 if not revs:
2177 if not revs:
2178 return -1
2178 return -1
2179
2179
2180 # analyze revs for earlier grafts
2180 # analyze revs for earlier grafts
2181 ids = {}
2181 ids = {}
2182 for ctx in repo.set("%ld", revs):
2182 for ctx in repo.set("%ld", revs):
2183 ids[ctx.hex()] = ctx.rev()
2183 ids[ctx.hex()] = ctx.rev()
2184 n = ctx.extra().get('source')
2184 n = ctx.extra().get('source')
2185 if n:
2185 if n:
2186 ids[n] = ctx.rev()
2186 ids[n] = ctx.rev()
2187
2187
2188 # check ancestors for earlier grafts
2188 # check ancestors for earlier grafts
2189 ui.debug('scanning for duplicate grafts\n')
2189 ui.debug('scanning for duplicate grafts\n')
2190
2190
2191 # The only changesets we can be sure doesn't contain grafts of any
2191 # The only changesets we can be sure doesn't contain grafts of any
2192 # revs, are the ones that are common ancestors of *all* revs:
2192 # revs, are the ones that are common ancestors of *all* revs:
2193 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2193 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2194 ctx = repo[rev]
2194 ctx = repo[rev]
2195 n = ctx.extra().get('source')
2195 n = ctx.extra().get('source')
2196 if n in ids:
2196 if n in ids:
2197 try:
2197 try:
2198 r = repo[n].rev()
2198 r = repo[n].rev()
2199 except error.RepoLookupError:
2199 except error.RepoLookupError:
2200 r = None
2200 r = None
2201 if r in revs:
2201 if r in revs:
2202 ui.warn(_('skipping revision %d:%s '
2202 ui.warn(_('skipping revision %d:%s '
2203 '(already grafted to %d:%s)\n')
2203 '(already grafted to %d:%s)\n')
2204 % (r, repo[r], rev, ctx))
2204 % (r, repo[r], rev, ctx))
2205 revs.remove(r)
2205 revs.remove(r)
2206 elif ids[n] in revs:
2206 elif ids[n] in revs:
2207 if r is None:
2207 if r is None:
2208 ui.warn(_('skipping already grafted revision %d:%s '
2208 ui.warn(_('skipping already grafted revision %d:%s '
2209 '(%d:%s also has unknown origin %s)\n')
2209 '(%d:%s also has unknown origin %s)\n')
2210 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2210 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2211 else:
2211 else:
2212 ui.warn(_('skipping already grafted revision %d:%s '
2212 ui.warn(_('skipping already grafted revision %d:%s '
2213 '(%d:%s also has origin %d:%s)\n')
2213 '(%d:%s also has origin %d:%s)\n')
2214 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2214 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2215 revs.remove(ids[n])
2215 revs.remove(ids[n])
2216 elif ctx.hex() in ids:
2216 elif ctx.hex() in ids:
2217 r = ids[ctx.hex()]
2217 r = ids[ctx.hex()]
2218 ui.warn(_('skipping already grafted revision %d:%s '
2218 ui.warn(_('skipping already grafted revision %d:%s '
2219 '(was grafted from %d:%s)\n') %
2219 '(was grafted from %d:%s)\n') %
2220 (r, repo[r], rev, ctx))
2220 (r, repo[r], rev, ctx))
2221 revs.remove(r)
2221 revs.remove(r)
2222 if not revs:
2222 if not revs:
2223 return -1
2223 return -1
2224
2224
2225 for pos, ctx in enumerate(repo.set("%ld", revs)):
2225 for pos, ctx in enumerate(repo.set("%ld", revs)):
2226 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2226 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2227 ctx.description().split('\n', 1)[0])
2227 ctx.description().split('\n', 1)[0])
2228 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2228 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2229 if names:
2229 if names:
2230 desc += ' (%s)' % ' '.join(names)
2230 desc += ' (%s)' % ' '.join(names)
2231 ui.status(_('grafting %s\n') % desc)
2231 ui.status(_('grafting %s\n') % desc)
2232 if opts.get('dry_run'):
2232 if opts.get('dry_run'):
2233 continue
2233 continue
2234
2234
2235 source = ctx.extra().get('source')
2235 source = ctx.extra().get('source')
2236 extra = {}
2236 extra = {}
2237 if source:
2237 if source:
2238 extra['source'] = source
2238 extra['source'] = source
2239 extra['intermediate-source'] = ctx.hex()
2239 extra['intermediate-source'] = ctx.hex()
2240 else:
2240 else:
2241 extra['source'] = ctx.hex()
2241 extra['source'] = ctx.hex()
2242 user = ctx.user()
2242 user = ctx.user()
2243 if opts.get('user'):
2243 if opts.get('user'):
2244 user = opts['user']
2244 user = opts['user']
2245 date = ctx.date()
2245 date = ctx.date()
2246 if opts.get('date'):
2246 if opts.get('date'):
2247 date = opts['date']
2247 date = opts['date']
2248 message = ctx.description()
2248 message = ctx.description()
2249 if opts.get('log'):
2249 if opts.get('log'):
2250 message += '\n(grafted from %s)' % ctx.hex()
2250 message += '\n(grafted from %s)' % ctx.hex()
2251
2251
2252 # we don't merge the first commit when continuing
2252 # we don't merge the first commit when continuing
2253 if not cont:
2253 if not cont:
2254 # perform the graft merge with p1(rev) as 'ancestor'
2254 # perform the graft merge with p1(rev) as 'ancestor'
2255 try:
2255 try:
2256 # ui.forcemerge is an internal variable, do not document
2256 # ui.forcemerge is an internal variable, do not document
2257 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
2257 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
2258 'graft')
2258 'graft')
2259 stats = mergemod.graft(repo, ctx, ctx.p1(),
2259 stats = mergemod.graft(repo, ctx, ctx.p1(),
2260 ['local', 'graft'])
2260 ['local', 'graft'])
2261 finally:
2261 finally:
2262 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
2262 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
2263 # report any conflicts
2263 # report any conflicts
2264 if stats and stats[3] > 0:
2264 if stats and stats[3] > 0:
2265 # write out state for --continue
2265 # write out state for --continue
2266 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2266 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2267 repo.vfs.write('graftstate', ''.join(nodelines))
2267 repo.vfs.write('graftstate', ''.join(nodelines))
2268 extra = ''
2268 extra = ''
2269 if opts.get('user'):
2269 if opts.get('user'):
2270 extra += ' --user %s' % util.shellquote(opts['user'])
2270 extra += ' --user %s' % util.shellquote(opts['user'])
2271 if opts.get('date'):
2271 if opts.get('date'):
2272 extra += ' --date %s' % util.shellquote(opts['date'])
2272 extra += ' --date %s' % util.shellquote(opts['date'])
2273 if opts.get('log'):
2273 if opts.get('log'):
2274 extra += ' --log'
2274 extra += ' --log'
2275 hint=_("use 'hg resolve' and 'hg graft --continue%s'") % extra
2275 hint=_("use 'hg resolve' and 'hg graft --continue%s'") % extra
2276 raise error.Abort(
2276 raise error.Abort(
2277 _("unresolved conflicts, can't continue"),
2277 _("unresolved conflicts, can't continue"),
2278 hint=hint)
2278 hint=hint)
2279 else:
2279 else:
2280 cont = False
2280 cont = False
2281
2281
2282 # commit
2282 # commit
2283 node = repo.commit(text=message, user=user,
2283 node = repo.commit(text=message, user=user,
2284 date=date, extra=extra, editor=editor)
2284 date=date, extra=extra, editor=editor)
2285 if node is None:
2285 if node is None:
2286 ui.warn(
2286 ui.warn(
2287 _('note: graft of %d:%s created no changes to commit\n') %
2287 _('note: graft of %d:%s created no changes to commit\n') %
2288 (ctx.rev(), ctx))
2288 (ctx.rev(), ctx))
2289
2289
2290 # remove state when we complete successfully
2290 # remove state when we complete successfully
2291 if not opts.get('dry_run'):
2291 if not opts.get('dry_run'):
2292 repo.vfs.unlinkpath('graftstate', ignoremissing=True)
2292 repo.vfs.unlinkpath('graftstate', ignoremissing=True)
2293
2293
2294 return 0
2294 return 0
2295
2295
2296 @command('grep',
2296 @command('grep',
2297 [('0', 'print0', None, _('end fields with NUL')),
2297 [('0', 'print0', None, _('end fields with NUL')),
2298 ('', 'all', None, _('print all revisions that match')),
2298 ('', 'all', None, _('print all revisions that match')),
2299 ('a', 'text', None, _('treat all files as text')),
2299 ('a', 'text', None, _('treat all files as text')),
2300 ('f', 'follow', None,
2300 ('f', 'follow', None,
2301 _('follow changeset history,'
2301 _('follow changeset history,'
2302 ' or file history across copies and renames')),
2302 ' or file history across copies and renames')),
2303 ('i', 'ignore-case', None, _('ignore case when matching')),
2303 ('i', 'ignore-case', None, _('ignore case when matching')),
2304 ('l', 'files-with-matches', None,
2304 ('l', 'files-with-matches', None,
2305 _('print only filenames and revisions that match')),
2305 _('print only filenames and revisions that match')),
2306 ('n', 'line-number', None, _('print matching line numbers')),
2306 ('n', 'line-number', None, _('print matching line numbers')),
2307 ('r', 'rev', [],
2307 ('r', 'rev', [],
2308 _('only search files changed within revision range'), _('REV')),
2308 _('only search files changed within revision range'), _('REV')),
2309 ('u', 'user', None, _('list the author (long with -v)')),
2309 ('u', 'user', None, _('list the author (long with -v)')),
2310 ('d', 'date', None, _('list the date (short with -q)')),
2310 ('d', 'date', None, _('list the date (short with -q)')),
2311 ] + formatteropts + walkopts,
2311 ] + formatteropts + walkopts,
2312 _('[OPTION]... PATTERN [FILE]...'),
2312 _('[OPTION]... PATTERN [FILE]...'),
2313 inferrepo=True)
2313 inferrepo=True)
2314 def grep(ui, repo, pattern, *pats, **opts):
2314 def grep(ui, repo, pattern, *pats, **opts):
2315 """search revision history for a pattern in specified files
2315 """search revision history for a pattern in specified files
2316
2316
2317 Search revision history for a regular expression in the specified
2317 Search revision history for a regular expression in the specified
2318 files or the entire project.
2318 files or the entire project.
2319
2319
2320 By default, grep prints the most recent revision number for each
2320 By default, grep prints the most recent revision number for each
2321 file in which it finds a match. To get it to print every revision
2321 file in which it finds a match. To get it to print every revision
2322 that contains a change in match status ("-" for a match that becomes
2322 that contains a change in match status ("-" for a match that becomes
2323 a non-match, or "+" for a non-match that becomes a match), use the
2323 a non-match, or "+" for a non-match that becomes a match), use the
2324 --all flag.
2324 --all flag.
2325
2325
2326 PATTERN can be any Python (roughly Perl-compatible) regular
2326 PATTERN can be any Python (roughly Perl-compatible) regular
2327 expression.
2327 expression.
2328
2328
2329 If no FILEs are specified (and -f/--follow isn't set), all files in
2329 If no FILEs are specified (and -f/--follow isn't set), all files in
2330 the repository are searched, including those that don't exist in the
2330 the repository are searched, including those that don't exist in the
2331 current branch or have been deleted in a prior changeset.
2331 current branch or have been deleted in a prior changeset.
2332
2332
2333 Returns 0 if a match is found, 1 otherwise.
2333 Returns 0 if a match is found, 1 otherwise.
2334 """
2334 """
2335 opts = pycompat.byteskwargs(opts)
2335 opts = pycompat.byteskwargs(opts)
2336 reflags = re.M
2336 reflags = re.M
2337 if opts.get('ignore_case'):
2337 if opts.get('ignore_case'):
2338 reflags |= re.I
2338 reflags |= re.I
2339 try:
2339 try:
2340 regexp = util.re.compile(pattern, reflags)
2340 regexp = util.re.compile(pattern, reflags)
2341 except re.error as inst:
2341 except re.error as inst:
2342 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2342 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2343 return 1
2343 return 1
2344 sep, eol = ':', '\n'
2344 sep, eol = ':', '\n'
2345 if opts.get('print0'):
2345 if opts.get('print0'):
2346 sep = eol = '\0'
2346 sep = eol = '\0'
2347
2347
2348 getfile = util.lrucachefunc(repo.file)
2348 getfile = util.lrucachefunc(repo.file)
2349
2349
2350 def matchlines(body):
2350 def matchlines(body):
2351 begin = 0
2351 begin = 0
2352 linenum = 0
2352 linenum = 0
2353 while begin < len(body):
2353 while begin < len(body):
2354 match = regexp.search(body, begin)
2354 match = regexp.search(body, begin)
2355 if not match:
2355 if not match:
2356 break
2356 break
2357 mstart, mend = match.span()
2357 mstart, mend = match.span()
2358 linenum += body.count('\n', begin, mstart) + 1
2358 linenum += body.count('\n', begin, mstart) + 1
2359 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2359 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2360 begin = body.find('\n', mend) + 1 or len(body) + 1
2360 begin = body.find('\n', mend) + 1 or len(body) + 1
2361 lend = begin - 1
2361 lend = begin - 1
2362 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2362 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2363
2363
2364 class linestate(object):
2364 class linestate(object):
2365 def __init__(self, line, linenum, colstart, colend):
2365 def __init__(self, line, linenum, colstart, colend):
2366 self.line = line
2366 self.line = line
2367 self.linenum = linenum
2367 self.linenum = linenum
2368 self.colstart = colstart
2368 self.colstart = colstart
2369 self.colend = colend
2369 self.colend = colend
2370
2370
2371 def __hash__(self):
2371 def __hash__(self):
2372 return hash((self.linenum, self.line))
2372 return hash((self.linenum, self.line))
2373
2373
2374 def __eq__(self, other):
2374 def __eq__(self, other):
2375 return self.line == other.line
2375 return self.line == other.line
2376
2376
2377 def findpos(self):
2377 def findpos(self):
2378 """Iterate all (start, end) indices of matches"""
2378 """Iterate all (start, end) indices of matches"""
2379 yield self.colstart, self.colend
2379 yield self.colstart, self.colend
2380 p = self.colend
2380 p = self.colend
2381 while p < len(self.line):
2381 while p < len(self.line):
2382 m = regexp.search(self.line, p)
2382 m = regexp.search(self.line, p)
2383 if not m:
2383 if not m:
2384 break
2384 break
2385 yield m.span()
2385 yield m.span()
2386 p = m.end()
2386 p = m.end()
2387
2387
2388 matches = {}
2388 matches = {}
2389 copies = {}
2389 copies = {}
2390 def grepbody(fn, rev, body):
2390 def grepbody(fn, rev, body):
2391 matches[rev].setdefault(fn, [])
2391 matches[rev].setdefault(fn, [])
2392 m = matches[rev][fn]
2392 m = matches[rev][fn]
2393 for lnum, cstart, cend, line in matchlines(body):
2393 for lnum, cstart, cend, line in matchlines(body):
2394 s = linestate(line, lnum, cstart, cend)
2394 s = linestate(line, lnum, cstart, cend)
2395 m.append(s)
2395 m.append(s)
2396
2396
2397 def difflinestates(a, b):
2397 def difflinestates(a, b):
2398 sm = difflib.SequenceMatcher(None, a, b)
2398 sm = difflib.SequenceMatcher(None, a, b)
2399 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2399 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2400 if tag == 'insert':
2400 if tag == 'insert':
2401 for i in xrange(blo, bhi):
2401 for i in xrange(blo, bhi):
2402 yield ('+', b[i])
2402 yield ('+', b[i])
2403 elif tag == 'delete':
2403 elif tag == 'delete':
2404 for i in xrange(alo, ahi):
2404 for i in xrange(alo, ahi):
2405 yield ('-', a[i])
2405 yield ('-', a[i])
2406 elif tag == 'replace':
2406 elif tag == 'replace':
2407 for i in xrange(alo, ahi):
2407 for i in xrange(alo, ahi):
2408 yield ('-', a[i])
2408 yield ('-', a[i])
2409 for i in xrange(blo, bhi):
2409 for i in xrange(blo, bhi):
2410 yield ('+', b[i])
2410 yield ('+', b[i])
2411
2411
2412 def display(fm, fn, ctx, pstates, states):
2412 def display(fm, fn, ctx, pstates, states):
2413 rev = ctx.rev()
2413 rev = ctx.rev()
2414 if fm.isplain():
2414 if fm.isplain():
2415 formatuser = ui.shortuser
2415 formatuser = ui.shortuser
2416 else:
2416 else:
2417 formatuser = str
2417 formatuser = str
2418 if ui.quiet:
2418 if ui.quiet:
2419 datefmt = '%Y-%m-%d'
2419 datefmt = '%Y-%m-%d'
2420 else:
2420 else:
2421 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2421 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2422 found = False
2422 found = False
2423 @util.cachefunc
2423 @util.cachefunc
2424 def binary():
2424 def binary():
2425 flog = getfile(fn)
2425 flog = getfile(fn)
2426 return util.binary(flog.read(ctx.filenode(fn)))
2426 return util.binary(flog.read(ctx.filenode(fn)))
2427
2427
2428 fieldnamemap = {'filename': 'file', 'linenumber': 'line_number'}
2428 fieldnamemap = {'filename': 'file', 'linenumber': 'line_number'}
2429 if opts.get('all'):
2429 if opts.get('all'):
2430 iter = difflinestates(pstates, states)
2430 iter = difflinestates(pstates, states)
2431 else:
2431 else:
2432 iter = [('', l) for l in states]
2432 iter = [('', l) for l in states]
2433 for change, l in iter:
2433 for change, l in iter:
2434 fm.startitem()
2434 fm.startitem()
2435 fm.data(node=fm.hexfunc(ctx.node()))
2435 fm.data(node=fm.hexfunc(ctx.node()))
2436 cols = [
2436 cols = [
2437 ('filename', fn, True),
2437 ('filename', fn, True),
2438 ('rev', rev, True),
2438 ('rev', rev, True),
2439 ('linenumber', l.linenum, opts.get('line_number')),
2439 ('linenumber', l.linenum, opts.get('line_number')),
2440 ]
2440 ]
2441 if opts.get('all'):
2441 if opts.get('all'):
2442 cols.append(('change', change, True))
2442 cols.append(('change', change, True))
2443 cols.extend([
2443 cols.extend([
2444 ('user', formatuser(ctx.user()), opts.get('user')),
2444 ('user', formatuser(ctx.user()), opts.get('user')),
2445 ('date', fm.formatdate(ctx.date(), datefmt), opts.get('date')),
2445 ('date', fm.formatdate(ctx.date(), datefmt), opts.get('date')),
2446 ])
2446 ])
2447 lastcol = next(name for name, data, cond in reversed(cols) if cond)
2447 lastcol = next(name for name, data, cond in reversed(cols) if cond)
2448 for name, data, cond in cols:
2448 for name, data, cond in cols:
2449 field = fieldnamemap.get(name, name)
2449 field = fieldnamemap.get(name, name)
2450 fm.condwrite(cond, field, '%s', data, label='grep.%s' % name)
2450 fm.condwrite(cond, field, '%s', data, label='grep.%s' % name)
2451 if cond and name != lastcol:
2451 if cond and name != lastcol:
2452 fm.plain(sep, label='grep.sep')
2452 fm.plain(sep, label='grep.sep')
2453 if not opts.get('files_with_matches'):
2453 if not opts.get('files_with_matches'):
2454 fm.plain(sep, label='grep.sep')
2454 fm.plain(sep, label='grep.sep')
2455 if not opts.get('text') and binary():
2455 if not opts.get('text') and binary():
2456 fm.plain(_(" Binary file matches"))
2456 fm.plain(_(" Binary file matches"))
2457 else:
2457 else:
2458 displaymatches(fm.nested('texts'), l)
2458 displaymatches(fm.nested('texts'), l)
2459 fm.plain(eol)
2459 fm.plain(eol)
2460 found = True
2460 found = True
2461 if opts.get('files_with_matches'):
2461 if opts.get('files_with_matches'):
2462 break
2462 break
2463 return found
2463 return found
2464
2464
2465 def displaymatches(fm, l):
2465 def displaymatches(fm, l):
2466 p = 0
2466 p = 0
2467 for s, e in l.findpos():
2467 for s, e in l.findpos():
2468 if p < s:
2468 if p < s:
2469 fm.startitem()
2469 fm.startitem()
2470 fm.write('text', '%s', l.line[p:s])
2470 fm.write('text', '%s', l.line[p:s])
2471 fm.data(matched=False)
2471 fm.data(matched=False)
2472 fm.startitem()
2472 fm.startitem()
2473 fm.write('text', '%s', l.line[s:e], label='grep.match')
2473 fm.write('text', '%s', l.line[s:e], label='grep.match')
2474 fm.data(matched=True)
2474 fm.data(matched=True)
2475 p = e
2475 p = e
2476 if p < len(l.line):
2476 if p < len(l.line):
2477 fm.startitem()
2477 fm.startitem()
2478 fm.write('text', '%s', l.line[p:])
2478 fm.write('text', '%s', l.line[p:])
2479 fm.data(matched=False)
2479 fm.data(matched=False)
2480 fm.end()
2480 fm.end()
2481
2481
2482 skip = {}
2482 skip = {}
2483 revfiles = {}
2483 revfiles = {}
2484 matchfn = scmutil.match(repo[None], pats, opts)
2484 matchfn = scmutil.match(repo[None], pats, opts)
2485 found = False
2485 found = False
2486 follow = opts.get('follow')
2486 follow = opts.get('follow')
2487
2487
2488 def prep(ctx, fns):
2488 def prep(ctx, fns):
2489 rev = ctx.rev()
2489 rev = ctx.rev()
2490 pctx = ctx.p1()
2490 pctx = ctx.p1()
2491 parent = pctx.rev()
2491 parent = pctx.rev()
2492 matches.setdefault(rev, {})
2492 matches.setdefault(rev, {})
2493 matches.setdefault(parent, {})
2493 matches.setdefault(parent, {})
2494 files = revfiles.setdefault(rev, [])
2494 files = revfiles.setdefault(rev, [])
2495 for fn in fns:
2495 for fn in fns:
2496 flog = getfile(fn)
2496 flog = getfile(fn)
2497 try:
2497 try:
2498 fnode = ctx.filenode(fn)
2498 fnode = ctx.filenode(fn)
2499 except error.LookupError:
2499 except error.LookupError:
2500 continue
2500 continue
2501
2501
2502 copied = flog.renamed(fnode)
2502 copied = flog.renamed(fnode)
2503 copy = follow and copied and copied[0]
2503 copy = follow and copied and copied[0]
2504 if copy:
2504 if copy:
2505 copies.setdefault(rev, {})[fn] = copy
2505 copies.setdefault(rev, {})[fn] = copy
2506 if fn in skip:
2506 if fn in skip:
2507 if copy:
2507 if copy:
2508 skip[copy] = True
2508 skip[copy] = True
2509 continue
2509 continue
2510 files.append(fn)
2510 files.append(fn)
2511
2511
2512 if fn not in matches[rev]:
2512 if fn not in matches[rev]:
2513 grepbody(fn, rev, flog.read(fnode))
2513 grepbody(fn, rev, flog.read(fnode))
2514
2514
2515 pfn = copy or fn
2515 pfn = copy or fn
2516 if pfn not in matches[parent]:
2516 if pfn not in matches[parent]:
2517 try:
2517 try:
2518 fnode = pctx.filenode(pfn)
2518 fnode = pctx.filenode(pfn)
2519 grepbody(pfn, parent, flog.read(fnode))
2519 grepbody(pfn, parent, flog.read(fnode))
2520 except error.LookupError:
2520 except error.LookupError:
2521 pass
2521 pass
2522
2522
2523 ui.pager('grep')
2523 ui.pager('grep')
2524 fm = ui.formatter('grep', opts)
2524 fm = ui.formatter('grep', opts)
2525 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2525 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2526 rev = ctx.rev()
2526 rev = ctx.rev()
2527 parent = ctx.p1().rev()
2527 parent = ctx.p1().rev()
2528 for fn in sorted(revfiles.get(rev, [])):
2528 for fn in sorted(revfiles.get(rev, [])):
2529 states = matches[rev][fn]
2529 states = matches[rev][fn]
2530 copy = copies.get(rev, {}).get(fn)
2530 copy = copies.get(rev, {}).get(fn)
2531 if fn in skip:
2531 if fn in skip:
2532 if copy:
2532 if copy:
2533 skip[copy] = True
2533 skip[copy] = True
2534 continue
2534 continue
2535 pstates = matches.get(parent, {}).get(copy or fn, [])
2535 pstates = matches.get(parent, {}).get(copy or fn, [])
2536 if pstates or states:
2536 if pstates or states:
2537 r = display(fm, fn, ctx, pstates, states)
2537 r = display(fm, fn, ctx, pstates, states)
2538 found = found or r
2538 found = found or r
2539 if r and not opts.get('all'):
2539 if r and not opts.get('all'):
2540 skip[fn] = True
2540 skip[fn] = True
2541 if copy:
2541 if copy:
2542 skip[copy] = True
2542 skip[copy] = True
2543 del matches[rev]
2543 del matches[rev]
2544 del revfiles[rev]
2544 del revfiles[rev]
2545 fm.end()
2545 fm.end()
2546
2546
2547 return not found
2547 return not found
2548
2548
2549 @command('heads',
2549 @command('heads',
2550 [('r', 'rev', '',
2550 [('r', 'rev', '',
2551 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2551 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2552 ('t', 'topo', False, _('show topological heads only')),
2552 ('t', 'topo', False, _('show topological heads only')),
2553 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2553 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2554 ('c', 'closed', False, _('show normal and closed branch heads')),
2554 ('c', 'closed', False, _('show normal and closed branch heads')),
2555 ] + templateopts,
2555 ] + templateopts,
2556 _('[-ct] [-r STARTREV] [REV]...'))
2556 _('[-ct] [-r STARTREV] [REV]...'))
2557 def heads(ui, repo, *branchrevs, **opts):
2557 def heads(ui, repo, *branchrevs, **opts):
2558 """show branch heads
2558 """show branch heads
2559
2559
2560 With no arguments, show all open branch heads in the repository.
2560 With no arguments, show all open branch heads in the repository.
2561 Branch heads are changesets that have no descendants on the
2561 Branch heads are changesets that have no descendants on the
2562 same branch. They are where development generally takes place and
2562 same branch. They are where development generally takes place and
2563 are the usual targets for update and merge operations.
2563 are the usual targets for update and merge operations.
2564
2564
2565 If one or more REVs are given, only open branch heads on the
2565 If one or more REVs are given, only open branch heads on the
2566 branches associated with the specified changesets are shown. This
2566 branches associated with the specified changesets are shown. This
2567 means that you can use :hg:`heads .` to see the heads on the
2567 means that you can use :hg:`heads .` to see the heads on the
2568 currently checked-out branch.
2568 currently checked-out branch.
2569
2569
2570 If -c/--closed is specified, also show branch heads marked closed
2570 If -c/--closed is specified, also show branch heads marked closed
2571 (see :hg:`commit --close-branch`).
2571 (see :hg:`commit --close-branch`).
2572
2572
2573 If STARTREV is specified, only those heads that are descendants of
2573 If STARTREV is specified, only those heads that are descendants of
2574 STARTREV will be displayed.
2574 STARTREV will be displayed.
2575
2575
2576 If -t/--topo is specified, named branch mechanics will be ignored and only
2576 If -t/--topo is specified, named branch mechanics will be ignored and only
2577 topological heads (changesets with no children) will be shown.
2577 topological heads (changesets with no children) will be shown.
2578
2578
2579 Returns 0 if matching heads are found, 1 if not.
2579 Returns 0 if matching heads are found, 1 if not.
2580 """
2580 """
2581
2581
2582 opts = pycompat.byteskwargs(opts)
2582 opts = pycompat.byteskwargs(opts)
2583 start = None
2583 start = None
2584 if 'rev' in opts:
2584 if 'rev' in opts:
2585 start = scmutil.revsingle(repo, opts['rev'], None).node()
2585 start = scmutil.revsingle(repo, opts['rev'], None).node()
2586
2586
2587 if opts.get('topo'):
2587 if opts.get('topo'):
2588 heads = [repo[h] for h in repo.heads(start)]
2588 heads = [repo[h] for h in repo.heads(start)]
2589 else:
2589 else:
2590 heads = []
2590 heads = []
2591 for branch in repo.branchmap():
2591 for branch in repo.branchmap():
2592 heads += repo.branchheads(branch, start, opts.get('closed'))
2592 heads += repo.branchheads(branch, start, opts.get('closed'))
2593 heads = [repo[h] for h in heads]
2593 heads = [repo[h] for h in heads]
2594
2594
2595 if branchrevs:
2595 if branchrevs:
2596 branches = set(repo[br].branch() for br in branchrevs)
2596 branches = set(repo[br].branch() for br in branchrevs)
2597 heads = [h for h in heads if h.branch() in branches]
2597 heads = [h for h in heads if h.branch() in branches]
2598
2598
2599 if opts.get('active') and branchrevs:
2599 if opts.get('active') and branchrevs:
2600 dagheads = repo.heads(start)
2600 dagheads = repo.heads(start)
2601 heads = [h for h in heads if h.node() in dagheads]
2601 heads = [h for h in heads if h.node() in dagheads]
2602
2602
2603 if branchrevs:
2603 if branchrevs:
2604 haveheads = set(h.branch() for h in heads)
2604 haveheads = set(h.branch() for h in heads)
2605 if branches - haveheads:
2605 if branches - haveheads:
2606 headless = ', '.join(b for b in branches - haveheads)
2606 headless = ', '.join(b for b in branches - haveheads)
2607 msg = _('no open branch heads found on branches %s')
2607 msg = _('no open branch heads found on branches %s')
2608 if opts.get('rev'):
2608 if opts.get('rev'):
2609 msg += _(' (started at %s)') % opts['rev']
2609 msg += _(' (started at %s)') % opts['rev']
2610 ui.warn((msg + '\n') % headless)
2610 ui.warn((msg + '\n') % headless)
2611
2611
2612 if not heads:
2612 if not heads:
2613 return 1
2613 return 1
2614
2614
2615 ui.pager('heads')
2615 ui.pager('heads')
2616 heads = sorted(heads, key=lambda x: -x.rev())
2616 heads = sorted(heads, key=lambda x: -x.rev())
2617 displayer = cmdutil.show_changeset(ui, repo, opts)
2617 displayer = cmdutil.show_changeset(ui, repo, opts)
2618 for ctx in heads:
2618 for ctx in heads:
2619 displayer.show(ctx)
2619 displayer.show(ctx)
2620 displayer.close()
2620 displayer.close()
2621
2621
2622 @command('help',
2622 @command('help',
2623 [('e', 'extension', None, _('show only help for extensions')),
2623 [('e', 'extension', None, _('show only help for extensions')),
2624 ('c', 'command', None, _('show only help for commands')),
2624 ('c', 'command', None, _('show only help for commands')),
2625 ('k', 'keyword', None, _('show topics matching keyword')),
2625 ('k', 'keyword', None, _('show topics matching keyword')),
2626 ('s', 'system', [], _('show help for specific platform(s)')),
2626 ('s', 'system', [], _('show help for specific platform(s)')),
2627 ],
2627 ],
2628 _('[-ecks] [TOPIC]'),
2628 _('[-ecks] [TOPIC]'),
2629 norepo=True)
2629 norepo=True)
2630 def help_(ui, name=None, **opts):
2630 def help_(ui, name=None, **opts):
2631 """show help for a given topic or a help overview
2631 """show help for a given topic or a help overview
2632
2632
2633 With no arguments, print a list of commands with short help messages.
2633 With no arguments, print a list of commands with short help messages.
2634
2634
2635 Given a topic, extension, or command name, print help for that
2635 Given a topic, extension, or command name, print help for that
2636 topic.
2636 topic.
2637
2637
2638 Returns 0 if successful.
2638 Returns 0 if successful.
2639 """
2639 """
2640
2640
2641 keep = opts.get(r'system') or []
2641 keep = opts.get(r'system') or []
2642 if len(keep) == 0:
2642 if len(keep) == 0:
2643 if pycompat.sysplatform.startswith('win'):
2643 if pycompat.sysplatform.startswith('win'):
2644 keep.append('windows')
2644 keep.append('windows')
2645 elif pycompat.sysplatform == 'OpenVMS':
2645 elif pycompat.sysplatform == 'OpenVMS':
2646 keep.append('vms')
2646 keep.append('vms')
2647 elif pycompat.sysplatform == 'plan9':
2647 elif pycompat.sysplatform == 'plan9':
2648 keep.append('plan9')
2648 keep.append('plan9')
2649 else:
2649 else:
2650 keep.append('unix')
2650 keep.append('unix')
2651 keep.append(pycompat.sysplatform.lower())
2651 keep.append(pycompat.sysplatform.lower())
2652 if ui.verbose:
2652 if ui.verbose:
2653 keep.append('verbose')
2653 keep.append('verbose')
2654
2654
2655 commands = sys.modules[__name__]
2655 commands = sys.modules[__name__]
2656 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
2656 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
2657 ui.pager('help')
2657 ui.pager('help')
2658 ui.write(formatted)
2658 ui.write(formatted)
2659
2659
2660
2660
2661 @command('identify|id',
2661 @command('identify|id',
2662 [('r', 'rev', '',
2662 [('r', 'rev', '',
2663 _('identify the specified revision'), _('REV')),
2663 _('identify the specified revision'), _('REV')),
2664 ('n', 'num', None, _('show local revision number')),
2664 ('n', 'num', None, _('show local revision number')),
2665 ('i', 'id', None, _('show global revision id')),
2665 ('i', 'id', None, _('show global revision id')),
2666 ('b', 'branch', None, _('show branch')),
2666 ('b', 'branch', None, _('show branch')),
2667 ('t', 'tags', None, _('show tags')),
2667 ('t', 'tags', None, _('show tags')),
2668 ('B', 'bookmarks', None, _('show bookmarks')),
2668 ('B', 'bookmarks', None, _('show bookmarks')),
2669 ] + remoteopts + formatteropts,
2669 ] + remoteopts + formatteropts,
2670 _('[-nibtB] [-r REV] [SOURCE]'),
2670 _('[-nibtB] [-r REV] [SOURCE]'),
2671 optionalrepo=True)
2671 optionalrepo=True)
2672 def identify(ui, repo, source=None, rev=None,
2672 def identify(ui, repo, source=None, rev=None,
2673 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
2673 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
2674 """identify the working directory or specified revision
2674 """identify the working directory or specified revision
2675
2675
2676 Print a summary identifying the repository state at REV using one or
2676 Print a summary identifying the repository state at REV using one or
2677 two parent hash identifiers, followed by a "+" if the working
2677 two parent hash identifiers, followed by a "+" if the working
2678 directory has uncommitted changes, the branch name (if not default),
2678 directory has uncommitted changes, the branch name (if not default),
2679 a list of tags, and a list of bookmarks.
2679 a list of tags, and a list of bookmarks.
2680
2680
2681 When REV is not given, print a summary of the current state of the
2681 When REV is not given, print a summary of the current state of the
2682 repository.
2682 repository.
2683
2683
2684 Specifying a path to a repository root or Mercurial bundle will
2684 Specifying a path to a repository root or Mercurial bundle will
2685 cause lookup to operate on that repository/bundle.
2685 cause lookup to operate on that repository/bundle.
2686
2686
2687 .. container:: verbose
2687 .. container:: verbose
2688
2688
2689 Examples:
2689 Examples:
2690
2690
2691 - generate a build identifier for the working directory::
2691 - generate a build identifier for the working directory::
2692
2692
2693 hg id --id > build-id.dat
2693 hg id --id > build-id.dat
2694
2694
2695 - find the revision corresponding to a tag::
2695 - find the revision corresponding to a tag::
2696
2696
2697 hg id -n -r 1.3
2697 hg id -n -r 1.3
2698
2698
2699 - check the most recent revision of a remote repository::
2699 - check the most recent revision of a remote repository::
2700
2700
2701 hg id -r tip https://www.mercurial-scm.org/repo/hg/
2701 hg id -r tip https://www.mercurial-scm.org/repo/hg/
2702
2702
2703 See :hg:`log` for generating more information about specific revisions,
2703 See :hg:`log` for generating more information about specific revisions,
2704 including full hash identifiers.
2704 including full hash identifiers.
2705
2705
2706 Returns 0 if successful.
2706 Returns 0 if successful.
2707 """
2707 """
2708
2708
2709 opts = pycompat.byteskwargs(opts)
2709 opts = pycompat.byteskwargs(opts)
2710 if not repo and not source:
2710 if not repo and not source:
2711 raise error.Abort(_("there is no Mercurial repository here "
2711 raise error.Abort(_("there is no Mercurial repository here "
2712 "(.hg not found)"))
2712 "(.hg not found)"))
2713
2713
2714 if ui.debugflag:
2714 if ui.debugflag:
2715 hexfunc = hex
2715 hexfunc = hex
2716 else:
2716 else:
2717 hexfunc = short
2717 hexfunc = short
2718 default = not (num or id or branch or tags or bookmarks)
2718 default = not (num or id or branch or tags or bookmarks)
2719 output = []
2719 output = []
2720 revs = []
2720 revs = []
2721
2721
2722 if source:
2722 if source:
2723 source, branches = hg.parseurl(ui.expandpath(source))
2723 source, branches = hg.parseurl(ui.expandpath(source))
2724 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
2724 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
2725 repo = peer.local()
2725 repo = peer.local()
2726 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
2726 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
2727
2727
2728 fm = ui.formatter('identify', opts)
2728 fm = ui.formatter('identify', opts)
2729 fm.startitem()
2729 fm.startitem()
2730
2730
2731 if not repo:
2731 if not repo:
2732 if num or branch or tags:
2732 if num or branch or tags:
2733 raise error.Abort(
2733 raise error.Abort(
2734 _("can't query remote revision number, branch, or tags"))
2734 _("can't query remote revision number, branch, or tags"))
2735 if not rev and revs:
2735 if not rev and revs:
2736 rev = revs[0]
2736 rev = revs[0]
2737 if not rev:
2737 if not rev:
2738 rev = "tip"
2738 rev = "tip"
2739
2739
2740 remoterev = peer.lookup(rev)
2740 remoterev = peer.lookup(rev)
2741 hexrev = hexfunc(remoterev)
2741 hexrev = hexfunc(remoterev)
2742 if default or id:
2742 if default or id:
2743 output = [hexrev]
2743 output = [hexrev]
2744 fm.data(id=hexrev)
2744 fm.data(id=hexrev)
2745
2745
2746 def getbms():
2746 def getbms():
2747 bms = []
2747 bms = []
2748
2748
2749 if 'bookmarks' in peer.listkeys('namespaces'):
2749 if 'bookmarks' in peer.listkeys('namespaces'):
2750 hexremoterev = hex(remoterev)
2750 hexremoterev = hex(remoterev)
2751 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
2751 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
2752 if bmr == hexremoterev]
2752 if bmr == hexremoterev]
2753
2753
2754 return sorted(bms)
2754 return sorted(bms)
2755
2755
2756 bms = getbms()
2756 bms = getbms()
2757 if bookmarks:
2757 if bookmarks:
2758 output.extend(bms)
2758 output.extend(bms)
2759 elif default and not ui.quiet:
2759 elif default and not ui.quiet:
2760 # multiple bookmarks for a single parent separated by '/'
2760 # multiple bookmarks for a single parent separated by '/'
2761 bm = '/'.join(bms)
2761 bm = '/'.join(bms)
2762 if bm:
2762 if bm:
2763 output.append(bm)
2763 output.append(bm)
2764
2764
2765 fm.data(node=hex(remoterev))
2765 fm.data(node=hex(remoterev))
2766 fm.data(bookmarks=fm.formatlist(bms, name='bookmark'))
2766 fm.data(bookmarks=fm.formatlist(bms, name='bookmark'))
2767 else:
2767 else:
2768 ctx = scmutil.revsingle(repo, rev, None)
2768 ctx = scmutil.revsingle(repo, rev, None)
2769
2769
2770 if ctx.rev() is None:
2770 if ctx.rev() is None:
2771 ctx = repo[None]
2771 ctx = repo[None]
2772 parents = ctx.parents()
2772 parents = ctx.parents()
2773 taglist = []
2773 taglist = []
2774 for p in parents:
2774 for p in parents:
2775 taglist.extend(p.tags())
2775 taglist.extend(p.tags())
2776
2776
2777 dirty = ""
2777 dirty = ""
2778 if ctx.dirty(missing=True, merge=False, branch=False):
2778 if ctx.dirty(missing=True, merge=False, branch=False):
2779 dirty = '+'
2779 dirty = '+'
2780 fm.data(dirty=dirty)
2780 fm.data(dirty=dirty)
2781
2781
2782 hexoutput = [hexfunc(p.node()) for p in parents]
2782 hexoutput = [hexfunc(p.node()) for p in parents]
2783 if default or id:
2783 if default or id:
2784 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
2784 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
2785 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
2785 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
2786
2786
2787 if num:
2787 if num:
2788 numoutput = ["%d" % p.rev() for p in parents]
2788 numoutput = ["%d" % p.rev() for p in parents]
2789 output.append("%s%s" % ('+'.join(numoutput), dirty))
2789 output.append("%s%s" % ('+'.join(numoutput), dirty))
2790
2790
2791 fn = fm.nested('parents')
2791 fn = fm.nested('parents')
2792 for p in parents:
2792 for p in parents:
2793 fn.startitem()
2793 fn.startitem()
2794 fn.data(rev=p.rev())
2794 fn.data(rev=p.rev())
2795 fn.data(node=p.hex())
2795 fn.data(node=p.hex())
2796 fn.context(ctx=p)
2796 fn.context(ctx=p)
2797 fn.end()
2797 fn.end()
2798 else:
2798 else:
2799 hexoutput = hexfunc(ctx.node())
2799 hexoutput = hexfunc(ctx.node())
2800 if default or id:
2800 if default or id:
2801 output = [hexoutput]
2801 output = [hexoutput]
2802 fm.data(id=hexoutput)
2802 fm.data(id=hexoutput)
2803
2803
2804 if num:
2804 if num:
2805 output.append(pycompat.bytestr(ctx.rev()))
2805 output.append(pycompat.bytestr(ctx.rev()))
2806 taglist = ctx.tags()
2806 taglist = ctx.tags()
2807
2807
2808 if default and not ui.quiet:
2808 if default and not ui.quiet:
2809 b = ctx.branch()
2809 b = ctx.branch()
2810 if b != 'default':
2810 if b != 'default':
2811 output.append("(%s)" % b)
2811 output.append("(%s)" % b)
2812
2812
2813 # multiple tags for a single parent separated by '/'
2813 # multiple tags for a single parent separated by '/'
2814 t = '/'.join(taglist)
2814 t = '/'.join(taglist)
2815 if t:
2815 if t:
2816 output.append(t)
2816 output.append(t)
2817
2817
2818 # multiple bookmarks for a single parent separated by '/'
2818 # multiple bookmarks for a single parent separated by '/'
2819 bm = '/'.join(ctx.bookmarks())
2819 bm = '/'.join(ctx.bookmarks())
2820 if bm:
2820 if bm:
2821 output.append(bm)
2821 output.append(bm)
2822 else:
2822 else:
2823 if branch:
2823 if branch:
2824 output.append(ctx.branch())
2824 output.append(ctx.branch())
2825
2825
2826 if tags:
2826 if tags:
2827 output.extend(taglist)
2827 output.extend(taglist)
2828
2828
2829 if bookmarks:
2829 if bookmarks:
2830 output.extend(ctx.bookmarks())
2830 output.extend(ctx.bookmarks())
2831
2831
2832 fm.data(node=ctx.hex())
2832 fm.data(node=ctx.hex())
2833 fm.data(branch=ctx.branch())
2833 fm.data(branch=ctx.branch())
2834 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
2834 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
2835 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
2835 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
2836 fm.context(ctx=ctx)
2836 fm.context(ctx=ctx)
2837
2837
2838 fm.plain("%s\n" % ' '.join(output))
2838 fm.plain("%s\n" % ' '.join(output))
2839 fm.end()
2839 fm.end()
2840
2840
2841 @command('import|patch',
2841 @command('import|patch',
2842 [('p', 'strip', 1,
2842 [('p', 'strip', 1,
2843 _('directory strip option for patch. This has the same '
2843 _('directory strip option for patch. This has the same '
2844 'meaning as the corresponding patch option'), _('NUM')),
2844 'meaning as the corresponding patch option'), _('NUM')),
2845 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
2845 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
2846 ('e', 'edit', False, _('invoke editor on commit messages')),
2846 ('e', 'edit', False, _('invoke editor on commit messages')),
2847 ('f', 'force', None,
2847 ('f', 'force', None,
2848 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
2848 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
2849 ('', 'no-commit', None,
2849 ('', 'no-commit', None,
2850 _("don't commit, just update the working directory")),
2850 _("don't commit, just update the working directory")),
2851 ('', 'bypass', None,
2851 ('', 'bypass', None,
2852 _("apply patch without touching the working directory")),
2852 _("apply patch without touching the working directory")),
2853 ('', 'partial', None,
2853 ('', 'partial', None,
2854 _('commit even if some hunks fail')),
2854 _('commit even if some hunks fail')),
2855 ('', 'exact', None,
2855 ('', 'exact', None,
2856 _('abort if patch would apply lossily')),
2856 _('abort if patch would apply lossily')),
2857 ('', 'prefix', '',
2857 ('', 'prefix', '',
2858 _('apply patch to subdirectory'), _('DIR')),
2858 _('apply patch to subdirectory'), _('DIR')),
2859 ('', 'import-branch', None,
2859 ('', 'import-branch', None,
2860 _('use any branch information in patch (implied by --exact)'))] +
2860 _('use any branch information in patch (implied by --exact)'))] +
2861 commitopts + commitopts2 + similarityopts,
2861 commitopts + commitopts2 + similarityopts,
2862 _('[OPTION]... PATCH...'))
2862 _('[OPTION]... PATCH...'))
2863 def import_(ui, repo, patch1=None, *patches, **opts):
2863 def import_(ui, repo, patch1=None, *patches, **opts):
2864 """import an ordered set of patches
2864 """import an ordered set of patches
2865
2865
2866 Import a list of patches and commit them individually (unless
2866 Import a list of patches and commit them individually (unless
2867 --no-commit is specified).
2867 --no-commit is specified).
2868
2868
2869 To read a patch from standard input (stdin), use "-" as the patch
2869 To read a patch from standard input (stdin), use "-" as the patch
2870 name. If a URL is specified, the patch will be downloaded from
2870 name. If a URL is specified, the patch will be downloaded from
2871 there.
2871 there.
2872
2872
2873 Import first applies changes to the working directory (unless
2873 Import first applies changes to the working directory (unless
2874 --bypass is specified), import will abort if there are outstanding
2874 --bypass is specified), import will abort if there are outstanding
2875 changes.
2875 changes.
2876
2876
2877 Use --bypass to apply and commit patches directly to the
2877 Use --bypass to apply and commit patches directly to the
2878 repository, without affecting the working directory. Without
2878 repository, without affecting the working directory. Without
2879 --exact, patches will be applied on top of the working directory
2879 --exact, patches will be applied on top of the working directory
2880 parent revision.
2880 parent revision.
2881
2881
2882 You can import a patch straight from a mail message. Even patches
2882 You can import a patch straight from a mail message. Even patches
2883 as attachments work (to use the body part, it must have type
2883 as attachments work (to use the body part, it must have type
2884 text/plain or text/x-patch). From and Subject headers of email
2884 text/plain or text/x-patch). From and Subject headers of email
2885 message are used as default committer and commit message. All
2885 message are used as default committer and commit message. All
2886 text/plain body parts before first diff are added to the commit
2886 text/plain body parts before first diff are added to the commit
2887 message.
2887 message.
2888
2888
2889 If the imported patch was generated by :hg:`export`, user and
2889 If the imported patch was generated by :hg:`export`, user and
2890 description from patch override values from message headers and
2890 description from patch override values from message headers and
2891 body. Values given on command line with -m/--message and -u/--user
2891 body. Values given on command line with -m/--message and -u/--user
2892 override these.
2892 override these.
2893
2893
2894 If --exact is specified, import will set the working directory to
2894 If --exact is specified, import will set the working directory to
2895 the parent of each patch before applying it, and will abort if the
2895 the parent of each patch before applying it, and will abort if the
2896 resulting changeset has a different ID than the one recorded in
2896 resulting changeset has a different ID than the one recorded in
2897 the patch. This will guard against various ways that portable
2897 the patch. This will guard against various ways that portable
2898 patch formats and mail systems might fail to transfer Mercurial
2898 patch formats and mail systems might fail to transfer Mercurial
2899 data or metadata. See :hg:`bundle` for lossless transmission.
2899 data or metadata. See :hg:`bundle` for lossless transmission.
2900
2900
2901 Use --partial to ensure a changeset will be created from the patch
2901 Use --partial to ensure a changeset will be created from the patch
2902 even if some hunks fail to apply. Hunks that fail to apply will be
2902 even if some hunks fail to apply. Hunks that fail to apply will be
2903 written to a <target-file>.rej file. Conflicts can then be resolved
2903 written to a <target-file>.rej file. Conflicts can then be resolved
2904 by hand before :hg:`commit --amend` is run to update the created
2904 by hand before :hg:`commit --amend` is run to update the created
2905 changeset. This flag exists to let people import patches that
2905 changeset. This flag exists to let people import patches that
2906 partially apply without losing the associated metadata (author,
2906 partially apply without losing the associated metadata (author,
2907 date, description, ...).
2907 date, description, ...).
2908
2908
2909 .. note::
2909 .. note::
2910
2910
2911 When no hunks apply cleanly, :hg:`import --partial` will create
2911 When no hunks apply cleanly, :hg:`import --partial` will create
2912 an empty changeset, importing only the patch metadata.
2912 an empty changeset, importing only the patch metadata.
2913
2913
2914 With -s/--similarity, hg will attempt to discover renames and
2914 With -s/--similarity, hg will attempt to discover renames and
2915 copies in the patch in the same way as :hg:`addremove`.
2915 copies in the patch in the same way as :hg:`addremove`.
2916
2916
2917 It is possible to use external patch programs to perform the patch
2917 It is possible to use external patch programs to perform the patch
2918 by setting the ``ui.patch`` configuration option. For the default
2918 by setting the ``ui.patch`` configuration option. For the default
2919 internal tool, the fuzz can also be configured via ``patch.fuzz``.
2919 internal tool, the fuzz can also be configured via ``patch.fuzz``.
2920 See :hg:`help config` for more information about configuration
2920 See :hg:`help config` for more information about configuration
2921 files and how to use these options.
2921 files and how to use these options.
2922
2922
2923 See :hg:`help dates` for a list of formats valid for -d/--date.
2923 See :hg:`help dates` for a list of formats valid for -d/--date.
2924
2924
2925 .. container:: verbose
2925 .. container:: verbose
2926
2926
2927 Examples:
2927 Examples:
2928
2928
2929 - import a traditional patch from a website and detect renames::
2929 - import a traditional patch from a website and detect renames::
2930
2930
2931 hg import -s 80 http://example.com/bugfix.patch
2931 hg import -s 80 http://example.com/bugfix.patch
2932
2932
2933 - import a changeset from an hgweb server::
2933 - import a changeset from an hgweb server::
2934
2934
2935 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
2935 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
2936
2936
2937 - import all the patches in an Unix-style mbox::
2937 - import all the patches in an Unix-style mbox::
2938
2938
2939 hg import incoming-patches.mbox
2939 hg import incoming-patches.mbox
2940
2940
2941 - import patches from stdin::
2941 - import patches from stdin::
2942
2942
2943 hg import -
2943 hg import -
2944
2944
2945 - attempt to exactly restore an exported changeset (not always
2945 - attempt to exactly restore an exported changeset (not always
2946 possible)::
2946 possible)::
2947
2947
2948 hg import --exact proposed-fix.patch
2948 hg import --exact proposed-fix.patch
2949
2949
2950 - use an external tool to apply a patch which is too fuzzy for
2950 - use an external tool to apply a patch which is too fuzzy for
2951 the default internal tool.
2951 the default internal tool.
2952
2952
2953 hg import --config ui.patch="patch --merge" fuzzy.patch
2953 hg import --config ui.patch="patch --merge" fuzzy.patch
2954
2954
2955 - change the default fuzzing from 2 to a less strict 7
2955 - change the default fuzzing from 2 to a less strict 7
2956
2956
2957 hg import --config ui.fuzz=7 fuzz.patch
2957 hg import --config ui.fuzz=7 fuzz.patch
2958
2958
2959 Returns 0 on success, 1 on partial success (see --partial).
2959 Returns 0 on success, 1 on partial success (see --partial).
2960 """
2960 """
2961
2961
2962 opts = pycompat.byteskwargs(opts)
2962 opts = pycompat.byteskwargs(opts)
2963 if not patch1:
2963 if not patch1:
2964 raise error.Abort(_('need at least one patch to import'))
2964 raise error.Abort(_('need at least one patch to import'))
2965
2965
2966 patches = (patch1,) + patches
2966 patches = (patch1,) + patches
2967
2967
2968 date = opts.get('date')
2968 date = opts.get('date')
2969 if date:
2969 if date:
2970 opts['date'] = util.parsedate(date)
2970 opts['date'] = util.parsedate(date)
2971
2971
2972 exact = opts.get('exact')
2972 exact = opts.get('exact')
2973 update = not opts.get('bypass')
2973 update = not opts.get('bypass')
2974 if not update and opts.get('no_commit'):
2974 if not update and opts.get('no_commit'):
2975 raise error.Abort(_('cannot use --no-commit with --bypass'))
2975 raise error.Abort(_('cannot use --no-commit with --bypass'))
2976 try:
2976 try:
2977 sim = float(opts.get('similarity') or 0)
2977 sim = float(opts.get('similarity') or 0)
2978 except ValueError:
2978 except ValueError:
2979 raise error.Abort(_('similarity must be a number'))
2979 raise error.Abort(_('similarity must be a number'))
2980 if sim < 0 or sim > 100:
2980 if sim < 0 or sim > 100:
2981 raise error.Abort(_('similarity must be between 0 and 100'))
2981 raise error.Abort(_('similarity must be between 0 and 100'))
2982 if sim and not update:
2982 if sim and not update:
2983 raise error.Abort(_('cannot use --similarity with --bypass'))
2983 raise error.Abort(_('cannot use --similarity with --bypass'))
2984 if exact:
2984 if exact:
2985 if opts.get('edit'):
2985 if opts.get('edit'):
2986 raise error.Abort(_('cannot use --exact with --edit'))
2986 raise error.Abort(_('cannot use --exact with --edit'))
2987 if opts.get('prefix'):
2987 if opts.get('prefix'):
2988 raise error.Abort(_('cannot use --exact with --prefix'))
2988 raise error.Abort(_('cannot use --exact with --prefix'))
2989
2989
2990 base = opts["base"]
2990 base = opts["base"]
2991 wlock = dsguard = lock = tr = None
2991 wlock = dsguard = lock = tr = None
2992 msgs = []
2992 msgs = []
2993 ret = 0
2993 ret = 0
2994
2994
2995
2995
2996 try:
2996 try:
2997 wlock = repo.wlock()
2997 wlock = repo.wlock()
2998
2998
2999 if update:
2999 if update:
3000 cmdutil.checkunfinished(repo)
3000 cmdutil.checkunfinished(repo)
3001 if (exact or not opts.get('force')):
3001 if (exact or not opts.get('force')):
3002 cmdutil.bailifchanged(repo)
3002 cmdutil.bailifchanged(repo)
3003
3003
3004 if not opts.get('no_commit'):
3004 if not opts.get('no_commit'):
3005 lock = repo.lock()
3005 lock = repo.lock()
3006 tr = repo.transaction('import')
3006 tr = repo.transaction('import')
3007 else:
3007 else:
3008 dsguard = dirstateguard.dirstateguard(repo, 'import')
3008 dsguard = dirstateguard.dirstateguard(repo, 'import')
3009 parents = repo[None].parents()
3009 parents = repo[None].parents()
3010 for patchurl in patches:
3010 for patchurl in patches:
3011 if patchurl == '-':
3011 if patchurl == '-':
3012 ui.status(_('applying patch from stdin\n'))
3012 ui.status(_('applying patch from stdin\n'))
3013 patchfile = ui.fin
3013 patchfile = ui.fin
3014 patchurl = 'stdin' # for error message
3014 patchurl = 'stdin' # for error message
3015 else:
3015 else:
3016 patchurl = os.path.join(base, patchurl)
3016 patchurl = os.path.join(base, patchurl)
3017 ui.status(_('applying %s\n') % patchurl)
3017 ui.status(_('applying %s\n') % patchurl)
3018 patchfile = hg.openpath(ui, patchurl)
3018 patchfile = hg.openpath(ui, patchurl)
3019
3019
3020 haspatch = False
3020 haspatch = False
3021 for hunk in patch.split(patchfile):
3021 for hunk in patch.split(patchfile):
3022 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
3022 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
3023 parents, opts,
3023 parents, opts,
3024 msgs, hg.clean)
3024 msgs, hg.clean)
3025 if msg:
3025 if msg:
3026 haspatch = True
3026 haspatch = True
3027 ui.note(msg + '\n')
3027 ui.note(msg + '\n')
3028 if update or exact:
3028 if update or exact:
3029 parents = repo[None].parents()
3029 parents = repo[None].parents()
3030 else:
3030 else:
3031 parents = [repo[node]]
3031 parents = [repo[node]]
3032 if rej:
3032 if rej:
3033 ui.write_err(_("patch applied partially\n"))
3033 ui.write_err(_("patch applied partially\n"))
3034 ui.write_err(_("(fix the .rej files and run "
3034 ui.write_err(_("(fix the .rej files and run "
3035 "`hg commit --amend`)\n"))
3035 "`hg commit --amend`)\n"))
3036 ret = 1
3036 ret = 1
3037 break
3037 break
3038
3038
3039 if not haspatch:
3039 if not haspatch:
3040 raise error.Abort(_('%s: no diffs found') % patchurl)
3040 raise error.Abort(_('%s: no diffs found') % patchurl)
3041
3041
3042 if tr:
3042 if tr:
3043 tr.close()
3043 tr.close()
3044 if msgs:
3044 if msgs:
3045 repo.savecommitmessage('\n* * *\n'.join(msgs))
3045 repo.savecommitmessage('\n* * *\n'.join(msgs))
3046 if dsguard:
3046 if dsguard:
3047 dsguard.close()
3047 dsguard.close()
3048 return ret
3048 return ret
3049 finally:
3049 finally:
3050 if tr:
3050 if tr:
3051 tr.release()
3051 tr.release()
3052 release(lock, dsguard, wlock)
3052 release(lock, dsguard, wlock)
3053
3053
3054 @command('incoming|in',
3054 @command('incoming|in',
3055 [('f', 'force', None,
3055 [('f', 'force', None,
3056 _('run even if remote repository is unrelated')),
3056 _('run even if remote repository is unrelated')),
3057 ('n', 'newest-first', None, _('show newest record first')),
3057 ('n', 'newest-first', None, _('show newest record first')),
3058 ('', 'bundle', '',
3058 ('', 'bundle', '',
3059 _('file to store the bundles into'), _('FILE')),
3059 _('file to store the bundles into'), _('FILE')),
3060 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3060 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3061 ('B', 'bookmarks', False, _("compare bookmarks")),
3061 ('B', 'bookmarks', False, _("compare bookmarks")),
3062 ('b', 'branch', [],
3062 ('b', 'branch', [],
3063 _('a specific branch you would like to pull'), _('BRANCH')),
3063 _('a specific branch you would like to pull'), _('BRANCH')),
3064 ] + logopts + remoteopts + subrepoopts,
3064 ] + logopts + remoteopts + subrepoopts,
3065 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3065 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3066 def incoming(ui, repo, source="default", **opts):
3066 def incoming(ui, repo, source="default", **opts):
3067 """show new changesets found in source
3067 """show new changesets found in source
3068
3068
3069 Show new changesets found in the specified path/URL or the default
3069 Show new changesets found in the specified path/URL or the default
3070 pull location. These are the changesets that would have been pulled
3070 pull location. These are the changesets that would have been pulled
3071 if a pull at the time you issued this command.
3071 if a pull at the time you issued this command.
3072
3072
3073 See pull for valid source format details.
3073 See pull for valid source format details.
3074
3074
3075 .. container:: verbose
3075 .. container:: verbose
3076
3076
3077 With -B/--bookmarks, the result of bookmark comparison between
3077 With -B/--bookmarks, the result of bookmark comparison between
3078 local and remote repositories is displayed. With -v/--verbose,
3078 local and remote repositories is displayed. With -v/--verbose,
3079 status is also displayed for each bookmark like below::
3079 status is also displayed for each bookmark like below::
3080
3080
3081 BM1 01234567890a added
3081 BM1 01234567890a added
3082 BM2 1234567890ab advanced
3082 BM2 1234567890ab advanced
3083 BM3 234567890abc diverged
3083 BM3 234567890abc diverged
3084 BM4 34567890abcd changed
3084 BM4 34567890abcd changed
3085
3085
3086 The action taken locally when pulling depends on the
3086 The action taken locally when pulling depends on the
3087 status of each bookmark:
3087 status of each bookmark:
3088
3088
3089 :``added``: pull will create it
3089 :``added``: pull will create it
3090 :``advanced``: pull will update it
3090 :``advanced``: pull will update it
3091 :``diverged``: pull will create a divergent bookmark
3091 :``diverged``: pull will create a divergent bookmark
3092 :``changed``: result depends on remote changesets
3092 :``changed``: result depends on remote changesets
3093
3093
3094 From the point of view of pulling behavior, bookmark
3094 From the point of view of pulling behavior, bookmark
3095 existing only in the remote repository are treated as ``added``,
3095 existing only in the remote repository are treated as ``added``,
3096 even if it is in fact locally deleted.
3096 even if it is in fact locally deleted.
3097
3097
3098 .. container:: verbose
3098 .. container:: verbose
3099
3099
3100 For remote repository, using --bundle avoids downloading the
3100 For remote repository, using --bundle avoids downloading the
3101 changesets twice if the incoming is followed by a pull.
3101 changesets twice if the incoming is followed by a pull.
3102
3102
3103 Examples:
3103 Examples:
3104
3104
3105 - show incoming changes with patches and full description::
3105 - show incoming changes with patches and full description::
3106
3106
3107 hg incoming -vp
3107 hg incoming -vp
3108
3108
3109 - show incoming changes excluding merges, store a bundle::
3109 - show incoming changes excluding merges, store a bundle::
3110
3110
3111 hg in -vpM --bundle incoming.hg
3111 hg in -vpM --bundle incoming.hg
3112 hg pull incoming.hg
3112 hg pull incoming.hg
3113
3113
3114 - briefly list changes inside a bundle::
3114 - briefly list changes inside a bundle::
3115
3115
3116 hg in changes.hg -T "{desc|firstline}\\n"
3116 hg in changes.hg -T "{desc|firstline}\\n"
3117
3117
3118 Returns 0 if there are incoming changes, 1 otherwise.
3118 Returns 0 if there are incoming changes, 1 otherwise.
3119 """
3119 """
3120 opts = pycompat.byteskwargs(opts)
3120 opts = pycompat.byteskwargs(opts)
3121 if opts.get('graph'):
3121 if opts.get('graph'):
3122 cmdutil.checkunsupportedgraphflags([], opts)
3122 cmdutil.checkunsupportedgraphflags([], opts)
3123 def display(other, chlist, displayer):
3123 def display(other, chlist, displayer):
3124 revdag = cmdutil.graphrevs(other, chlist, opts)
3124 revdag = cmdutil.graphrevs(other, chlist, opts)
3125 cmdutil.displaygraph(ui, repo, revdag, displayer,
3125 cmdutil.displaygraph(ui, repo, revdag, displayer,
3126 graphmod.asciiedges)
3126 graphmod.asciiedges)
3127
3127
3128 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3128 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3129 return 0
3129 return 0
3130
3130
3131 if opts.get('bundle') and opts.get('subrepos'):
3131 if opts.get('bundle') and opts.get('subrepos'):
3132 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3132 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3133
3133
3134 if opts.get('bookmarks'):
3134 if opts.get('bookmarks'):
3135 source, branches = hg.parseurl(ui.expandpath(source),
3135 source, branches = hg.parseurl(ui.expandpath(source),
3136 opts.get('branch'))
3136 opts.get('branch'))
3137 other = hg.peer(repo, opts, source)
3137 other = hg.peer(repo, opts, source)
3138 if 'bookmarks' not in other.listkeys('namespaces'):
3138 if 'bookmarks' not in other.listkeys('namespaces'):
3139 ui.warn(_("remote doesn't support bookmarks\n"))
3139 ui.warn(_("remote doesn't support bookmarks\n"))
3140 return 0
3140 return 0
3141 ui.pager('incoming')
3141 ui.pager('incoming')
3142 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3142 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3143 return bookmarks.incoming(ui, repo, other)
3143 return bookmarks.incoming(ui, repo, other)
3144
3144
3145 repo._subtoppath = ui.expandpath(source)
3145 repo._subtoppath = ui.expandpath(source)
3146 try:
3146 try:
3147 return hg.incoming(ui, repo, source, opts)
3147 return hg.incoming(ui, repo, source, opts)
3148 finally:
3148 finally:
3149 del repo._subtoppath
3149 del repo._subtoppath
3150
3150
3151
3151
3152 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3152 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3153 norepo=True)
3153 norepo=True)
3154 def init(ui, dest=".", **opts):
3154 def init(ui, dest=".", **opts):
3155 """create a new repository in the given directory
3155 """create a new repository in the given directory
3156
3156
3157 Initialize a new repository in the given directory. If the given
3157 Initialize a new repository in the given directory. If the given
3158 directory does not exist, it will be created.
3158 directory does not exist, it will be created.
3159
3159
3160 If no directory is given, the current directory is used.
3160 If no directory is given, the current directory is used.
3161
3161
3162 It is possible to specify an ``ssh://`` URL as the destination.
3162 It is possible to specify an ``ssh://`` URL as the destination.
3163 See :hg:`help urls` for more information.
3163 See :hg:`help urls` for more information.
3164
3164
3165 Returns 0 on success.
3165 Returns 0 on success.
3166 """
3166 """
3167 opts = pycompat.byteskwargs(opts)
3167 opts = pycompat.byteskwargs(opts)
3168 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3168 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3169
3169
3170 @command('locate',
3170 @command('locate',
3171 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3171 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3172 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3172 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3173 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3173 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3174 ] + walkopts,
3174 ] + walkopts,
3175 _('[OPTION]... [PATTERN]...'))
3175 _('[OPTION]... [PATTERN]...'))
3176 def locate(ui, repo, *pats, **opts):
3176 def locate(ui, repo, *pats, **opts):
3177 """locate files matching specific patterns (DEPRECATED)
3177 """locate files matching specific patterns (DEPRECATED)
3178
3178
3179 Print files under Mercurial control in the working directory whose
3179 Print files under Mercurial control in the working directory whose
3180 names match the given patterns.
3180 names match the given patterns.
3181
3181
3182 By default, this command searches all directories in the working
3182 By default, this command searches all directories in the working
3183 directory. To search just the current directory and its
3183 directory. To search just the current directory and its
3184 subdirectories, use "--include .".
3184 subdirectories, use "--include .".
3185
3185
3186 If no patterns are given to match, this command prints the names
3186 If no patterns are given to match, this command prints the names
3187 of all files under Mercurial control in the working directory.
3187 of all files under Mercurial control in the working directory.
3188
3188
3189 If you want to feed the output of this command into the "xargs"
3189 If you want to feed the output of this command into the "xargs"
3190 command, use the -0 option to both this command and "xargs". This
3190 command, use the -0 option to both this command and "xargs". This
3191 will avoid the problem of "xargs" treating single filenames that
3191 will avoid the problem of "xargs" treating single filenames that
3192 contain whitespace as multiple filenames.
3192 contain whitespace as multiple filenames.
3193
3193
3194 See :hg:`help files` for a more versatile command.
3194 See :hg:`help files` for a more versatile command.
3195
3195
3196 Returns 0 if a match is found, 1 otherwise.
3196 Returns 0 if a match is found, 1 otherwise.
3197 """
3197 """
3198 opts = pycompat.byteskwargs(opts)
3198 opts = pycompat.byteskwargs(opts)
3199 if opts.get('print0'):
3199 if opts.get('print0'):
3200 end = '\0'
3200 end = '\0'
3201 else:
3201 else:
3202 end = '\n'
3202 end = '\n'
3203 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3203 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3204
3204
3205 ret = 1
3205 ret = 1
3206 ctx = repo[rev]
3206 ctx = repo[rev]
3207 m = scmutil.match(ctx, pats, opts, default='relglob',
3207 m = scmutil.match(ctx, pats, opts, default='relglob',
3208 badfn=lambda x, y: False)
3208 badfn=lambda x, y: False)
3209
3209
3210 ui.pager('locate')
3210 ui.pager('locate')
3211 for abs in ctx.matches(m):
3211 for abs in ctx.matches(m):
3212 if opts.get('fullpath'):
3212 if opts.get('fullpath'):
3213 ui.write(repo.wjoin(abs), end)
3213 ui.write(repo.wjoin(abs), end)
3214 else:
3214 else:
3215 ui.write(((pats and m.rel(abs)) or abs), end)
3215 ui.write(((pats and m.rel(abs)) or abs), end)
3216 ret = 0
3216 ret = 0
3217
3217
3218 return ret
3218 return ret
3219
3219
3220 @command('^log|history',
3220 @command('^log|history',
3221 [('f', 'follow', None,
3221 [('f', 'follow', None,
3222 _('follow changeset history, or file history across copies and renames')),
3222 _('follow changeset history, or file history across copies and renames')),
3223 ('', 'follow-first', None,
3223 ('', 'follow-first', None,
3224 _('only follow the first parent of merge changesets (DEPRECATED)')),
3224 _('only follow the first parent of merge changesets (DEPRECATED)')),
3225 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3225 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3226 ('C', 'copies', None, _('show copied files')),
3226 ('C', 'copies', None, _('show copied files')),
3227 ('k', 'keyword', [],
3227 ('k', 'keyword', [],
3228 _('do case-insensitive search for a given text'), _('TEXT')),
3228 _('do case-insensitive search for a given text'), _('TEXT')),
3229 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3229 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3230 ('', 'removed', None, _('include revisions where files were removed')),
3230 ('', 'removed', None, _('include revisions where files were removed')),
3231 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3231 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3232 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3232 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3233 ('', 'only-branch', [],
3233 ('', 'only-branch', [],
3234 _('show only changesets within the given named branch (DEPRECATED)'),
3234 _('show only changesets within the given named branch (DEPRECATED)'),
3235 _('BRANCH')),
3235 _('BRANCH')),
3236 ('b', 'branch', [],
3236 ('b', 'branch', [],
3237 _('show changesets within the given named branch'), _('BRANCH')),
3237 _('show changesets within the given named branch'), _('BRANCH')),
3238 ('P', 'prune', [],
3238 ('P', 'prune', [],
3239 _('do not display revision or any of its ancestors'), _('REV')),
3239 _('do not display revision or any of its ancestors'), _('REV')),
3240 ] + logopts + walkopts,
3240 ] + logopts + walkopts,
3241 _('[OPTION]... [FILE]'),
3241 _('[OPTION]... [FILE]'),
3242 inferrepo=True)
3242 inferrepo=True)
3243 def log(ui, repo, *pats, **opts):
3243 def log(ui, repo, *pats, **opts):
3244 """show revision history of entire repository or files
3244 """show revision history of entire repository or files
3245
3245
3246 Print the revision history of the specified files or the entire
3246 Print the revision history of the specified files or the entire
3247 project.
3247 project.
3248
3248
3249 If no revision range is specified, the default is ``tip:0`` unless
3249 If no revision range is specified, the default is ``tip:0`` unless
3250 --follow is set, in which case the working directory parent is
3250 --follow is set, in which case the working directory parent is
3251 used as the starting revision.
3251 used as the starting revision.
3252
3252
3253 File history is shown without following rename or copy history of
3253 File history is shown without following rename or copy history of
3254 files. Use -f/--follow with a filename to follow history across
3254 files. Use -f/--follow with a filename to follow history across
3255 renames and copies. --follow without a filename will only show
3255 renames and copies. --follow without a filename will only show
3256 ancestors or descendants of the starting revision.
3256 ancestors or descendants of the starting revision.
3257
3257
3258 By default this command prints revision number and changeset id,
3258 By default this command prints revision number and changeset id,
3259 tags, non-trivial parents, user, date and time, and a summary for
3259 tags, non-trivial parents, user, date and time, and a summary for
3260 each commit. When the -v/--verbose switch is used, the list of
3260 each commit. When the -v/--verbose switch is used, the list of
3261 changed files and full commit message are shown.
3261 changed files and full commit message are shown.
3262
3262
3263 With --graph the revisions are shown as an ASCII art DAG with the most
3263 With --graph the revisions are shown as an ASCII art DAG with the most
3264 recent changeset at the top.
3264 recent changeset at the top.
3265 'o' is a changeset, '@' is a working directory parent, 'x' is obsolete,
3265 'o' is a changeset, '@' is a working directory parent, 'x' is obsolete,
3266 and '+' represents a fork where the changeset from the lines below is a
3266 and '+' represents a fork where the changeset from the lines below is a
3267 parent of the 'o' merge on the same line.
3267 parent of the 'o' merge on the same line.
3268 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3268 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3269 of a '|' indicates one or more revisions in a path are omitted.
3269 of a '|' indicates one or more revisions in a path are omitted.
3270
3270
3271 .. note::
3271 .. note::
3272
3272
3273 :hg:`log --patch` may generate unexpected diff output for merge
3273 :hg:`log --patch` may generate unexpected diff output for merge
3274 changesets, as it will only compare the merge changeset against
3274 changesets, as it will only compare the merge changeset against
3275 its first parent. Also, only files different from BOTH parents
3275 its first parent. Also, only files different from BOTH parents
3276 will appear in files:.
3276 will appear in files:.
3277
3277
3278 .. note::
3278 .. note::
3279
3279
3280 For performance reasons, :hg:`log FILE` may omit duplicate changes
3280 For performance reasons, :hg:`log FILE` may omit duplicate changes
3281 made on branches and will not show removals or mode changes. To
3281 made on branches and will not show removals or mode changes. To
3282 see all such changes, use the --removed switch.
3282 see all such changes, use the --removed switch.
3283
3283
3284 .. container:: verbose
3284 .. container:: verbose
3285
3285
3286 Some examples:
3286 Some examples:
3287
3287
3288 - changesets with full descriptions and file lists::
3288 - changesets with full descriptions and file lists::
3289
3289
3290 hg log -v
3290 hg log -v
3291
3291
3292 - changesets ancestral to the working directory::
3292 - changesets ancestral to the working directory::
3293
3293
3294 hg log -f
3294 hg log -f
3295
3295
3296 - last 10 commits on the current branch::
3296 - last 10 commits on the current branch::
3297
3297
3298 hg log -l 10 -b .
3298 hg log -l 10 -b .
3299
3299
3300 - changesets showing all modifications of a file, including removals::
3300 - changesets showing all modifications of a file, including removals::
3301
3301
3302 hg log --removed file.c
3302 hg log --removed file.c
3303
3303
3304 - all changesets that touch a directory, with diffs, excluding merges::
3304 - all changesets that touch a directory, with diffs, excluding merges::
3305
3305
3306 hg log -Mp lib/
3306 hg log -Mp lib/
3307
3307
3308 - all revision numbers that match a keyword::
3308 - all revision numbers that match a keyword::
3309
3309
3310 hg log -k bug --template "{rev}\\n"
3310 hg log -k bug --template "{rev}\\n"
3311
3311
3312 - the full hash identifier of the working directory parent::
3312 - the full hash identifier of the working directory parent::
3313
3313
3314 hg log -r . --template "{node}\\n"
3314 hg log -r . --template "{node}\\n"
3315
3315
3316 - list available log templates::
3316 - list available log templates::
3317
3317
3318 hg log -T list
3318 hg log -T list
3319
3319
3320 - check if a given changeset is included in a tagged release::
3320 - check if a given changeset is included in a tagged release::
3321
3321
3322 hg log -r "a21ccf and ancestor(1.9)"
3322 hg log -r "a21ccf and ancestor(1.9)"
3323
3323
3324 - find all changesets by some user in a date range::
3324 - find all changesets by some user in a date range::
3325
3325
3326 hg log -k alice -d "may 2008 to jul 2008"
3326 hg log -k alice -d "may 2008 to jul 2008"
3327
3327
3328 - summary of all changesets after the last tag::
3328 - summary of all changesets after the last tag::
3329
3329
3330 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3330 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3331
3331
3332 See :hg:`help dates` for a list of formats valid for -d/--date.
3332 See :hg:`help dates` for a list of formats valid for -d/--date.
3333
3333
3334 See :hg:`help revisions` for more about specifying and ordering
3334 See :hg:`help revisions` for more about specifying and ordering
3335 revisions.
3335 revisions.
3336
3336
3337 See :hg:`help templates` for more about pre-packaged styles and
3337 See :hg:`help templates` for more about pre-packaged styles and
3338 specifying custom templates.
3338 specifying custom templates. The default template used by the log
3339 command can be customized via the ``ui.logtemplate`` configuration
3340 setting.
3339
3341
3340 Returns 0 on success.
3342 Returns 0 on success.
3341
3343
3342 """
3344 """
3343 opts = pycompat.byteskwargs(opts)
3345 opts = pycompat.byteskwargs(opts)
3344 if opts.get('follow') and opts.get('rev'):
3346 if opts.get('follow') and opts.get('rev'):
3345 opts['rev'] = [revsetlang.formatspec('reverse(::%lr)', opts.get('rev'))]
3347 opts['rev'] = [revsetlang.formatspec('reverse(::%lr)', opts.get('rev'))]
3346 del opts['follow']
3348 del opts['follow']
3347
3349
3348 if opts.get('graph'):
3350 if opts.get('graph'):
3349 return cmdutil.graphlog(ui, repo, pats, opts)
3351 return cmdutil.graphlog(ui, repo, pats, opts)
3350
3352
3351 revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
3353 revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
3352 limit = cmdutil.loglimit(opts)
3354 limit = cmdutil.loglimit(opts)
3353 count = 0
3355 count = 0
3354
3356
3355 getrenamed = None
3357 getrenamed = None
3356 if opts.get('copies'):
3358 if opts.get('copies'):
3357 endrev = None
3359 endrev = None
3358 if opts.get('rev'):
3360 if opts.get('rev'):
3359 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
3361 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
3360 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3362 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3361
3363
3362 ui.pager('log')
3364 ui.pager('log')
3363 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3365 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3364 for rev in revs:
3366 for rev in revs:
3365 if count == limit:
3367 if count == limit:
3366 break
3368 break
3367 ctx = repo[rev]
3369 ctx = repo[rev]
3368 copies = None
3370 copies = None
3369 if getrenamed is not None and rev:
3371 if getrenamed is not None and rev:
3370 copies = []
3372 copies = []
3371 for fn in ctx.files():
3373 for fn in ctx.files():
3372 rename = getrenamed(fn, rev)
3374 rename = getrenamed(fn, rev)
3373 if rename:
3375 if rename:
3374 copies.append((fn, rename[0]))
3376 copies.append((fn, rename[0]))
3375 if filematcher:
3377 if filematcher:
3376 revmatchfn = filematcher(ctx.rev())
3378 revmatchfn = filematcher(ctx.rev())
3377 else:
3379 else:
3378 revmatchfn = None
3380 revmatchfn = None
3379 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
3381 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
3380 if displayer.flush(ctx):
3382 if displayer.flush(ctx):
3381 count += 1
3383 count += 1
3382
3384
3383 displayer.close()
3385 displayer.close()
3384
3386
3385 @command('manifest',
3387 @command('manifest',
3386 [('r', 'rev', '', _('revision to display'), _('REV')),
3388 [('r', 'rev', '', _('revision to display'), _('REV')),
3387 ('', 'all', False, _("list files from all revisions"))]
3389 ('', 'all', False, _("list files from all revisions"))]
3388 + formatteropts,
3390 + formatteropts,
3389 _('[-r REV]'))
3391 _('[-r REV]'))
3390 def manifest(ui, repo, node=None, rev=None, **opts):
3392 def manifest(ui, repo, node=None, rev=None, **opts):
3391 """output the current or given revision of the project manifest
3393 """output the current or given revision of the project manifest
3392
3394
3393 Print a list of version controlled files for the given revision.
3395 Print a list of version controlled files for the given revision.
3394 If no revision is given, the first parent of the working directory
3396 If no revision is given, the first parent of the working directory
3395 is used, or the null revision if no revision is checked out.
3397 is used, or the null revision if no revision is checked out.
3396
3398
3397 With -v, print file permissions, symlink and executable bits.
3399 With -v, print file permissions, symlink and executable bits.
3398 With --debug, print file revision hashes.
3400 With --debug, print file revision hashes.
3399
3401
3400 If option --all is specified, the list of all files from all revisions
3402 If option --all is specified, the list of all files from all revisions
3401 is printed. This includes deleted and renamed files.
3403 is printed. This includes deleted and renamed files.
3402
3404
3403 Returns 0 on success.
3405 Returns 0 on success.
3404 """
3406 """
3405 opts = pycompat.byteskwargs(opts)
3407 opts = pycompat.byteskwargs(opts)
3406 fm = ui.formatter('manifest', opts)
3408 fm = ui.formatter('manifest', opts)
3407
3409
3408 if opts.get('all'):
3410 if opts.get('all'):
3409 if rev or node:
3411 if rev or node:
3410 raise error.Abort(_("can't specify a revision with --all"))
3412 raise error.Abort(_("can't specify a revision with --all"))
3411
3413
3412 res = []
3414 res = []
3413 prefix = "data/"
3415 prefix = "data/"
3414 suffix = ".i"
3416 suffix = ".i"
3415 plen = len(prefix)
3417 plen = len(prefix)
3416 slen = len(suffix)
3418 slen = len(suffix)
3417 with repo.lock():
3419 with repo.lock():
3418 for fn, b, size in repo.store.datafiles():
3420 for fn, b, size in repo.store.datafiles():
3419 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
3421 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
3420 res.append(fn[plen:-slen])
3422 res.append(fn[plen:-slen])
3421 ui.pager('manifest')
3423 ui.pager('manifest')
3422 for f in res:
3424 for f in res:
3423 fm.startitem()
3425 fm.startitem()
3424 fm.write("path", '%s\n', f)
3426 fm.write("path", '%s\n', f)
3425 fm.end()
3427 fm.end()
3426 return
3428 return
3427
3429
3428 if rev and node:
3430 if rev and node:
3429 raise error.Abort(_("please specify just one revision"))
3431 raise error.Abort(_("please specify just one revision"))
3430
3432
3431 if not node:
3433 if not node:
3432 node = rev
3434 node = rev
3433
3435
3434 char = {'l': '@', 'x': '*', '': ''}
3436 char = {'l': '@', 'x': '*', '': ''}
3435 mode = {'l': '644', 'x': '755', '': '644'}
3437 mode = {'l': '644', 'x': '755', '': '644'}
3436 ctx = scmutil.revsingle(repo, node)
3438 ctx = scmutil.revsingle(repo, node)
3437 mf = ctx.manifest()
3439 mf = ctx.manifest()
3438 ui.pager('manifest')
3440 ui.pager('manifest')
3439 for f in ctx:
3441 for f in ctx:
3440 fm.startitem()
3442 fm.startitem()
3441 fl = ctx[f].flags()
3443 fl = ctx[f].flags()
3442 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3444 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3443 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3445 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3444 fm.write('path', '%s\n', f)
3446 fm.write('path', '%s\n', f)
3445 fm.end()
3447 fm.end()
3446
3448
3447 @command('^merge',
3449 @command('^merge',
3448 [('f', 'force', None,
3450 [('f', 'force', None,
3449 _('force a merge including outstanding changes (DEPRECATED)')),
3451 _('force a merge including outstanding changes (DEPRECATED)')),
3450 ('r', 'rev', '', _('revision to merge'), _('REV')),
3452 ('r', 'rev', '', _('revision to merge'), _('REV')),
3451 ('P', 'preview', None,
3453 ('P', 'preview', None,
3452 _('review revisions to merge (no merge is performed)'))
3454 _('review revisions to merge (no merge is performed)'))
3453 ] + mergetoolopts,
3455 ] + mergetoolopts,
3454 _('[-P] [[-r] REV]'))
3456 _('[-P] [[-r] REV]'))
3455 def merge(ui, repo, node=None, **opts):
3457 def merge(ui, repo, node=None, **opts):
3456 """merge another revision into working directory
3458 """merge another revision into working directory
3457
3459
3458 The current working directory is updated with all changes made in
3460 The current working directory is updated with all changes made in
3459 the requested revision since the last common predecessor revision.
3461 the requested revision since the last common predecessor revision.
3460
3462
3461 Files that changed between either parent are marked as changed for
3463 Files that changed between either parent are marked as changed for
3462 the next commit and a commit must be performed before any further
3464 the next commit and a commit must be performed before any further
3463 updates to the repository are allowed. The next commit will have
3465 updates to the repository are allowed. The next commit will have
3464 two parents.
3466 two parents.
3465
3467
3466 ``--tool`` can be used to specify the merge tool used for file
3468 ``--tool`` can be used to specify the merge tool used for file
3467 merges. It overrides the HGMERGE environment variable and your
3469 merges. It overrides the HGMERGE environment variable and your
3468 configuration files. See :hg:`help merge-tools` for options.
3470 configuration files. See :hg:`help merge-tools` for options.
3469
3471
3470 If no revision is specified, the working directory's parent is a
3472 If no revision is specified, the working directory's parent is a
3471 head revision, and the current branch contains exactly one other
3473 head revision, and the current branch contains exactly one other
3472 head, the other head is merged with by default. Otherwise, an
3474 head, the other head is merged with by default. Otherwise, an
3473 explicit revision with which to merge with must be provided.
3475 explicit revision with which to merge with must be provided.
3474
3476
3475 See :hg:`help resolve` for information on handling file conflicts.
3477 See :hg:`help resolve` for information on handling file conflicts.
3476
3478
3477 To undo an uncommitted merge, use :hg:`update --clean .` which
3479 To undo an uncommitted merge, use :hg:`update --clean .` which
3478 will check out a clean copy of the original merge parent, losing
3480 will check out a clean copy of the original merge parent, losing
3479 all changes.
3481 all changes.
3480
3482
3481 Returns 0 on success, 1 if there are unresolved files.
3483 Returns 0 on success, 1 if there are unresolved files.
3482 """
3484 """
3483
3485
3484 opts = pycompat.byteskwargs(opts)
3486 opts = pycompat.byteskwargs(opts)
3485 if opts.get('rev') and node:
3487 if opts.get('rev') and node:
3486 raise error.Abort(_("please specify just one revision"))
3488 raise error.Abort(_("please specify just one revision"))
3487 if not node:
3489 if not node:
3488 node = opts.get('rev')
3490 node = opts.get('rev')
3489
3491
3490 if node:
3492 if node:
3491 node = scmutil.revsingle(repo, node).node()
3493 node = scmutil.revsingle(repo, node).node()
3492
3494
3493 if not node:
3495 if not node:
3494 node = repo[destutil.destmerge(repo)].node()
3496 node = repo[destutil.destmerge(repo)].node()
3495
3497
3496 if opts.get('preview'):
3498 if opts.get('preview'):
3497 # find nodes that are ancestors of p2 but not of p1
3499 # find nodes that are ancestors of p2 but not of p1
3498 p1 = repo.lookup('.')
3500 p1 = repo.lookup('.')
3499 p2 = repo.lookup(node)
3501 p2 = repo.lookup(node)
3500 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3502 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3501
3503
3502 displayer = cmdutil.show_changeset(ui, repo, opts)
3504 displayer = cmdutil.show_changeset(ui, repo, opts)
3503 for node in nodes:
3505 for node in nodes:
3504 displayer.show(repo[node])
3506 displayer.show(repo[node])
3505 displayer.close()
3507 displayer.close()
3506 return 0
3508 return 0
3507
3509
3508 try:
3510 try:
3509 # ui.forcemerge is an internal variable, do not document
3511 # ui.forcemerge is an internal variable, do not document
3510 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
3512 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
3511 force = opts.get('force')
3513 force = opts.get('force')
3512 labels = ['working copy', 'merge rev']
3514 labels = ['working copy', 'merge rev']
3513 return hg.merge(repo, node, force=force, mergeforce=force,
3515 return hg.merge(repo, node, force=force, mergeforce=force,
3514 labels=labels)
3516 labels=labels)
3515 finally:
3517 finally:
3516 ui.setconfig('ui', 'forcemerge', '', 'merge')
3518 ui.setconfig('ui', 'forcemerge', '', 'merge')
3517
3519
3518 @command('outgoing|out',
3520 @command('outgoing|out',
3519 [('f', 'force', None, _('run even when the destination is unrelated')),
3521 [('f', 'force', None, _('run even when the destination is unrelated')),
3520 ('r', 'rev', [],
3522 ('r', 'rev', [],
3521 _('a changeset intended to be included in the destination'), _('REV')),
3523 _('a changeset intended to be included in the destination'), _('REV')),
3522 ('n', 'newest-first', None, _('show newest record first')),
3524 ('n', 'newest-first', None, _('show newest record first')),
3523 ('B', 'bookmarks', False, _('compare bookmarks')),
3525 ('B', 'bookmarks', False, _('compare bookmarks')),
3524 ('b', 'branch', [], _('a specific branch you would like to push'),
3526 ('b', 'branch', [], _('a specific branch you would like to push'),
3525 _('BRANCH')),
3527 _('BRANCH')),
3526 ] + logopts + remoteopts + subrepoopts,
3528 ] + logopts + remoteopts + subrepoopts,
3527 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3529 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3528 def outgoing(ui, repo, dest=None, **opts):
3530 def outgoing(ui, repo, dest=None, **opts):
3529 """show changesets not found in the destination
3531 """show changesets not found in the destination
3530
3532
3531 Show changesets not found in the specified destination repository
3533 Show changesets not found in the specified destination repository
3532 or the default push location. These are the changesets that would
3534 or the default push location. These are the changesets that would
3533 be pushed if a push was requested.
3535 be pushed if a push was requested.
3534
3536
3535 See pull for details of valid destination formats.
3537 See pull for details of valid destination formats.
3536
3538
3537 .. container:: verbose
3539 .. container:: verbose
3538
3540
3539 With -B/--bookmarks, the result of bookmark comparison between
3541 With -B/--bookmarks, the result of bookmark comparison between
3540 local and remote repositories is displayed. With -v/--verbose,
3542 local and remote repositories is displayed. With -v/--verbose,
3541 status is also displayed for each bookmark like below::
3543 status is also displayed for each bookmark like below::
3542
3544
3543 BM1 01234567890a added
3545 BM1 01234567890a added
3544 BM2 deleted
3546 BM2 deleted
3545 BM3 234567890abc advanced
3547 BM3 234567890abc advanced
3546 BM4 34567890abcd diverged
3548 BM4 34567890abcd diverged
3547 BM5 4567890abcde changed
3549 BM5 4567890abcde changed
3548
3550
3549 The action taken when pushing depends on the
3551 The action taken when pushing depends on the
3550 status of each bookmark:
3552 status of each bookmark:
3551
3553
3552 :``added``: push with ``-B`` will create it
3554 :``added``: push with ``-B`` will create it
3553 :``deleted``: push with ``-B`` will delete it
3555 :``deleted``: push with ``-B`` will delete it
3554 :``advanced``: push will update it
3556 :``advanced``: push will update it
3555 :``diverged``: push with ``-B`` will update it
3557 :``diverged``: push with ``-B`` will update it
3556 :``changed``: push with ``-B`` will update it
3558 :``changed``: push with ``-B`` will update it
3557
3559
3558 From the point of view of pushing behavior, bookmarks
3560 From the point of view of pushing behavior, bookmarks
3559 existing only in the remote repository are treated as
3561 existing only in the remote repository are treated as
3560 ``deleted``, even if it is in fact added remotely.
3562 ``deleted``, even if it is in fact added remotely.
3561
3563
3562 Returns 0 if there are outgoing changes, 1 otherwise.
3564 Returns 0 if there are outgoing changes, 1 otherwise.
3563 """
3565 """
3564 opts = pycompat.byteskwargs(opts)
3566 opts = pycompat.byteskwargs(opts)
3565 if opts.get('graph'):
3567 if opts.get('graph'):
3566 cmdutil.checkunsupportedgraphflags([], opts)
3568 cmdutil.checkunsupportedgraphflags([], opts)
3567 o, other = hg._outgoing(ui, repo, dest, opts)
3569 o, other = hg._outgoing(ui, repo, dest, opts)
3568 if not o:
3570 if not o:
3569 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3571 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3570 return
3572 return
3571
3573
3572 revdag = cmdutil.graphrevs(repo, o, opts)
3574 revdag = cmdutil.graphrevs(repo, o, opts)
3573 ui.pager('outgoing')
3575 ui.pager('outgoing')
3574 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3576 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3575 cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges)
3577 cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges)
3576 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3578 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3577 return 0
3579 return 0
3578
3580
3579 if opts.get('bookmarks'):
3581 if opts.get('bookmarks'):
3580 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3582 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3581 dest, branches = hg.parseurl(dest, opts.get('branch'))
3583 dest, branches = hg.parseurl(dest, opts.get('branch'))
3582 other = hg.peer(repo, opts, dest)
3584 other = hg.peer(repo, opts, dest)
3583 if 'bookmarks' not in other.listkeys('namespaces'):
3585 if 'bookmarks' not in other.listkeys('namespaces'):
3584 ui.warn(_("remote doesn't support bookmarks\n"))
3586 ui.warn(_("remote doesn't support bookmarks\n"))
3585 return 0
3587 return 0
3586 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3588 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3587 ui.pager('outgoing')
3589 ui.pager('outgoing')
3588 return bookmarks.outgoing(ui, repo, other)
3590 return bookmarks.outgoing(ui, repo, other)
3589
3591
3590 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
3592 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
3591 try:
3593 try:
3592 return hg.outgoing(ui, repo, dest, opts)
3594 return hg.outgoing(ui, repo, dest, opts)
3593 finally:
3595 finally:
3594 del repo._subtoppath
3596 del repo._subtoppath
3595
3597
3596 @command('parents',
3598 @command('parents',
3597 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3599 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3598 ] + templateopts,
3600 ] + templateopts,
3599 _('[-r REV] [FILE]'),
3601 _('[-r REV] [FILE]'),
3600 inferrepo=True)
3602 inferrepo=True)
3601 def parents(ui, repo, file_=None, **opts):
3603 def parents(ui, repo, file_=None, **opts):
3602 """show the parents of the working directory or revision (DEPRECATED)
3604 """show the parents of the working directory or revision (DEPRECATED)
3603
3605
3604 Print the working directory's parent revisions. If a revision is
3606 Print the working directory's parent revisions. If a revision is
3605 given via -r/--rev, the parent of that revision will be printed.
3607 given via -r/--rev, the parent of that revision will be printed.
3606 If a file argument is given, the revision in which the file was
3608 If a file argument is given, the revision in which the file was
3607 last changed (before the working directory revision or the
3609 last changed (before the working directory revision or the
3608 argument to --rev if given) is printed.
3610 argument to --rev if given) is printed.
3609
3611
3610 This command is equivalent to::
3612 This command is equivalent to::
3611
3613
3612 hg log -r "p1()+p2()" or
3614 hg log -r "p1()+p2()" or
3613 hg log -r "p1(REV)+p2(REV)" or
3615 hg log -r "p1(REV)+p2(REV)" or
3614 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
3616 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
3615 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
3617 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
3616
3618
3617 See :hg:`summary` and :hg:`help revsets` for related information.
3619 See :hg:`summary` and :hg:`help revsets` for related information.
3618
3620
3619 Returns 0 on success.
3621 Returns 0 on success.
3620 """
3622 """
3621
3623
3622 opts = pycompat.byteskwargs(opts)
3624 opts = pycompat.byteskwargs(opts)
3623 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3625 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3624
3626
3625 if file_:
3627 if file_:
3626 m = scmutil.match(ctx, (file_,), opts)
3628 m = scmutil.match(ctx, (file_,), opts)
3627 if m.anypats() or len(m.files()) != 1:
3629 if m.anypats() or len(m.files()) != 1:
3628 raise error.Abort(_('can only specify an explicit filename'))
3630 raise error.Abort(_('can only specify an explicit filename'))
3629 file_ = m.files()[0]
3631 file_ = m.files()[0]
3630 filenodes = []
3632 filenodes = []
3631 for cp in ctx.parents():
3633 for cp in ctx.parents():
3632 if not cp:
3634 if not cp:
3633 continue
3635 continue
3634 try:
3636 try:
3635 filenodes.append(cp.filenode(file_))
3637 filenodes.append(cp.filenode(file_))
3636 except error.LookupError:
3638 except error.LookupError:
3637 pass
3639 pass
3638 if not filenodes:
3640 if not filenodes:
3639 raise error.Abort(_("'%s' not found in manifest!") % file_)
3641 raise error.Abort(_("'%s' not found in manifest!") % file_)
3640 p = []
3642 p = []
3641 for fn in filenodes:
3643 for fn in filenodes:
3642 fctx = repo.filectx(file_, fileid=fn)
3644 fctx = repo.filectx(file_, fileid=fn)
3643 p.append(fctx.node())
3645 p.append(fctx.node())
3644 else:
3646 else:
3645 p = [cp.node() for cp in ctx.parents()]
3647 p = [cp.node() for cp in ctx.parents()]
3646
3648
3647 displayer = cmdutil.show_changeset(ui, repo, opts)
3649 displayer = cmdutil.show_changeset(ui, repo, opts)
3648 for n in p:
3650 for n in p:
3649 if n != nullid:
3651 if n != nullid:
3650 displayer.show(repo[n])
3652 displayer.show(repo[n])
3651 displayer.close()
3653 displayer.close()
3652
3654
3653 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True)
3655 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True)
3654 def paths(ui, repo, search=None, **opts):
3656 def paths(ui, repo, search=None, **opts):
3655 """show aliases for remote repositories
3657 """show aliases for remote repositories
3656
3658
3657 Show definition of symbolic path name NAME. If no name is given,
3659 Show definition of symbolic path name NAME. If no name is given,
3658 show definition of all available names.
3660 show definition of all available names.
3659
3661
3660 Option -q/--quiet suppresses all output when searching for NAME
3662 Option -q/--quiet suppresses all output when searching for NAME
3661 and shows only the path names when listing all definitions.
3663 and shows only the path names when listing all definitions.
3662
3664
3663 Path names are defined in the [paths] section of your
3665 Path names are defined in the [paths] section of your
3664 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3666 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3665 repository, ``.hg/hgrc`` is used, too.
3667 repository, ``.hg/hgrc`` is used, too.
3666
3668
3667 The path names ``default`` and ``default-push`` have a special
3669 The path names ``default`` and ``default-push`` have a special
3668 meaning. When performing a push or pull operation, they are used
3670 meaning. When performing a push or pull operation, they are used
3669 as fallbacks if no location is specified on the command-line.
3671 as fallbacks if no location is specified on the command-line.
3670 When ``default-push`` is set, it will be used for push and
3672 When ``default-push`` is set, it will be used for push and
3671 ``default`` will be used for pull; otherwise ``default`` is used
3673 ``default`` will be used for pull; otherwise ``default`` is used
3672 as the fallback for both. When cloning a repository, the clone
3674 as the fallback for both. When cloning a repository, the clone
3673 source is written as ``default`` in ``.hg/hgrc``.
3675 source is written as ``default`` in ``.hg/hgrc``.
3674
3676
3675 .. note::
3677 .. note::
3676
3678
3677 ``default`` and ``default-push`` apply to all inbound (e.g.
3679 ``default`` and ``default-push`` apply to all inbound (e.g.
3678 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
3680 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
3679 and :hg:`bundle`) operations.
3681 and :hg:`bundle`) operations.
3680
3682
3681 See :hg:`help urls` for more information.
3683 See :hg:`help urls` for more information.
3682
3684
3683 Returns 0 on success.
3685 Returns 0 on success.
3684 """
3686 """
3685
3687
3686 opts = pycompat.byteskwargs(opts)
3688 opts = pycompat.byteskwargs(opts)
3687 ui.pager('paths')
3689 ui.pager('paths')
3688 if search:
3690 if search:
3689 pathitems = [(name, path) for name, path in ui.paths.iteritems()
3691 pathitems = [(name, path) for name, path in ui.paths.iteritems()
3690 if name == search]
3692 if name == search]
3691 else:
3693 else:
3692 pathitems = sorted(ui.paths.iteritems())
3694 pathitems = sorted(ui.paths.iteritems())
3693
3695
3694 fm = ui.formatter('paths', opts)
3696 fm = ui.formatter('paths', opts)
3695 if fm.isplain():
3697 if fm.isplain():
3696 hidepassword = util.hidepassword
3698 hidepassword = util.hidepassword
3697 else:
3699 else:
3698 hidepassword = str
3700 hidepassword = str
3699 if ui.quiet:
3701 if ui.quiet:
3700 namefmt = '%s\n'
3702 namefmt = '%s\n'
3701 else:
3703 else:
3702 namefmt = '%s = '
3704 namefmt = '%s = '
3703 showsubopts = not search and not ui.quiet
3705 showsubopts = not search and not ui.quiet
3704
3706
3705 for name, path in pathitems:
3707 for name, path in pathitems:
3706 fm.startitem()
3708 fm.startitem()
3707 fm.condwrite(not search, 'name', namefmt, name)
3709 fm.condwrite(not search, 'name', namefmt, name)
3708 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
3710 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
3709 for subopt, value in sorted(path.suboptions.items()):
3711 for subopt, value in sorted(path.suboptions.items()):
3710 assert subopt not in ('name', 'url')
3712 assert subopt not in ('name', 'url')
3711 if showsubopts:
3713 if showsubopts:
3712 fm.plain('%s:%s = ' % (name, subopt))
3714 fm.plain('%s:%s = ' % (name, subopt))
3713 fm.condwrite(showsubopts, subopt, '%s\n', value)
3715 fm.condwrite(showsubopts, subopt, '%s\n', value)
3714
3716
3715 fm.end()
3717 fm.end()
3716
3718
3717 if search and not pathitems:
3719 if search and not pathitems:
3718 if not ui.quiet:
3720 if not ui.quiet:
3719 ui.warn(_("not found!\n"))
3721 ui.warn(_("not found!\n"))
3720 return 1
3722 return 1
3721 else:
3723 else:
3722 return 0
3724 return 0
3723
3725
3724 @command('phase',
3726 @command('phase',
3725 [('p', 'public', False, _('set changeset phase to public')),
3727 [('p', 'public', False, _('set changeset phase to public')),
3726 ('d', 'draft', False, _('set changeset phase to draft')),
3728 ('d', 'draft', False, _('set changeset phase to draft')),
3727 ('s', 'secret', False, _('set changeset phase to secret')),
3729 ('s', 'secret', False, _('set changeset phase to secret')),
3728 ('f', 'force', False, _('allow to move boundary backward')),
3730 ('f', 'force', False, _('allow to move boundary backward')),
3729 ('r', 'rev', [], _('target revision'), _('REV')),
3731 ('r', 'rev', [], _('target revision'), _('REV')),
3730 ],
3732 ],
3731 _('[-p|-d|-s] [-f] [-r] [REV...]'))
3733 _('[-p|-d|-s] [-f] [-r] [REV...]'))
3732 def phase(ui, repo, *revs, **opts):
3734 def phase(ui, repo, *revs, **opts):
3733 """set or show the current phase name
3735 """set or show the current phase name
3734
3736
3735 With no argument, show the phase name of the current revision(s).
3737 With no argument, show the phase name of the current revision(s).
3736
3738
3737 With one of -p/--public, -d/--draft or -s/--secret, change the
3739 With one of -p/--public, -d/--draft or -s/--secret, change the
3738 phase value of the specified revisions.
3740 phase value of the specified revisions.
3739
3741
3740 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
3742 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
3741 lower phase to an higher phase. Phases are ordered as follows::
3743 lower phase to an higher phase. Phases are ordered as follows::
3742
3744
3743 public < draft < secret
3745 public < draft < secret
3744
3746
3745 Returns 0 on success, 1 if some phases could not be changed.
3747 Returns 0 on success, 1 if some phases could not be changed.
3746
3748
3747 (For more information about the phases concept, see :hg:`help phases`.)
3749 (For more information about the phases concept, see :hg:`help phases`.)
3748 """
3750 """
3749 opts = pycompat.byteskwargs(opts)
3751 opts = pycompat.byteskwargs(opts)
3750 # search for a unique phase argument
3752 # search for a unique phase argument
3751 targetphase = None
3753 targetphase = None
3752 for idx, name in enumerate(phases.phasenames):
3754 for idx, name in enumerate(phases.phasenames):
3753 if opts[name]:
3755 if opts[name]:
3754 if targetphase is not None:
3756 if targetphase is not None:
3755 raise error.Abort(_('only one phase can be specified'))
3757 raise error.Abort(_('only one phase can be specified'))
3756 targetphase = idx
3758 targetphase = idx
3757
3759
3758 # look for specified revision
3760 # look for specified revision
3759 revs = list(revs)
3761 revs = list(revs)
3760 revs.extend(opts['rev'])
3762 revs.extend(opts['rev'])
3761 if not revs:
3763 if not revs:
3762 # display both parents as the second parent phase can influence
3764 # display both parents as the second parent phase can influence
3763 # the phase of a merge commit
3765 # the phase of a merge commit
3764 revs = [c.rev() for c in repo[None].parents()]
3766 revs = [c.rev() for c in repo[None].parents()]
3765
3767
3766 revs = scmutil.revrange(repo, revs)
3768 revs = scmutil.revrange(repo, revs)
3767
3769
3768 lock = None
3770 lock = None
3769 ret = 0
3771 ret = 0
3770 if targetphase is None:
3772 if targetphase is None:
3771 # display
3773 # display
3772 for r in revs:
3774 for r in revs:
3773 ctx = repo[r]
3775 ctx = repo[r]
3774 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
3776 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
3775 else:
3777 else:
3776 tr = None
3778 tr = None
3777 lock = repo.lock()
3779 lock = repo.lock()
3778 try:
3780 try:
3779 tr = repo.transaction("phase")
3781 tr = repo.transaction("phase")
3780 # set phase
3782 # set phase
3781 if not revs:
3783 if not revs:
3782 raise error.Abort(_('empty revision set'))
3784 raise error.Abort(_('empty revision set'))
3783 nodes = [repo[r].node() for r in revs]
3785 nodes = [repo[r].node() for r in revs]
3784 # moving revision from public to draft may hide them
3786 # moving revision from public to draft may hide them
3785 # We have to check result on an unfiltered repository
3787 # We have to check result on an unfiltered repository
3786 unfi = repo.unfiltered()
3788 unfi = repo.unfiltered()
3787 getphase = unfi._phasecache.phase
3789 getphase = unfi._phasecache.phase
3788 olddata = [getphase(unfi, r) for r in unfi]
3790 olddata = [getphase(unfi, r) for r in unfi]
3789 phases.advanceboundary(repo, tr, targetphase, nodes)
3791 phases.advanceboundary(repo, tr, targetphase, nodes)
3790 if opts['force']:
3792 if opts['force']:
3791 phases.retractboundary(repo, tr, targetphase, nodes)
3793 phases.retractboundary(repo, tr, targetphase, nodes)
3792 tr.close()
3794 tr.close()
3793 finally:
3795 finally:
3794 if tr is not None:
3796 if tr is not None:
3795 tr.release()
3797 tr.release()
3796 lock.release()
3798 lock.release()
3797 getphase = unfi._phasecache.phase
3799 getphase = unfi._phasecache.phase
3798 newdata = [getphase(unfi, r) for r in unfi]
3800 newdata = [getphase(unfi, r) for r in unfi]
3799 changes = sum(newdata[r] != olddata[r] for r in unfi)
3801 changes = sum(newdata[r] != olddata[r] for r in unfi)
3800 cl = unfi.changelog
3802 cl = unfi.changelog
3801 rejected = [n for n in nodes
3803 rejected = [n for n in nodes
3802 if newdata[cl.rev(n)] < targetphase]
3804 if newdata[cl.rev(n)] < targetphase]
3803 if rejected:
3805 if rejected:
3804 ui.warn(_('cannot move %i changesets to a higher '
3806 ui.warn(_('cannot move %i changesets to a higher '
3805 'phase, use --force\n') % len(rejected))
3807 'phase, use --force\n') % len(rejected))
3806 ret = 1
3808 ret = 1
3807 if changes:
3809 if changes:
3808 msg = _('phase changed for %i changesets\n') % changes
3810 msg = _('phase changed for %i changesets\n') % changes
3809 if ret:
3811 if ret:
3810 ui.status(msg)
3812 ui.status(msg)
3811 else:
3813 else:
3812 ui.note(msg)
3814 ui.note(msg)
3813 else:
3815 else:
3814 ui.warn(_('no phases changed\n'))
3816 ui.warn(_('no phases changed\n'))
3815 return ret
3817 return ret
3816
3818
3817 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
3819 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
3818 """Run after a changegroup has been added via pull/unbundle
3820 """Run after a changegroup has been added via pull/unbundle
3819
3821
3820 This takes arguments below:
3822 This takes arguments below:
3821
3823
3822 :modheads: change of heads by pull/unbundle
3824 :modheads: change of heads by pull/unbundle
3823 :optupdate: updating working directory is needed or not
3825 :optupdate: updating working directory is needed or not
3824 :checkout: update destination revision (or None to default destination)
3826 :checkout: update destination revision (or None to default destination)
3825 :brev: a name, which might be a bookmark to be activated after updating
3827 :brev: a name, which might be a bookmark to be activated after updating
3826 """
3828 """
3827 if modheads == 0:
3829 if modheads == 0:
3828 return
3830 return
3829 if optupdate:
3831 if optupdate:
3830 try:
3832 try:
3831 return hg.updatetotally(ui, repo, checkout, brev)
3833 return hg.updatetotally(ui, repo, checkout, brev)
3832 except error.UpdateAbort as inst:
3834 except error.UpdateAbort as inst:
3833 msg = _("not updating: %s") % str(inst)
3835 msg = _("not updating: %s") % str(inst)
3834 hint = inst.hint
3836 hint = inst.hint
3835 raise error.UpdateAbort(msg, hint=hint)
3837 raise error.UpdateAbort(msg, hint=hint)
3836 if modheads > 1:
3838 if modheads > 1:
3837 currentbranchheads = len(repo.branchheads())
3839 currentbranchheads = len(repo.branchheads())
3838 if currentbranchheads == modheads:
3840 if currentbranchheads == modheads:
3839 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3841 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3840 elif currentbranchheads > 1:
3842 elif currentbranchheads > 1:
3841 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
3843 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
3842 "merge)\n"))
3844 "merge)\n"))
3843 else:
3845 else:
3844 ui.status(_("(run 'hg heads' to see heads)\n"))
3846 ui.status(_("(run 'hg heads' to see heads)\n"))
3845 else:
3847 else:
3846 ui.status(_("(run 'hg update' to get a working copy)\n"))
3848 ui.status(_("(run 'hg update' to get a working copy)\n"))
3847
3849
3848 @command('^pull',
3850 @command('^pull',
3849 [('u', 'update', None,
3851 [('u', 'update', None,
3850 _('update to new branch head if changesets were pulled')),
3852 _('update to new branch head if changesets were pulled')),
3851 ('f', 'force', None, _('run even when remote repository is unrelated')),
3853 ('f', 'force', None, _('run even when remote repository is unrelated')),
3852 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3854 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3853 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
3855 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
3854 ('b', 'branch', [], _('a specific branch you would like to pull'),
3856 ('b', 'branch', [], _('a specific branch you would like to pull'),
3855 _('BRANCH')),
3857 _('BRANCH')),
3856 ] + remoteopts,
3858 ] + remoteopts,
3857 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
3859 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
3858 def pull(ui, repo, source="default", **opts):
3860 def pull(ui, repo, source="default", **opts):
3859 """pull changes from the specified source
3861 """pull changes from the specified source
3860
3862
3861 Pull changes from a remote repository to a local one.
3863 Pull changes from a remote repository to a local one.
3862
3864
3863 This finds all changes from the repository at the specified path
3865 This finds all changes from the repository at the specified path
3864 or URL and adds them to a local repository (the current one unless
3866 or URL and adds them to a local repository (the current one unless
3865 -R is specified). By default, this does not update the copy of the
3867 -R is specified). By default, this does not update the copy of the
3866 project in the working directory.
3868 project in the working directory.
3867
3869
3868 Use :hg:`incoming` if you want to see what would have been added
3870 Use :hg:`incoming` if you want to see what would have been added
3869 by a pull at the time you issued this command. If you then decide
3871 by a pull at the time you issued this command. If you then decide
3870 to add those changes to the repository, you should use :hg:`pull
3872 to add those changes to the repository, you should use :hg:`pull
3871 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3873 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3872
3874
3873 If SOURCE is omitted, the 'default' path will be used.
3875 If SOURCE is omitted, the 'default' path will be used.
3874 See :hg:`help urls` for more information.
3876 See :hg:`help urls` for more information.
3875
3877
3876 Specifying bookmark as ``.`` is equivalent to specifying the active
3878 Specifying bookmark as ``.`` is equivalent to specifying the active
3877 bookmark's name.
3879 bookmark's name.
3878
3880
3879 Returns 0 on success, 1 if an update had unresolved files.
3881 Returns 0 on success, 1 if an update had unresolved files.
3880 """
3882 """
3881
3883
3882 opts = pycompat.byteskwargs(opts)
3884 opts = pycompat.byteskwargs(opts)
3883 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
3885 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
3884 msg = _('update destination required by configuration')
3886 msg = _('update destination required by configuration')
3885 hint = _('use hg pull followed by hg update DEST')
3887 hint = _('use hg pull followed by hg update DEST')
3886 raise error.Abort(msg, hint=hint)
3888 raise error.Abort(msg, hint=hint)
3887
3889
3888 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3890 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3889 ui.status(_('pulling from %s\n') % util.hidepassword(source))
3891 ui.status(_('pulling from %s\n') % util.hidepassword(source))
3890 other = hg.peer(repo, opts, source)
3892 other = hg.peer(repo, opts, source)
3891 try:
3893 try:
3892 revs, checkout = hg.addbranchrevs(repo, other, branches,
3894 revs, checkout = hg.addbranchrevs(repo, other, branches,
3893 opts.get('rev'))
3895 opts.get('rev'))
3894
3896
3895
3897
3896 pullopargs = {}
3898 pullopargs = {}
3897 if opts.get('bookmark'):
3899 if opts.get('bookmark'):
3898 if not revs:
3900 if not revs:
3899 revs = []
3901 revs = []
3900 # The list of bookmark used here is not the one used to actually
3902 # The list of bookmark used here is not the one used to actually
3901 # update the bookmark name. This can result in the revision pulled
3903 # update the bookmark name. This can result in the revision pulled
3902 # not ending up with the name of the bookmark because of a race
3904 # not ending up with the name of the bookmark because of a race
3903 # condition on the server. (See issue 4689 for details)
3905 # condition on the server. (See issue 4689 for details)
3904 remotebookmarks = other.listkeys('bookmarks')
3906 remotebookmarks = other.listkeys('bookmarks')
3905 pullopargs['remotebookmarks'] = remotebookmarks
3907 pullopargs['remotebookmarks'] = remotebookmarks
3906 for b in opts['bookmark']:
3908 for b in opts['bookmark']:
3907 b = repo._bookmarks.expandname(b)
3909 b = repo._bookmarks.expandname(b)
3908 if b not in remotebookmarks:
3910 if b not in remotebookmarks:
3909 raise error.Abort(_('remote bookmark %s not found!') % b)
3911 raise error.Abort(_('remote bookmark %s not found!') % b)
3910 revs.append(remotebookmarks[b])
3912 revs.append(remotebookmarks[b])
3911
3913
3912 if revs:
3914 if revs:
3913 try:
3915 try:
3914 # When 'rev' is a bookmark name, we cannot guarantee that it
3916 # When 'rev' is a bookmark name, we cannot guarantee that it
3915 # will be updated with that name because of a race condition
3917 # will be updated with that name because of a race condition
3916 # server side. (See issue 4689 for details)
3918 # server side. (See issue 4689 for details)
3917 oldrevs = revs
3919 oldrevs = revs
3918 revs = [] # actually, nodes
3920 revs = [] # actually, nodes
3919 for r in oldrevs:
3921 for r in oldrevs:
3920 node = other.lookup(r)
3922 node = other.lookup(r)
3921 revs.append(node)
3923 revs.append(node)
3922 if r == checkout:
3924 if r == checkout:
3923 checkout = node
3925 checkout = node
3924 except error.CapabilityError:
3926 except error.CapabilityError:
3925 err = _("other repository doesn't support revision lookup, "
3927 err = _("other repository doesn't support revision lookup, "
3926 "so a rev cannot be specified.")
3928 "so a rev cannot be specified.")
3927 raise error.Abort(err)
3929 raise error.Abort(err)
3928
3930
3929 pullopargs.update(opts.get('opargs', {}))
3931 pullopargs.update(opts.get('opargs', {}))
3930 modheads = exchange.pull(repo, other, heads=revs,
3932 modheads = exchange.pull(repo, other, heads=revs,
3931 force=opts.get('force'),
3933 force=opts.get('force'),
3932 bookmarks=opts.get('bookmark', ()),
3934 bookmarks=opts.get('bookmark', ()),
3933 opargs=pullopargs).cgresult
3935 opargs=pullopargs).cgresult
3934
3936
3935 # brev is a name, which might be a bookmark to be activated at
3937 # brev is a name, which might be a bookmark to be activated at
3936 # the end of the update. In other words, it is an explicit
3938 # the end of the update. In other words, it is an explicit
3937 # destination of the update
3939 # destination of the update
3938 brev = None
3940 brev = None
3939
3941
3940 if checkout:
3942 if checkout:
3941 checkout = str(repo.changelog.rev(checkout))
3943 checkout = str(repo.changelog.rev(checkout))
3942
3944
3943 # order below depends on implementation of
3945 # order below depends on implementation of
3944 # hg.addbranchrevs(). opts['bookmark'] is ignored,
3946 # hg.addbranchrevs(). opts['bookmark'] is ignored,
3945 # because 'checkout' is determined without it.
3947 # because 'checkout' is determined without it.
3946 if opts.get('rev'):
3948 if opts.get('rev'):
3947 brev = opts['rev'][0]
3949 brev = opts['rev'][0]
3948 elif opts.get('branch'):
3950 elif opts.get('branch'):
3949 brev = opts['branch'][0]
3951 brev = opts['branch'][0]
3950 else:
3952 else:
3951 brev = branches[0]
3953 brev = branches[0]
3952 repo._subtoppath = source
3954 repo._subtoppath = source
3953 try:
3955 try:
3954 ret = postincoming(ui, repo, modheads, opts.get('update'),
3956 ret = postincoming(ui, repo, modheads, opts.get('update'),
3955 checkout, brev)
3957 checkout, brev)
3956
3958
3957 finally:
3959 finally:
3958 del repo._subtoppath
3960 del repo._subtoppath
3959
3961
3960 finally:
3962 finally:
3961 other.close()
3963 other.close()
3962 return ret
3964 return ret
3963
3965
3964 @command('^push',
3966 @command('^push',
3965 [('f', 'force', None, _('force push')),
3967 [('f', 'force', None, _('force push')),
3966 ('r', 'rev', [],
3968 ('r', 'rev', [],
3967 _('a changeset intended to be included in the destination'),
3969 _('a changeset intended to be included in the destination'),
3968 _('REV')),
3970 _('REV')),
3969 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
3971 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
3970 ('b', 'branch', [],
3972 ('b', 'branch', [],
3971 _('a specific branch you would like to push'), _('BRANCH')),
3973 _('a specific branch you would like to push'), _('BRANCH')),
3972 ('', 'new-branch', False, _('allow pushing a new branch')),
3974 ('', 'new-branch', False, _('allow pushing a new branch')),
3973 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
3975 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
3974 ] + remoteopts,
3976 ] + remoteopts,
3975 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
3977 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
3976 def push(ui, repo, dest=None, **opts):
3978 def push(ui, repo, dest=None, **opts):
3977 """push changes to the specified destination
3979 """push changes to the specified destination
3978
3980
3979 Push changesets from the local repository to the specified
3981 Push changesets from the local repository to the specified
3980 destination.
3982 destination.
3981
3983
3982 This operation is symmetrical to pull: it is identical to a pull
3984 This operation is symmetrical to pull: it is identical to a pull
3983 in the destination repository from the current one.
3985 in the destination repository from the current one.
3984
3986
3985 By default, push will not allow creation of new heads at the
3987 By default, push will not allow creation of new heads at the
3986 destination, since multiple heads would make it unclear which head
3988 destination, since multiple heads would make it unclear which head
3987 to use. In this situation, it is recommended to pull and merge
3989 to use. In this situation, it is recommended to pull and merge
3988 before pushing.
3990 before pushing.
3989
3991
3990 Use --new-branch if you want to allow push to create a new named
3992 Use --new-branch if you want to allow push to create a new named
3991 branch that is not present at the destination. This allows you to
3993 branch that is not present at the destination. This allows you to
3992 only create a new branch without forcing other changes.
3994 only create a new branch without forcing other changes.
3993
3995
3994 .. note::
3996 .. note::
3995
3997
3996 Extra care should be taken with the -f/--force option,
3998 Extra care should be taken with the -f/--force option,
3997 which will push all new heads on all branches, an action which will
3999 which will push all new heads on all branches, an action which will
3998 almost always cause confusion for collaborators.
4000 almost always cause confusion for collaborators.
3999
4001
4000 If -r/--rev is used, the specified revision and all its ancestors
4002 If -r/--rev is used, the specified revision and all its ancestors
4001 will be pushed to the remote repository.
4003 will be pushed to the remote repository.
4002
4004
4003 If -B/--bookmark is used, the specified bookmarked revision, its
4005 If -B/--bookmark is used, the specified bookmarked revision, its
4004 ancestors, and the bookmark will be pushed to the remote
4006 ancestors, and the bookmark will be pushed to the remote
4005 repository. Specifying ``.`` is equivalent to specifying the active
4007 repository. Specifying ``.`` is equivalent to specifying the active
4006 bookmark's name.
4008 bookmark's name.
4007
4009
4008 Please see :hg:`help urls` for important details about ``ssh://``
4010 Please see :hg:`help urls` for important details about ``ssh://``
4009 URLs. If DESTINATION is omitted, a default path will be used.
4011 URLs. If DESTINATION is omitted, a default path will be used.
4010
4012
4011 .. container:: verbose
4013 .. container:: verbose
4012
4014
4013 The --pushvars option sends strings to the server that become
4015 The --pushvars option sends strings to the server that become
4014 environment variables prepended with ``HG_USERVAR_``. For example,
4016 environment variables prepended with ``HG_USERVAR_``. For example,
4015 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4017 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4016 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4018 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4017
4019
4018 pushvars can provide for user-overridable hooks as well as set debug
4020 pushvars can provide for user-overridable hooks as well as set debug
4019 levels. One example is having a hook that blocks commits containing
4021 levels. One example is having a hook that blocks commits containing
4020 conflict markers, but enables the user to override the hook if the file
4022 conflict markers, but enables the user to override the hook if the file
4021 is using conflict markers for testing purposes or the file format has
4023 is using conflict markers for testing purposes or the file format has
4022 strings that look like conflict markers.
4024 strings that look like conflict markers.
4023
4025
4024 By default, servers will ignore `--pushvars`. To enable it add the
4026 By default, servers will ignore `--pushvars`. To enable it add the
4025 following to your configuration file::
4027 following to your configuration file::
4026
4028
4027 [push]
4029 [push]
4028 pushvars.server = true
4030 pushvars.server = true
4029
4031
4030 Returns 0 if push was successful, 1 if nothing to push.
4032 Returns 0 if push was successful, 1 if nothing to push.
4031 """
4033 """
4032
4034
4033 opts = pycompat.byteskwargs(opts)
4035 opts = pycompat.byteskwargs(opts)
4034 if opts.get('bookmark'):
4036 if opts.get('bookmark'):
4035 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4037 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4036 for b in opts['bookmark']:
4038 for b in opts['bookmark']:
4037 # translate -B options to -r so changesets get pushed
4039 # translate -B options to -r so changesets get pushed
4038 b = repo._bookmarks.expandname(b)
4040 b = repo._bookmarks.expandname(b)
4039 if b in repo._bookmarks:
4041 if b in repo._bookmarks:
4040 opts.setdefault('rev', []).append(b)
4042 opts.setdefault('rev', []).append(b)
4041 else:
4043 else:
4042 # if we try to push a deleted bookmark, translate it to null
4044 # if we try to push a deleted bookmark, translate it to null
4043 # this lets simultaneous -r, -b options continue working
4045 # this lets simultaneous -r, -b options continue working
4044 opts.setdefault('rev', []).append("null")
4046 opts.setdefault('rev', []).append("null")
4045
4047
4046 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4048 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4047 if not path:
4049 if not path:
4048 raise error.Abort(_('default repository not configured!'),
4050 raise error.Abort(_('default repository not configured!'),
4049 hint=_("see 'hg help config.paths'"))
4051 hint=_("see 'hg help config.paths'"))
4050 dest = path.pushloc or path.loc
4052 dest = path.pushloc or path.loc
4051 branches = (path.branch, opts.get('branch') or [])
4053 branches = (path.branch, opts.get('branch') or [])
4052 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4054 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4053 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4055 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4054 other = hg.peer(repo, opts, dest)
4056 other = hg.peer(repo, opts, dest)
4055
4057
4056 if revs:
4058 if revs:
4057 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4059 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4058 if not revs:
4060 if not revs:
4059 raise error.Abort(_("specified revisions evaluate to an empty set"),
4061 raise error.Abort(_("specified revisions evaluate to an empty set"),
4060 hint=_("use different revision arguments"))
4062 hint=_("use different revision arguments"))
4061 elif path.pushrev:
4063 elif path.pushrev:
4062 # It doesn't make any sense to specify ancestor revisions. So limit
4064 # It doesn't make any sense to specify ancestor revisions. So limit
4063 # to DAG heads to make discovery simpler.
4065 # to DAG heads to make discovery simpler.
4064 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4066 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4065 revs = scmutil.revrange(repo, [expr])
4067 revs = scmutil.revrange(repo, [expr])
4066 revs = [repo[rev].node() for rev in revs]
4068 revs = [repo[rev].node() for rev in revs]
4067 if not revs:
4069 if not revs:
4068 raise error.Abort(_('default push revset for path evaluates to an '
4070 raise error.Abort(_('default push revset for path evaluates to an '
4069 'empty set'))
4071 'empty set'))
4070
4072
4071 repo._subtoppath = dest
4073 repo._subtoppath = dest
4072 try:
4074 try:
4073 # push subrepos depth-first for coherent ordering
4075 # push subrepos depth-first for coherent ordering
4074 c = repo['']
4076 c = repo['']
4075 subs = c.substate # only repos that are committed
4077 subs = c.substate # only repos that are committed
4076 for s in sorted(subs):
4078 for s in sorted(subs):
4077 result = c.sub(s).push(opts)
4079 result = c.sub(s).push(opts)
4078 if result == 0:
4080 if result == 0:
4079 return not result
4081 return not result
4080 finally:
4082 finally:
4081 del repo._subtoppath
4083 del repo._subtoppath
4082
4084
4083 pushvars = opts.get('pushvars')
4085 pushvars = opts.get('pushvars')
4084 if pushvars:
4086 if pushvars:
4085 shellvars = {}
4087 shellvars = {}
4086 for raw in pushvars:
4088 for raw in pushvars:
4087 if '=' not in raw:
4089 if '=' not in raw:
4088 msg = ("unable to parse variable '%s', should follow "
4090 msg = ("unable to parse variable '%s', should follow "
4089 "'KEY=VALUE' or 'KEY=' format")
4091 "'KEY=VALUE' or 'KEY=' format")
4090 raise error.Abort(msg % raw)
4092 raise error.Abort(msg % raw)
4091 k, v = raw.split('=', 1)
4093 k, v = raw.split('=', 1)
4092 shellvars[k] = v
4094 shellvars[k] = v
4093
4095
4094 repo._shellvars = shellvars
4096 repo._shellvars = shellvars
4095
4097
4096 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4098 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4097 newbranch=opts.get('new_branch'),
4099 newbranch=opts.get('new_branch'),
4098 bookmarks=opts.get('bookmark', ()),
4100 bookmarks=opts.get('bookmark', ()),
4099 opargs=opts.get('opargs'))
4101 opargs=opts.get('opargs'))
4100
4102
4101 if pushvars:
4103 if pushvars:
4102 del repo._shellvars
4104 del repo._shellvars
4103
4105
4104 result = not pushop.cgresult
4106 result = not pushop.cgresult
4105
4107
4106 if pushop.bkresult is not None:
4108 if pushop.bkresult is not None:
4107 if pushop.bkresult == 2:
4109 if pushop.bkresult == 2:
4108 result = 2
4110 result = 2
4109 elif not result and pushop.bkresult:
4111 elif not result and pushop.bkresult:
4110 result = 2
4112 result = 2
4111
4113
4112 return result
4114 return result
4113
4115
4114 @command('recover', [])
4116 @command('recover', [])
4115 def recover(ui, repo):
4117 def recover(ui, repo):
4116 """roll back an interrupted transaction
4118 """roll back an interrupted transaction
4117
4119
4118 Recover from an interrupted commit or pull.
4120 Recover from an interrupted commit or pull.
4119
4121
4120 This command tries to fix the repository status after an
4122 This command tries to fix the repository status after an
4121 interrupted operation. It should only be necessary when Mercurial
4123 interrupted operation. It should only be necessary when Mercurial
4122 suggests it.
4124 suggests it.
4123
4125
4124 Returns 0 if successful, 1 if nothing to recover or verify fails.
4126 Returns 0 if successful, 1 if nothing to recover or verify fails.
4125 """
4127 """
4126 if repo.recover():
4128 if repo.recover():
4127 return hg.verify(repo)
4129 return hg.verify(repo)
4128 return 1
4130 return 1
4129
4131
4130 @command('^remove|rm',
4132 @command('^remove|rm',
4131 [('A', 'after', None, _('record delete for missing files')),
4133 [('A', 'after', None, _('record delete for missing files')),
4132 ('f', 'force', None,
4134 ('f', 'force', None,
4133 _('forget added files, delete modified files')),
4135 _('forget added files, delete modified files')),
4134 ] + subrepoopts + walkopts,
4136 ] + subrepoopts + walkopts,
4135 _('[OPTION]... FILE...'),
4137 _('[OPTION]... FILE...'),
4136 inferrepo=True)
4138 inferrepo=True)
4137 def remove(ui, repo, *pats, **opts):
4139 def remove(ui, repo, *pats, **opts):
4138 """remove the specified files on the next commit
4140 """remove the specified files on the next commit
4139
4141
4140 Schedule the indicated files for removal from the current branch.
4142 Schedule the indicated files for removal from the current branch.
4141
4143
4142 This command schedules the files to be removed at the next commit.
4144 This command schedules the files to be removed at the next commit.
4143 To undo a remove before that, see :hg:`revert`. To undo added
4145 To undo a remove before that, see :hg:`revert`. To undo added
4144 files, see :hg:`forget`.
4146 files, see :hg:`forget`.
4145
4147
4146 .. container:: verbose
4148 .. container:: verbose
4147
4149
4148 -A/--after can be used to remove only files that have already
4150 -A/--after can be used to remove only files that have already
4149 been deleted, -f/--force can be used to force deletion, and -Af
4151 been deleted, -f/--force can be used to force deletion, and -Af
4150 can be used to remove files from the next revision without
4152 can be used to remove files from the next revision without
4151 deleting them from the working directory.
4153 deleting them from the working directory.
4152
4154
4153 The following table details the behavior of remove for different
4155 The following table details the behavior of remove for different
4154 file states (columns) and option combinations (rows). The file
4156 file states (columns) and option combinations (rows). The file
4155 states are Added [A], Clean [C], Modified [M] and Missing [!]
4157 states are Added [A], Clean [C], Modified [M] and Missing [!]
4156 (as reported by :hg:`status`). The actions are Warn, Remove
4158 (as reported by :hg:`status`). The actions are Warn, Remove
4157 (from branch) and Delete (from disk):
4159 (from branch) and Delete (from disk):
4158
4160
4159 ========= == == == ==
4161 ========= == == == ==
4160 opt/state A C M !
4162 opt/state A C M !
4161 ========= == == == ==
4163 ========= == == == ==
4162 none W RD W R
4164 none W RD W R
4163 -f R RD RD R
4165 -f R RD RD R
4164 -A W W W R
4166 -A W W W R
4165 -Af R R R R
4167 -Af R R R R
4166 ========= == == == ==
4168 ========= == == == ==
4167
4169
4168 .. note::
4170 .. note::
4169
4171
4170 :hg:`remove` never deletes files in Added [A] state from the
4172 :hg:`remove` never deletes files in Added [A] state from the
4171 working directory, not even if ``--force`` is specified.
4173 working directory, not even if ``--force`` is specified.
4172
4174
4173 Returns 0 on success, 1 if any warnings encountered.
4175 Returns 0 on success, 1 if any warnings encountered.
4174 """
4176 """
4175
4177
4176 opts = pycompat.byteskwargs(opts)
4178 opts = pycompat.byteskwargs(opts)
4177 after, force = opts.get('after'), opts.get('force')
4179 after, force = opts.get('after'), opts.get('force')
4178 if not pats and not after:
4180 if not pats and not after:
4179 raise error.Abort(_('no files specified'))
4181 raise error.Abort(_('no files specified'))
4180
4182
4181 m = scmutil.match(repo[None], pats, opts)
4183 m = scmutil.match(repo[None], pats, opts)
4182 subrepos = opts.get('subrepos')
4184 subrepos = opts.get('subrepos')
4183 return cmdutil.remove(ui, repo, m, "", after, force, subrepos)
4185 return cmdutil.remove(ui, repo, m, "", after, force, subrepos)
4184
4186
4185 @command('rename|move|mv',
4187 @command('rename|move|mv',
4186 [('A', 'after', None, _('record a rename that has already occurred')),
4188 [('A', 'after', None, _('record a rename that has already occurred')),
4187 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4189 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4188 ] + walkopts + dryrunopts,
4190 ] + walkopts + dryrunopts,
4189 _('[OPTION]... SOURCE... DEST'))
4191 _('[OPTION]... SOURCE... DEST'))
4190 def rename(ui, repo, *pats, **opts):
4192 def rename(ui, repo, *pats, **opts):
4191 """rename files; equivalent of copy + remove
4193 """rename files; equivalent of copy + remove
4192
4194
4193 Mark dest as copies of sources; mark sources for deletion. If dest
4195 Mark dest as copies of sources; mark sources for deletion. If dest
4194 is a directory, copies are put in that directory. If dest is a
4196 is a directory, copies are put in that directory. If dest is a
4195 file, there can only be one source.
4197 file, there can only be one source.
4196
4198
4197 By default, this command copies the contents of files as they
4199 By default, this command copies the contents of files as they
4198 exist in the working directory. If invoked with -A/--after, the
4200 exist in the working directory. If invoked with -A/--after, the
4199 operation is recorded, but no copying is performed.
4201 operation is recorded, but no copying is performed.
4200
4202
4201 This command takes effect at the next commit. To undo a rename
4203 This command takes effect at the next commit. To undo a rename
4202 before that, see :hg:`revert`.
4204 before that, see :hg:`revert`.
4203
4205
4204 Returns 0 on success, 1 if errors are encountered.
4206 Returns 0 on success, 1 if errors are encountered.
4205 """
4207 """
4206 opts = pycompat.byteskwargs(opts)
4208 opts = pycompat.byteskwargs(opts)
4207 with repo.wlock(False):
4209 with repo.wlock(False):
4208 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4210 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4209
4211
4210 @command('resolve',
4212 @command('resolve',
4211 [('a', 'all', None, _('select all unresolved files')),
4213 [('a', 'all', None, _('select all unresolved files')),
4212 ('l', 'list', None, _('list state of files needing merge')),
4214 ('l', 'list', None, _('list state of files needing merge')),
4213 ('m', 'mark', None, _('mark files as resolved')),
4215 ('m', 'mark', None, _('mark files as resolved')),
4214 ('u', 'unmark', None, _('mark files as unresolved')),
4216 ('u', 'unmark', None, _('mark files as unresolved')),
4215 ('n', 'no-status', None, _('hide status prefix'))]
4217 ('n', 'no-status', None, _('hide status prefix'))]
4216 + mergetoolopts + walkopts + formatteropts,
4218 + mergetoolopts + walkopts + formatteropts,
4217 _('[OPTION]... [FILE]...'),
4219 _('[OPTION]... [FILE]...'),
4218 inferrepo=True)
4220 inferrepo=True)
4219 def resolve(ui, repo, *pats, **opts):
4221 def resolve(ui, repo, *pats, **opts):
4220 """redo merges or set/view the merge status of files
4222 """redo merges or set/view the merge status of files
4221
4223
4222 Merges with unresolved conflicts are often the result of
4224 Merges with unresolved conflicts are often the result of
4223 non-interactive merging using the ``internal:merge`` configuration
4225 non-interactive merging using the ``internal:merge`` configuration
4224 setting, or a command-line merge tool like ``diff3``. The resolve
4226 setting, or a command-line merge tool like ``diff3``. The resolve
4225 command is used to manage the files involved in a merge, after
4227 command is used to manage the files involved in a merge, after
4226 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4228 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4227 working directory must have two parents). See :hg:`help
4229 working directory must have two parents). See :hg:`help
4228 merge-tools` for information on configuring merge tools.
4230 merge-tools` for information on configuring merge tools.
4229
4231
4230 The resolve command can be used in the following ways:
4232 The resolve command can be used in the following ways:
4231
4233
4232 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4234 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4233 files, discarding any previous merge attempts. Re-merging is not
4235 files, discarding any previous merge attempts. Re-merging is not
4234 performed for files already marked as resolved. Use ``--all/-a``
4236 performed for files already marked as resolved. Use ``--all/-a``
4235 to select all unresolved files. ``--tool`` can be used to specify
4237 to select all unresolved files. ``--tool`` can be used to specify
4236 the merge tool used for the given files. It overrides the HGMERGE
4238 the merge tool used for the given files. It overrides the HGMERGE
4237 environment variable and your configuration files. Previous file
4239 environment variable and your configuration files. Previous file
4238 contents are saved with a ``.orig`` suffix.
4240 contents are saved with a ``.orig`` suffix.
4239
4241
4240 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4242 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4241 (e.g. after having manually fixed-up the files). The default is
4243 (e.g. after having manually fixed-up the files). The default is
4242 to mark all unresolved files.
4244 to mark all unresolved files.
4243
4245
4244 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4246 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4245 default is to mark all resolved files.
4247 default is to mark all resolved files.
4246
4248
4247 - :hg:`resolve -l`: list files which had or still have conflicts.
4249 - :hg:`resolve -l`: list files which had or still have conflicts.
4248 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4250 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4249 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4251 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4250 the list. See :hg:`help filesets` for details.
4252 the list. See :hg:`help filesets` for details.
4251
4253
4252 .. note::
4254 .. note::
4253
4255
4254 Mercurial will not let you commit files with unresolved merge
4256 Mercurial will not let you commit files with unresolved merge
4255 conflicts. You must use :hg:`resolve -m ...` before you can
4257 conflicts. You must use :hg:`resolve -m ...` before you can
4256 commit after a conflicting merge.
4258 commit after a conflicting merge.
4257
4259
4258 Returns 0 on success, 1 if any files fail a resolve attempt.
4260 Returns 0 on success, 1 if any files fail a resolve attempt.
4259 """
4261 """
4260
4262
4261 opts = pycompat.byteskwargs(opts)
4263 opts = pycompat.byteskwargs(opts)
4262 flaglist = 'all mark unmark list no_status'.split()
4264 flaglist = 'all mark unmark list no_status'.split()
4263 all, mark, unmark, show, nostatus = \
4265 all, mark, unmark, show, nostatus = \
4264 [opts.get(o) for o in flaglist]
4266 [opts.get(o) for o in flaglist]
4265
4267
4266 if (show and (mark or unmark)) or (mark and unmark):
4268 if (show and (mark or unmark)) or (mark and unmark):
4267 raise error.Abort(_("too many options specified"))
4269 raise error.Abort(_("too many options specified"))
4268 if pats and all:
4270 if pats and all:
4269 raise error.Abort(_("can't specify --all and patterns"))
4271 raise error.Abort(_("can't specify --all and patterns"))
4270 if not (all or pats or show or mark or unmark):
4272 if not (all or pats or show or mark or unmark):
4271 raise error.Abort(_('no files or directories specified'),
4273 raise error.Abort(_('no files or directories specified'),
4272 hint=('use --all to re-merge all unresolved files'))
4274 hint=('use --all to re-merge all unresolved files'))
4273
4275
4274 if show:
4276 if show:
4275 ui.pager('resolve')
4277 ui.pager('resolve')
4276 fm = ui.formatter('resolve', opts)
4278 fm = ui.formatter('resolve', opts)
4277 ms = mergemod.mergestate.read(repo)
4279 ms = mergemod.mergestate.read(repo)
4278 m = scmutil.match(repo[None], pats, opts)
4280 m = scmutil.match(repo[None], pats, opts)
4279 for f in ms:
4281 for f in ms:
4280 if not m(f):
4282 if not m(f):
4281 continue
4283 continue
4282 l = 'resolve.' + {'u': 'unresolved', 'r': 'resolved',
4284 l = 'resolve.' + {'u': 'unresolved', 'r': 'resolved',
4283 'd': 'driverresolved'}[ms[f]]
4285 'd': 'driverresolved'}[ms[f]]
4284 fm.startitem()
4286 fm.startitem()
4285 fm.condwrite(not nostatus, 'status', '%s ', ms[f].upper(), label=l)
4287 fm.condwrite(not nostatus, 'status', '%s ', ms[f].upper(), label=l)
4286 fm.write('path', '%s\n', f, label=l)
4288 fm.write('path', '%s\n', f, label=l)
4287 fm.end()
4289 fm.end()
4288 return 0
4290 return 0
4289
4291
4290 with repo.wlock():
4292 with repo.wlock():
4291 ms = mergemod.mergestate.read(repo)
4293 ms = mergemod.mergestate.read(repo)
4292
4294
4293 if not (ms.active() or repo.dirstate.p2() != nullid):
4295 if not (ms.active() or repo.dirstate.p2() != nullid):
4294 raise error.Abort(
4296 raise error.Abort(
4295 _('resolve command not applicable when not merging'))
4297 _('resolve command not applicable when not merging'))
4296
4298
4297 wctx = repo[None]
4299 wctx = repo[None]
4298
4300
4299 if ms.mergedriver and ms.mdstate() == 'u':
4301 if ms.mergedriver and ms.mdstate() == 'u':
4300 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4302 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4301 ms.commit()
4303 ms.commit()
4302 # allow mark and unmark to go through
4304 # allow mark and unmark to go through
4303 if not mark and not unmark and not proceed:
4305 if not mark and not unmark and not proceed:
4304 return 1
4306 return 1
4305
4307
4306 m = scmutil.match(wctx, pats, opts)
4308 m = scmutil.match(wctx, pats, opts)
4307 ret = 0
4309 ret = 0
4308 didwork = False
4310 didwork = False
4309 runconclude = False
4311 runconclude = False
4310
4312
4311 tocomplete = []
4313 tocomplete = []
4312 for f in ms:
4314 for f in ms:
4313 if not m(f):
4315 if not m(f):
4314 continue
4316 continue
4315
4317
4316 didwork = True
4318 didwork = True
4317
4319
4318 # don't let driver-resolved files be marked, and run the conclude
4320 # don't let driver-resolved files be marked, and run the conclude
4319 # step if asked to resolve
4321 # step if asked to resolve
4320 if ms[f] == "d":
4322 if ms[f] == "d":
4321 exact = m.exact(f)
4323 exact = m.exact(f)
4322 if mark:
4324 if mark:
4323 if exact:
4325 if exact:
4324 ui.warn(_('not marking %s as it is driver-resolved\n')
4326 ui.warn(_('not marking %s as it is driver-resolved\n')
4325 % f)
4327 % f)
4326 elif unmark:
4328 elif unmark:
4327 if exact:
4329 if exact:
4328 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4330 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4329 % f)
4331 % f)
4330 else:
4332 else:
4331 runconclude = True
4333 runconclude = True
4332 continue
4334 continue
4333
4335
4334 if mark:
4336 if mark:
4335 ms.mark(f, "r")
4337 ms.mark(f, "r")
4336 elif unmark:
4338 elif unmark:
4337 ms.mark(f, "u")
4339 ms.mark(f, "u")
4338 else:
4340 else:
4339 # backup pre-resolve (merge uses .orig for its own purposes)
4341 # backup pre-resolve (merge uses .orig for its own purposes)
4340 a = repo.wjoin(f)
4342 a = repo.wjoin(f)
4341 try:
4343 try:
4342 util.copyfile(a, a + ".resolve")
4344 util.copyfile(a, a + ".resolve")
4343 except (IOError, OSError) as inst:
4345 except (IOError, OSError) as inst:
4344 if inst.errno != errno.ENOENT:
4346 if inst.errno != errno.ENOENT:
4345 raise
4347 raise
4346
4348
4347 try:
4349 try:
4348 # preresolve file
4350 # preresolve file
4349 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4351 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4350 'resolve')
4352 'resolve')
4351 complete, r = ms.preresolve(f, wctx)
4353 complete, r = ms.preresolve(f, wctx)
4352 if not complete:
4354 if not complete:
4353 tocomplete.append(f)
4355 tocomplete.append(f)
4354 elif r:
4356 elif r:
4355 ret = 1
4357 ret = 1
4356 finally:
4358 finally:
4357 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4359 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4358 ms.commit()
4360 ms.commit()
4359
4361
4360 # replace filemerge's .orig file with our resolve file, but only
4362 # replace filemerge's .orig file with our resolve file, but only
4361 # for merges that are complete
4363 # for merges that are complete
4362 if complete:
4364 if complete:
4363 try:
4365 try:
4364 util.rename(a + ".resolve",
4366 util.rename(a + ".resolve",
4365 scmutil.origpath(ui, repo, a))
4367 scmutil.origpath(ui, repo, a))
4366 except OSError as inst:
4368 except OSError as inst:
4367 if inst.errno != errno.ENOENT:
4369 if inst.errno != errno.ENOENT:
4368 raise
4370 raise
4369
4371
4370 for f in tocomplete:
4372 for f in tocomplete:
4371 try:
4373 try:
4372 # resolve file
4374 # resolve file
4373 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4375 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4374 'resolve')
4376 'resolve')
4375 r = ms.resolve(f, wctx)
4377 r = ms.resolve(f, wctx)
4376 if r:
4378 if r:
4377 ret = 1
4379 ret = 1
4378 finally:
4380 finally:
4379 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4381 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4380 ms.commit()
4382 ms.commit()
4381
4383
4382 # replace filemerge's .orig file with our resolve file
4384 # replace filemerge's .orig file with our resolve file
4383 a = repo.wjoin(f)
4385 a = repo.wjoin(f)
4384 try:
4386 try:
4385 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
4387 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
4386 except OSError as inst:
4388 except OSError as inst:
4387 if inst.errno != errno.ENOENT:
4389 if inst.errno != errno.ENOENT:
4388 raise
4390 raise
4389
4391
4390 ms.commit()
4392 ms.commit()
4391 ms.recordactions()
4393 ms.recordactions()
4392
4394
4393 if not didwork and pats:
4395 if not didwork and pats:
4394 hint = None
4396 hint = None
4395 if not any([p for p in pats if p.find(':') >= 0]):
4397 if not any([p for p in pats if p.find(':') >= 0]):
4396 pats = ['path:%s' % p for p in pats]
4398 pats = ['path:%s' % p for p in pats]
4397 m = scmutil.match(wctx, pats, opts)
4399 m = scmutil.match(wctx, pats, opts)
4398 for f in ms:
4400 for f in ms:
4399 if not m(f):
4401 if not m(f):
4400 continue
4402 continue
4401 flags = ''.join(['-%s ' % o[0] for o in flaglist
4403 flags = ''.join(['-%s ' % o[0] for o in flaglist
4402 if opts.get(o)])
4404 if opts.get(o)])
4403 hint = _("(try: hg resolve %s%s)\n") % (
4405 hint = _("(try: hg resolve %s%s)\n") % (
4404 flags,
4406 flags,
4405 ' '.join(pats))
4407 ' '.join(pats))
4406 break
4408 break
4407 ui.warn(_("arguments do not match paths that need resolving\n"))
4409 ui.warn(_("arguments do not match paths that need resolving\n"))
4408 if hint:
4410 if hint:
4409 ui.warn(hint)
4411 ui.warn(hint)
4410 elif ms.mergedriver and ms.mdstate() != 's':
4412 elif ms.mergedriver and ms.mdstate() != 's':
4411 # run conclude step when either a driver-resolved file is requested
4413 # run conclude step when either a driver-resolved file is requested
4412 # or there are no driver-resolved files
4414 # or there are no driver-resolved files
4413 # we can't use 'ret' to determine whether any files are unresolved
4415 # we can't use 'ret' to determine whether any files are unresolved
4414 # because we might not have tried to resolve some
4416 # because we might not have tried to resolve some
4415 if ((runconclude or not list(ms.driverresolved()))
4417 if ((runconclude or not list(ms.driverresolved()))
4416 and not list(ms.unresolved())):
4418 and not list(ms.unresolved())):
4417 proceed = mergemod.driverconclude(repo, ms, wctx)
4419 proceed = mergemod.driverconclude(repo, ms, wctx)
4418 ms.commit()
4420 ms.commit()
4419 if not proceed:
4421 if not proceed:
4420 return 1
4422 return 1
4421
4423
4422 # Nudge users into finishing an unfinished operation
4424 # Nudge users into finishing an unfinished operation
4423 unresolvedf = list(ms.unresolved())
4425 unresolvedf = list(ms.unresolved())
4424 driverresolvedf = list(ms.driverresolved())
4426 driverresolvedf = list(ms.driverresolved())
4425 if not unresolvedf and not driverresolvedf:
4427 if not unresolvedf and not driverresolvedf:
4426 ui.status(_('(no more unresolved files)\n'))
4428 ui.status(_('(no more unresolved files)\n'))
4427 cmdutil.checkafterresolved(repo)
4429 cmdutil.checkafterresolved(repo)
4428 elif not unresolvedf:
4430 elif not unresolvedf:
4429 ui.status(_('(no more unresolved files -- '
4431 ui.status(_('(no more unresolved files -- '
4430 'run "hg resolve --all" to conclude)\n'))
4432 'run "hg resolve --all" to conclude)\n'))
4431
4433
4432 return ret
4434 return ret
4433
4435
4434 @command('revert',
4436 @command('revert',
4435 [('a', 'all', None, _('revert all changes when no arguments given')),
4437 [('a', 'all', None, _('revert all changes when no arguments given')),
4436 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4438 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4437 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4439 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4438 ('C', 'no-backup', None, _('do not save backup copies of files')),
4440 ('C', 'no-backup', None, _('do not save backup copies of files')),
4439 ('i', 'interactive', None,
4441 ('i', 'interactive', None,
4440 _('interactively select the changes (EXPERIMENTAL)')),
4442 _('interactively select the changes (EXPERIMENTAL)')),
4441 ] + walkopts + dryrunopts,
4443 ] + walkopts + dryrunopts,
4442 _('[OPTION]... [-r REV] [NAME]...'))
4444 _('[OPTION]... [-r REV] [NAME]...'))
4443 def revert(ui, repo, *pats, **opts):
4445 def revert(ui, repo, *pats, **opts):
4444 """restore files to their checkout state
4446 """restore files to their checkout state
4445
4447
4446 .. note::
4448 .. note::
4447
4449
4448 To check out earlier revisions, you should use :hg:`update REV`.
4450 To check out earlier revisions, you should use :hg:`update REV`.
4449 To cancel an uncommitted merge (and lose your changes),
4451 To cancel an uncommitted merge (and lose your changes),
4450 use :hg:`update --clean .`.
4452 use :hg:`update --clean .`.
4451
4453
4452 With no revision specified, revert the specified files or directories
4454 With no revision specified, revert the specified files or directories
4453 to the contents they had in the parent of the working directory.
4455 to the contents they had in the parent of the working directory.
4454 This restores the contents of files to an unmodified
4456 This restores the contents of files to an unmodified
4455 state and unschedules adds, removes, copies, and renames. If the
4457 state and unschedules adds, removes, copies, and renames. If the
4456 working directory has two parents, you must explicitly specify a
4458 working directory has two parents, you must explicitly specify a
4457 revision.
4459 revision.
4458
4460
4459 Using the -r/--rev or -d/--date options, revert the given files or
4461 Using the -r/--rev or -d/--date options, revert the given files or
4460 directories to their states as of a specific revision. Because
4462 directories to their states as of a specific revision. Because
4461 revert does not change the working directory parents, this will
4463 revert does not change the working directory parents, this will
4462 cause these files to appear modified. This can be helpful to "back
4464 cause these files to appear modified. This can be helpful to "back
4463 out" some or all of an earlier change. See :hg:`backout` for a
4465 out" some or all of an earlier change. See :hg:`backout` for a
4464 related method.
4466 related method.
4465
4467
4466 Modified files are saved with a .orig suffix before reverting.
4468 Modified files are saved with a .orig suffix before reverting.
4467 To disable these backups, use --no-backup. It is possible to store
4469 To disable these backups, use --no-backup. It is possible to store
4468 the backup files in a custom directory relative to the root of the
4470 the backup files in a custom directory relative to the root of the
4469 repository by setting the ``ui.origbackuppath`` configuration
4471 repository by setting the ``ui.origbackuppath`` configuration
4470 option.
4472 option.
4471
4473
4472 See :hg:`help dates` for a list of formats valid for -d/--date.
4474 See :hg:`help dates` for a list of formats valid for -d/--date.
4473
4475
4474 See :hg:`help backout` for a way to reverse the effect of an
4476 See :hg:`help backout` for a way to reverse the effect of an
4475 earlier changeset.
4477 earlier changeset.
4476
4478
4477 Returns 0 on success.
4479 Returns 0 on success.
4478 """
4480 """
4479
4481
4480 if opts.get("date"):
4482 if opts.get("date"):
4481 if opts.get("rev"):
4483 if opts.get("rev"):
4482 raise error.Abort(_("you can't specify a revision and a date"))
4484 raise error.Abort(_("you can't specify a revision and a date"))
4483 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4485 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4484
4486
4485 parent, p2 = repo.dirstate.parents()
4487 parent, p2 = repo.dirstate.parents()
4486 if not opts.get('rev') and p2 != nullid:
4488 if not opts.get('rev') and p2 != nullid:
4487 # revert after merge is a trap for new users (issue2915)
4489 # revert after merge is a trap for new users (issue2915)
4488 raise error.Abort(_('uncommitted merge with no revision specified'),
4490 raise error.Abort(_('uncommitted merge with no revision specified'),
4489 hint=_("use 'hg update' or see 'hg help revert'"))
4491 hint=_("use 'hg update' or see 'hg help revert'"))
4490
4492
4491 ctx = scmutil.revsingle(repo, opts.get('rev'))
4493 ctx = scmutil.revsingle(repo, opts.get('rev'))
4492
4494
4493 if (not (pats or opts.get('include') or opts.get('exclude') or
4495 if (not (pats or opts.get('include') or opts.get('exclude') or
4494 opts.get('all') or opts.get('interactive'))):
4496 opts.get('all') or opts.get('interactive'))):
4495 msg = _("no files or directories specified")
4497 msg = _("no files or directories specified")
4496 if p2 != nullid:
4498 if p2 != nullid:
4497 hint = _("uncommitted merge, use --all to discard all changes,"
4499 hint = _("uncommitted merge, use --all to discard all changes,"
4498 " or 'hg update -C .' to abort the merge")
4500 " or 'hg update -C .' to abort the merge")
4499 raise error.Abort(msg, hint=hint)
4501 raise error.Abort(msg, hint=hint)
4500 dirty = any(repo.status())
4502 dirty = any(repo.status())
4501 node = ctx.node()
4503 node = ctx.node()
4502 if node != parent:
4504 if node != parent:
4503 if dirty:
4505 if dirty:
4504 hint = _("uncommitted changes, use --all to discard all"
4506 hint = _("uncommitted changes, use --all to discard all"
4505 " changes, or 'hg update %s' to update") % ctx.rev()
4507 " changes, or 'hg update %s' to update") % ctx.rev()
4506 else:
4508 else:
4507 hint = _("use --all to revert all files,"
4509 hint = _("use --all to revert all files,"
4508 " or 'hg update %s' to update") % ctx.rev()
4510 " or 'hg update %s' to update") % ctx.rev()
4509 elif dirty:
4511 elif dirty:
4510 hint = _("uncommitted changes, use --all to discard all changes")
4512 hint = _("uncommitted changes, use --all to discard all changes")
4511 else:
4513 else:
4512 hint = _("use --all to revert all files")
4514 hint = _("use --all to revert all files")
4513 raise error.Abort(msg, hint=hint)
4515 raise error.Abort(msg, hint=hint)
4514
4516
4515 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
4517 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
4516
4518
4517 @command('rollback', dryrunopts +
4519 @command('rollback', dryrunopts +
4518 [('f', 'force', False, _('ignore safety measures'))])
4520 [('f', 'force', False, _('ignore safety measures'))])
4519 def rollback(ui, repo, **opts):
4521 def rollback(ui, repo, **opts):
4520 """roll back the last transaction (DANGEROUS) (DEPRECATED)
4522 """roll back the last transaction (DANGEROUS) (DEPRECATED)
4521
4523
4522 Please use :hg:`commit --amend` instead of rollback to correct
4524 Please use :hg:`commit --amend` instead of rollback to correct
4523 mistakes in the last commit.
4525 mistakes in the last commit.
4524
4526
4525 This command should be used with care. There is only one level of
4527 This command should be used with care. There is only one level of
4526 rollback, and there is no way to undo a rollback. It will also
4528 rollback, and there is no way to undo a rollback. It will also
4527 restore the dirstate at the time of the last transaction, losing
4529 restore the dirstate at the time of the last transaction, losing
4528 any dirstate changes since that time. This command does not alter
4530 any dirstate changes since that time. This command does not alter
4529 the working directory.
4531 the working directory.
4530
4532
4531 Transactions are used to encapsulate the effects of all commands
4533 Transactions are used to encapsulate the effects of all commands
4532 that create new changesets or propagate existing changesets into a
4534 that create new changesets or propagate existing changesets into a
4533 repository.
4535 repository.
4534
4536
4535 .. container:: verbose
4537 .. container:: verbose
4536
4538
4537 For example, the following commands are transactional, and their
4539 For example, the following commands are transactional, and their
4538 effects can be rolled back:
4540 effects can be rolled back:
4539
4541
4540 - commit
4542 - commit
4541 - import
4543 - import
4542 - pull
4544 - pull
4543 - push (with this repository as the destination)
4545 - push (with this repository as the destination)
4544 - unbundle
4546 - unbundle
4545
4547
4546 To avoid permanent data loss, rollback will refuse to rollback a
4548 To avoid permanent data loss, rollback will refuse to rollback a
4547 commit transaction if it isn't checked out. Use --force to
4549 commit transaction if it isn't checked out. Use --force to
4548 override this protection.
4550 override this protection.
4549
4551
4550 The rollback command can be entirely disabled by setting the
4552 The rollback command can be entirely disabled by setting the
4551 ``ui.rollback`` configuration setting to false. If you're here
4553 ``ui.rollback`` configuration setting to false. If you're here
4552 because you want to use rollback and it's disabled, you can
4554 because you want to use rollback and it's disabled, you can
4553 re-enable the command by setting ``ui.rollback`` to true.
4555 re-enable the command by setting ``ui.rollback`` to true.
4554
4556
4555 This command is not intended for use on public repositories. Once
4557 This command is not intended for use on public repositories. Once
4556 changes are visible for pull by other users, rolling a transaction
4558 changes are visible for pull by other users, rolling a transaction
4557 back locally is ineffective (someone else may already have pulled
4559 back locally is ineffective (someone else may already have pulled
4558 the changes). Furthermore, a race is possible with readers of the
4560 the changes). Furthermore, a race is possible with readers of the
4559 repository; for example an in-progress pull from the repository
4561 repository; for example an in-progress pull from the repository
4560 may fail if a rollback is performed.
4562 may fail if a rollback is performed.
4561
4563
4562 Returns 0 on success, 1 if no rollback data is available.
4564 Returns 0 on success, 1 if no rollback data is available.
4563 """
4565 """
4564 if not ui.configbool('ui', 'rollback'):
4566 if not ui.configbool('ui', 'rollback'):
4565 raise error.Abort(_('rollback is disabled because it is unsafe'),
4567 raise error.Abort(_('rollback is disabled because it is unsafe'),
4566 hint=('see `hg help -v rollback` for information'))
4568 hint=('see `hg help -v rollback` for information'))
4567 return repo.rollback(dryrun=opts.get(r'dry_run'),
4569 return repo.rollback(dryrun=opts.get(r'dry_run'),
4568 force=opts.get(r'force'))
4570 force=opts.get(r'force'))
4569
4571
4570 @command('root', [])
4572 @command('root', [])
4571 def root(ui, repo):
4573 def root(ui, repo):
4572 """print the root (top) of the current working directory
4574 """print the root (top) of the current working directory
4573
4575
4574 Print the root directory of the current repository.
4576 Print the root directory of the current repository.
4575
4577
4576 Returns 0 on success.
4578 Returns 0 on success.
4577 """
4579 """
4578 ui.write(repo.root + "\n")
4580 ui.write(repo.root + "\n")
4579
4581
4580 @command('^serve',
4582 @command('^serve',
4581 [('A', 'accesslog', '', _('name of access log file to write to'),
4583 [('A', 'accesslog', '', _('name of access log file to write to'),
4582 _('FILE')),
4584 _('FILE')),
4583 ('d', 'daemon', None, _('run server in background')),
4585 ('d', 'daemon', None, _('run server in background')),
4584 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
4586 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
4585 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4587 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4586 # use string type, then we can check if something was passed
4588 # use string type, then we can check if something was passed
4587 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4589 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4588 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4590 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4589 _('ADDR')),
4591 _('ADDR')),
4590 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4592 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4591 _('PREFIX')),
4593 _('PREFIX')),
4592 ('n', 'name', '',
4594 ('n', 'name', '',
4593 _('name to show in web pages (default: working directory)'), _('NAME')),
4595 _('name to show in web pages (default: working directory)'), _('NAME')),
4594 ('', 'web-conf', '',
4596 ('', 'web-conf', '',
4595 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
4597 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
4596 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4598 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4597 _('FILE')),
4599 _('FILE')),
4598 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4600 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4599 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
4601 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
4600 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
4602 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
4601 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4603 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4602 ('', 'style', '', _('template style to use'), _('STYLE')),
4604 ('', 'style', '', _('template style to use'), _('STYLE')),
4603 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4605 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4604 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))]
4606 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))]
4605 + subrepoopts,
4607 + subrepoopts,
4606 _('[OPTION]...'),
4608 _('[OPTION]...'),
4607 optionalrepo=True)
4609 optionalrepo=True)
4608 def serve(ui, repo, **opts):
4610 def serve(ui, repo, **opts):
4609 """start stand-alone webserver
4611 """start stand-alone webserver
4610
4612
4611 Start a local HTTP repository browser and pull server. You can use
4613 Start a local HTTP repository browser and pull server. You can use
4612 this for ad-hoc sharing and browsing of repositories. It is
4614 this for ad-hoc sharing and browsing of repositories. It is
4613 recommended to use a real web server to serve a repository for
4615 recommended to use a real web server to serve a repository for
4614 longer periods of time.
4616 longer periods of time.
4615
4617
4616 Please note that the server does not implement access control.
4618 Please note that the server does not implement access control.
4617 This means that, by default, anybody can read from the server and
4619 This means that, by default, anybody can read from the server and
4618 nobody can write to it by default. Set the ``web.allow_push``
4620 nobody can write to it by default. Set the ``web.allow_push``
4619 option to ``*`` to allow everybody to push to the server. You
4621 option to ``*`` to allow everybody to push to the server. You
4620 should use a real web server if you need to authenticate users.
4622 should use a real web server if you need to authenticate users.
4621
4623
4622 By default, the server logs accesses to stdout and errors to
4624 By default, the server logs accesses to stdout and errors to
4623 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4625 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4624 files.
4626 files.
4625
4627
4626 To have the server choose a free port number to listen on, specify
4628 To have the server choose a free port number to listen on, specify
4627 a port number of 0; in this case, the server will print the port
4629 a port number of 0; in this case, the server will print the port
4628 number it uses.
4630 number it uses.
4629
4631
4630 Returns 0 on success.
4632 Returns 0 on success.
4631 """
4633 """
4632
4634
4633 opts = pycompat.byteskwargs(opts)
4635 opts = pycompat.byteskwargs(opts)
4634 if opts["stdio"] and opts["cmdserver"]:
4636 if opts["stdio"] and opts["cmdserver"]:
4635 raise error.Abort(_("cannot use --stdio with --cmdserver"))
4637 raise error.Abort(_("cannot use --stdio with --cmdserver"))
4636
4638
4637 if opts["stdio"]:
4639 if opts["stdio"]:
4638 if repo is None:
4640 if repo is None:
4639 raise error.RepoError(_("there is no Mercurial repository here"
4641 raise error.RepoError(_("there is no Mercurial repository here"
4640 " (.hg not found)"))
4642 " (.hg not found)"))
4641 s = sshserver.sshserver(ui, repo)
4643 s = sshserver.sshserver(ui, repo)
4642 s.serve_forever()
4644 s.serve_forever()
4643
4645
4644 service = server.createservice(ui, repo, opts)
4646 service = server.createservice(ui, repo, opts)
4645 return server.runservice(opts, initfn=service.init, runfn=service.run)
4647 return server.runservice(opts, initfn=service.init, runfn=service.run)
4646
4648
4647 @command('^status|st',
4649 @command('^status|st',
4648 [('A', 'all', None, _('show status of all files')),
4650 [('A', 'all', None, _('show status of all files')),
4649 ('m', 'modified', None, _('show only modified files')),
4651 ('m', 'modified', None, _('show only modified files')),
4650 ('a', 'added', None, _('show only added files')),
4652 ('a', 'added', None, _('show only added files')),
4651 ('r', 'removed', None, _('show only removed files')),
4653 ('r', 'removed', None, _('show only removed files')),
4652 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4654 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4653 ('c', 'clean', None, _('show only files without changes')),
4655 ('c', 'clean', None, _('show only files without changes')),
4654 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4656 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4655 ('i', 'ignored', None, _('show only ignored files')),
4657 ('i', 'ignored', None, _('show only ignored files')),
4656 ('n', 'no-status', None, _('hide status prefix')),
4658 ('n', 'no-status', None, _('hide status prefix')),
4657 ('t', 'terse', '', _('show the terse output (EXPERIMENTAL)')),
4659 ('t', 'terse', '', _('show the terse output (EXPERIMENTAL)')),
4658 ('C', 'copies', None, _('show source of copied files')),
4660 ('C', 'copies', None, _('show source of copied files')),
4659 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4661 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4660 ('', 'rev', [], _('show difference from revision'), _('REV')),
4662 ('', 'rev', [], _('show difference from revision'), _('REV')),
4661 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
4663 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
4662 ] + walkopts + subrepoopts + formatteropts,
4664 ] + walkopts + subrepoopts + formatteropts,
4663 _('[OPTION]... [FILE]...'),
4665 _('[OPTION]... [FILE]...'),
4664 inferrepo=True)
4666 inferrepo=True)
4665 def status(ui, repo, *pats, **opts):
4667 def status(ui, repo, *pats, **opts):
4666 """show changed files in the working directory
4668 """show changed files in the working directory
4667
4669
4668 Show status of files in the repository. If names are given, only
4670 Show status of files in the repository. If names are given, only
4669 files that match are shown. Files that are clean or ignored or
4671 files that match are shown. Files that are clean or ignored or
4670 the source of a copy/move operation, are not listed unless
4672 the source of a copy/move operation, are not listed unless
4671 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
4673 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
4672 Unless options described with "show only ..." are given, the
4674 Unless options described with "show only ..." are given, the
4673 options -mardu are used.
4675 options -mardu are used.
4674
4676
4675 Option -q/--quiet hides untracked (unknown and ignored) files
4677 Option -q/--quiet hides untracked (unknown and ignored) files
4676 unless explicitly requested with -u/--unknown or -i/--ignored.
4678 unless explicitly requested with -u/--unknown or -i/--ignored.
4677
4679
4678 .. note::
4680 .. note::
4679
4681
4680 :hg:`status` may appear to disagree with diff if permissions have
4682 :hg:`status` may appear to disagree with diff if permissions have
4681 changed or a merge has occurred. The standard diff format does
4683 changed or a merge has occurred. The standard diff format does
4682 not report permission changes and diff only reports changes
4684 not report permission changes and diff only reports changes
4683 relative to one merge parent.
4685 relative to one merge parent.
4684
4686
4685 If one revision is given, it is used as the base revision.
4687 If one revision is given, it is used as the base revision.
4686 If two revisions are given, the differences between them are
4688 If two revisions are given, the differences between them are
4687 shown. The --change option can also be used as a shortcut to list
4689 shown. The --change option can also be used as a shortcut to list
4688 the changed files of a revision from its first parent.
4690 the changed files of a revision from its first parent.
4689
4691
4690 The codes used to show the status of files are::
4692 The codes used to show the status of files are::
4691
4693
4692 M = modified
4694 M = modified
4693 A = added
4695 A = added
4694 R = removed
4696 R = removed
4695 C = clean
4697 C = clean
4696 ! = missing (deleted by non-hg command, but still tracked)
4698 ! = missing (deleted by non-hg command, but still tracked)
4697 ? = not tracked
4699 ? = not tracked
4698 I = ignored
4700 I = ignored
4699 = origin of the previous file (with --copies)
4701 = origin of the previous file (with --copies)
4700
4702
4701 .. container:: verbose
4703 .. container:: verbose
4702
4704
4703 The -t/--terse option abbreviates the output by showing directory name
4705 The -t/--terse option abbreviates the output by showing directory name
4704 if all the files in it share the same status. The option expects a value
4706 if all the files in it share the same status. The option expects a value
4705 which can be a string formed by using 'm', 'a', 'r', 'd', 'u', 'i', 'c'
4707 which can be a string formed by using 'm', 'a', 'r', 'd', 'u', 'i', 'c'
4706 where, 'm' stands for 'modified', 'a' for 'added', 'r' for 'removed',
4708 where, 'm' stands for 'modified', 'a' for 'added', 'r' for 'removed',
4707 'd' for 'deleted', 'u' for 'unknown', 'i' for 'ignored' and 'c' for clean.
4709 'd' for 'deleted', 'u' for 'unknown', 'i' for 'ignored' and 'c' for clean.
4708
4710
4709 It terses the output of only those status which are passed. The ignored
4711 It terses the output of only those status which are passed. The ignored
4710 files are not considered while tersing until 'i' is there in --terse value
4712 files are not considered while tersing until 'i' is there in --terse value
4711 or the --ignored option is used.
4713 or the --ignored option is used.
4712
4714
4713 --verbose option shows more context about the state of the repo
4715 --verbose option shows more context about the state of the repo
4714 like the repository is in unfinised merge, shelve, rebase state etc.
4716 like the repository is in unfinised merge, shelve, rebase state etc.
4715 You can have this behaviour turned on by default by following config:
4717 You can have this behaviour turned on by default by following config:
4716
4718
4717 [commands]
4719 [commands]
4718 status.verbose = true
4720 status.verbose = true
4719
4721
4720 You can also skip some states like bisect by adding following in
4722 You can also skip some states like bisect by adding following in
4721 configuration file.
4723 configuration file.
4722
4724
4723 [commands]
4725 [commands]
4724 status.skipstates = bisect
4726 status.skipstates = bisect
4725
4727
4726 Examples:
4728 Examples:
4727
4729
4728 - show changes in the working directory relative to a
4730 - show changes in the working directory relative to a
4729 changeset::
4731 changeset::
4730
4732
4731 hg status --rev 9353
4733 hg status --rev 9353
4732
4734
4733 - show changes in the working directory relative to the
4735 - show changes in the working directory relative to the
4734 current directory (see :hg:`help patterns` for more information)::
4736 current directory (see :hg:`help patterns` for more information)::
4735
4737
4736 hg status re:
4738 hg status re:
4737
4739
4738 - show all changes including copies in an existing changeset::
4740 - show all changes including copies in an existing changeset::
4739
4741
4740 hg status --copies --change 9353
4742 hg status --copies --change 9353
4741
4743
4742 - get a NUL separated list of added files, suitable for xargs::
4744 - get a NUL separated list of added files, suitable for xargs::
4743
4745
4744 hg status -an0
4746 hg status -an0
4745
4747
4746 Returns 0 on success.
4748 Returns 0 on success.
4747 """
4749 """
4748
4750
4749 opts = pycompat.byteskwargs(opts)
4751 opts = pycompat.byteskwargs(opts)
4750 revs = opts.get('rev')
4752 revs = opts.get('rev')
4751 change = opts.get('change')
4753 change = opts.get('change')
4752 terse = opts.get('terse')
4754 terse = opts.get('terse')
4753
4755
4754 if revs and change:
4756 if revs and change:
4755 msg = _('cannot specify --rev and --change at the same time')
4757 msg = _('cannot specify --rev and --change at the same time')
4756 raise error.Abort(msg)
4758 raise error.Abort(msg)
4757 elif revs and terse:
4759 elif revs and terse:
4758 msg = _('cannot use --terse with --rev')
4760 msg = _('cannot use --terse with --rev')
4759 raise error.Abort(msg)
4761 raise error.Abort(msg)
4760 elif change:
4762 elif change:
4761 node2 = scmutil.revsingle(repo, change, None).node()
4763 node2 = scmutil.revsingle(repo, change, None).node()
4762 node1 = repo[node2].p1().node()
4764 node1 = repo[node2].p1().node()
4763 else:
4765 else:
4764 node1, node2 = scmutil.revpair(repo, revs)
4766 node1, node2 = scmutil.revpair(repo, revs)
4765
4767
4766 if pats or ui.configbool('commands', 'status.relative'):
4768 if pats or ui.configbool('commands', 'status.relative'):
4767 cwd = repo.getcwd()
4769 cwd = repo.getcwd()
4768 else:
4770 else:
4769 cwd = ''
4771 cwd = ''
4770
4772
4771 if opts.get('print0'):
4773 if opts.get('print0'):
4772 end = '\0'
4774 end = '\0'
4773 else:
4775 else:
4774 end = '\n'
4776 end = '\n'
4775 copy = {}
4777 copy = {}
4776 states = 'modified added removed deleted unknown ignored clean'.split()
4778 states = 'modified added removed deleted unknown ignored clean'.split()
4777 show = [k for k in states if opts.get(k)]
4779 show = [k for k in states if opts.get(k)]
4778 if opts.get('all'):
4780 if opts.get('all'):
4779 show += ui.quiet and (states[:4] + ['clean']) or states
4781 show += ui.quiet and (states[:4] + ['clean']) or states
4780
4782
4781 if not show:
4783 if not show:
4782 if ui.quiet:
4784 if ui.quiet:
4783 show = states[:4]
4785 show = states[:4]
4784 else:
4786 else:
4785 show = states[:5]
4787 show = states[:5]
4786
4788
4787 m = scmutil.match(repo[node2], pats, opts)
4789 m = scmutil.match(repo[node2], pats, opts)
4788 stat = repo.status(node1, node2, m,
4790 stat = repo.status(node1, node2, m,
4789 'ignored' in show, 'clean' in show, 'unknown' in show,
4791 'ignored' in show, 'clean' in show, 'unknown' in show,
4790 opts.get('subrepos'))
4792 opts.get('subrepos'))
4791 if terse:
4793 if terse:
4792 stat = cmdutil.tersestatus(repo.root, stat, terse,
4794 stat = cmdutil.tersestatus(repo.root, stat, terse,
4793 repo.dirstate._ignore, opts.get('ignored'))
4795 repo.dirstate._ignore, opts.get('ignored'))
4794 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
4796 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
4795
4797
4796 if (opts.get('all') or opts.get('copies')
4798 if (opts.get('all') or opts.get('copies')
4797 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
4799 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
4798 copy = copies.pathcopies(repo[node1], repo[node2], m)
4800 copy = copies.pathcopies(repo[node1], repo[node2], m)
4799
4801
4800 ui.pager('status')
4802 ui.pager('status')
4801 fm = ui.formatter('status', opts)
4803 fm = ui.formatter('status', opts)
4802 fmt = '%s' + end
4804 fmt = '%s' + end
4803 showchar = not opts.get('no_status')
4805 showchar = not opts.get('no_status')
4804
4806
4805 for state, char, files in changestates:
4807 for state, char, files in changestates:
4806 if state in show:
4808 if state in show:
4807 label = 'status.' + state
4809 label = 'status.' + state
4808 for f in files:
4810 for f in files:
4809 fm.startitem()
4811 fm.startitem()
4810 fm.condwrite(showchar, 'status', '%s ', char, label=label)
4812 fm.condwrite(showchar, 'status', '%s ', char, label=label)
4811 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
4813 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
4812 if f in copy:
4814 if f in copy:
4813 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
4815 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
4814 label='status.copied')
4816 label='status.copied')
4815
4817
4816 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
4818 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
4817 and not ui.plain()):
4819 and not ui.plain()):
4818 cmdutil.morestatus(repo, fm)
4820 cmdutil.morestatus(repo, fm)
4819 fm.end()
4821 fm.end()
4820
4822
4821 @command('^summary|sum',
4823 @command('^summary|sum',
4822 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
4824 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
4823 def summary(ui, repo, **opts):
4825 def summary(ui, repo, **opts):
4824 """summarize working directory state
4826 """summarize working directory state
4825
4827
4826 This generates a brief summary of the working directory state,
4828 This generates a brief summary of the working directory state,
4827 including parents, branch, commit status, phase and available updates.
4829 including parents, branch, commit status, phase and available updates.
4828
4830
4829 With the --remote option, this will check the default paths for
4831 With the --remote option, this will check the default paths for
4830 incoming and outgoing changes. This can be time-consuming.
4832 incoming and outgoing changes. This can be time-consuming.
4831
4833
4832 Returns 0 on success.
4834 Returns 0 on success.
4833 """
4835 """
4834
4836
4835 opts = pycompat.byteskwargs(opts)
4837 opts = pycompat.byteskwargs(opts)
4836 ui.pager('summary')
4838 ui.pager('summary')
4837 ctx = repo[None]
4839 ctx = repo[None]
4838 parents = ctx.parents()
4840 parents = ctx.parents()
4839 pnode = parents[0].node()
4841 pnode = parents[0].node()
4840 marks = []
4842 marks = []
4841
4843
4842 ms = None
4844 ms = None
4843 try:
4845 try:
4844 ms = mergemod.mergestate.read(repo)
4846 ms = mergemod.mergestate.read(repo)
4845 except error.UnsupportedMergeRecords as e:
4847 except error.UnsupportedMergeRecords as e:
4846 s = ' '.join(e.recordtypes)
4848 s = ' '.join(e.recordtypes)
4847 ui.warn(
4849 ui.warn(
4848 _('warning: merge state has unsupported record types: %s\n') % s)
4850 _('warning: merge state has unsupported record types: %s\n') % s)
4849 unresolved = []
4851 unresolved = []
4850 else:
4852 else:
4851 unresolved = list(ms.unresolved())
4853 unresolved = list(ms.unresolved())
4852
4854
4853 for p in parents:
4855 for p in parents:
4854 # label with log.changeset (instead of log.parent) since this
4856 # label with log.changeset (instead of log.parent) since this
4855 # shows a working directory parent *changeset*:
4857 # shows a working directory parent *changeset*:
4856 # i18n: column positioning for "hg summary"
4858 # i18n: column positioning for "hg summary"
4857 ui.write(_('parent: %d:%s ') % (p.rev(), p),
4859 ui.write(_('parent: %d:%s ') % (p.rev(), p),
4858 label=cmdutil._changesetlabels(p))
4860 label=cmdutil._changesetlabels(p))
4859 ui.write(' '.join(p.tags()), label='log.tag')
4861 ui.write(' '.join(p.tags()), label='log.tag')
4860 if p.bookmarks():
4862 if p.bookmarks():
4861 marks.extend(p.bookmarks())
4863 marks.extend(p.bookmarks())
4862 if p.rev() == -1:
4864 if p.rev() == -1:
4863 if not len(repo):
4865 if not len(repo):
4864 ui.write(_(' (empty repository)'))
4866 ui.write(_(' (empty repository)'))
4865 else:
4867 else:
4866 ui.write(_(' (no revision checked out)'))
4868 ui.write(_(' (no revision checked out)'))
4867 if p.obsolete():
4869 if p.obsolete():
4868 ui.write(_(' (obsolete)'))
4870 ui.write(_(' (obsolete)'))
4869 if p.isunstable():
4871 if p.isunstable():
4870 instabilities = (ui.label(instability, 'trouble.%s' % instability)
4872 instabilities = (ui.label(instability, 'trouble.%s' % instability)
4871 for instability in p.instabilities())
4873 for instability in p.instabilities())
4872 ui.write(' ('
4874 ui.write(' ('
4873 + ', '.join(instabilities)
4875 + ', '.join(instabilities)
4874 + ')')
4876 + ')')
4875 ui.write('\n')
4877 ui.write('\n')
4876 if p.description():
4878 if p.description():
4877 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
4879 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
4878 label='log.summary')
4880 label='log.summary')
4879
4881
4880 branch = ctx.branch()
4882 branch = ctx.branch()
4881 bheads = repo.branchheads(branch)
4883 bheads = repo.branchheads(branch)
4882 # i18n: column positioning for "hg summary"
4884 # i18n: column positioning for "hg summary"
4883 m = _('branch: %s\n') % branch
4885 m = _('branch: %s\n') % branch
4884 if branch != 'default':
4886 if branch != 'default':
4885 ui.write(m, label='log.branch')
4887 ui.write(m, label='log.branch')
4886 else:
4888 else:
4887 ui.status(m, label='log.branch')
4889 ui.status(m, label='log.branch')
4888
4890
4889 if marks:
4891 if marks:
4890 active = repo._activebookmark
4892 active = repo._activebookmark
4891 # i18n: column positioning for "hg summary"
4893 # i18n: column positioning for "hg summary"
4892 ui.write(_('bookmarks:'), label='log.bookmark')
4894 ui.write(_('bookmarks:'), label='log.bookmark')
4893 if active is not None:
4895 if active is not None:
4894 if active in marks:
4896 if active in marks:
4895 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
4897 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
4896 marks.remove(active)
4898 marks.remove(active)
4897 else:
4899 else:
4898 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
4900 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
4899 for m in marks:
4901 for m in marks:
4900 ui.write(' ' + m, label='log.bookmark')
4902 ui.write(' ' + m, label='log.bookmark')
4901 ui.write('\n', label='log.bookmark')
4903 ui.write('\n', label='log.bookmark')
4902
4904
4903 status = repo.status(unknown=True)
4905 status = repo.status(unknown=True)
4904
4906
4905 c = repo.dirstate.copies()
4907 c = repo.dirstate.copies()
4906 copied, renamed = [], []
4908 copied, renamed = [], []
4907 for d, s in c.iteritems():
4909 for d, s in c.iteritems():
4908 if s in status.removed:
4910 if s in status.removed:
4909 status.removed.remove(s)
4911 status.removed.remove(s)
4910 renamed.append(d)
4912 renamed.append(d)
4911 else:
4913 else:
4912 copied.append(d)
4914 copied.append(d)
4913 if d in status.added:
4915 if d in status.added:
4914 status.added.remove(d)
4916 status.added.remove(d)
4915
4917
4916 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
4918 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
4917
4919
4918 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
4920 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
4919 (ui.label(_('%d added'), 'status.added'), status.added),
4921 (ui.label(_('%d added'), 'status.added'), status.added),
4920 (ui.label(_('%d removed'), 'status.removed'), status.removed),
4922 (ui.label(_('%d removed'), 'status.removed'), status.removed),
4921 (ui.label(_('%d renamed'), 'status.copied'), renamed),
4923 (ui.label(_('%d renamed'), 'status.copied'), renamed),
4922 (ui.label(_('%d copied'), 'status.copied'), copied),
4924 (ui.label(_('%d copied'), 'status.copied'), copied),
4923 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
4925 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
4924 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
4926 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
4925 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
4927 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
4926 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
4928 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
4927 t = []
4929 t = []
4928 for l, s in labels:
4930 for l, s in labels:
4929 if s:
4931 if s:
4930 t.append(l % len(s))
4932 t.append(l % len(s))
4931
4933
4932 t = ', '.join(t)
4934 t = ', '.join(t)
4933 cleanworkdir = False
4935 cleanworkdir = False
4934
4936
4935 if repo.vfs.exists('graftstate'):
4937 if repo.vfs.exists('graftstate'):
4936 t += _(' (graft in progress)')
4938 t += _(' (graft in progress)')
4937 if repo.vfs.exists('updatestate'):
4939 if repo.vfs.exists('updatestate'):
4938 t += _(' (interrupted update)')
4940 t += _(' (interrupted update)')
4939 elif len(parents) > 1:
4941 elif len(parents) > 1:
4940 t += _(' (merge)')
4942 t += _(' (merge)')
4941 elif branch != parents[0].branch():
4943 elif branch != parents[0].branch():
4942 t += _(' (new branch)')
4944 t += _(' (new branch)')
4943 elif (parents[0].closesbranch() and
4945 elif (parents[0].closesbranch() and
4944 pnode in repo.branchheads(branch, closed=True)):
4946 pnode in repo.branchheads(branch, closed=True)):
4945 t += _(' (head closed)')
4947 t += _(' (head closed)')
4946 elif not (status.modified or status.added or status.removed or renamed or
4948 elif not (status.modified or status.added or status.removed or renamed or
4947 copied or subs):
4949 copied or subs):
4948 t += _(' (clean)')
4950 t += _(' (clean)')
4949 cleanworkdir = True
4951 cleanworkdir = True
4950 elif pnode not in bheads:
4952 elif pnode not in bheads:
4951 t += _(' (new branch head)')
4953 t += _(' (new branch head)')
4952
4954
4953 if parents:
4955 if parents:
4954 pendingphase = max(p.phase() for p in parents)
4956 pendingphase = max(p.phase() for p in parents)
4955 else:
4957 else:
4956 pendingphase = phases.public
4958 pendingphase = phases.public
4957
4959
4958 if pendingphase > phases.newcommitphase(ui):
4960 if pendingphase > phases.newcommitphase(ui):
4959 t += ' (%s)' % phases.phasenames[pendingphase]
4961 t += ' (%s)' % phases.phasenames[pendingphase]
4960
4962
4961 if cleanworkdir:
4963 if cleanworkdir:
4962 # i18n: column positioning for "hg summary"
4964 # i18n: column positioning for "hg summary"
4963 ui.status(_('commit: %s\n') % t.strip())
4965 ui.status(_('commit: %s\n') % t.strip())
4964 else:
4966 else:
4965 # i18n: column positioning for "hg summary"
4967 # i18n: column positioning for "hg summary"
4966 ui.write(_('commit: %s\n') % t.strip())
4968 ui.write(_('commit: %s\n') % t.strip())
4967
4969
4968 # all ancestors of branch heads - all ancestors of parent = new csets
4970 # all ancestors of branch heads - all ancestors of parent = new csets
4969 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
4971 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
4970 bheads))
4972 bheads))
4971
4973
4972 if new == 0:
4974 if new == 0:
4973 # i18n: column positioning for "hg summary"
4975 # i18n: column positioning for "hg summary"
4974 ui.status(_('update: (current)\n'))
4976 ui.status(_('update: (current)\n'))
4975 elif pnode not in bheads:
4977 elif pnode not in bheads:
4976 # i18n: column positioning for "hg summary"
4978 # i18n: column positioning for "hg summary"
4977 ui.write(_('update: %d new changesets (update)\n') % new)
4979 ui.write(_('update: %d new changesets (update)\n') % new)
4978 else:
4980 else:
4979 # i18n: column positioning for "hg summary"
4981 # i18n: column positioning for "hg summary"
4980 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
4982 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
4981 (new, len(bheads)))
4983 (new, len(bheads)))
4982
4984
4983 t = []
4985 t = []
4984 draft = len(repo.revs('draft()'))
4986 draft = len(repo.revs('draft()'))
4985 if draft:
4987 if draft:
4986 t.append(_('%d draft') % draft)
4988 t.append(_('%d draft') % draft)
4987 secret = len(repo.revs('secret()'))
4989 secret = len(repo.revs('secret()'))
4988 if secret:
4990 if secret:
4989 t.append(_('%d secret') % secret)
4991 t.append(_('%d secret') % secret)
4990
4992
4991 if draft or secret:
4993 if draft or secret:
4992 ui.status(_('phases: %s\n') % ', '.join(t))
4994 ui.status(_('phases: %s\n') % ', '.join(t))
4993
4995
4994 if obsolete.isenabled(repo, obsolete.createmarkersopt):
4996 if obsolete.isenabled(repo, obsolete.createmarkersopt):
4995 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
4997 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
4996 numtrouble = len(repo.revs(trouble + "()"))
4998 numtrouble = len(repo.revs(trouble + "()"))
4997 # We write all the possibilities to ease translation
4999 # We write all the possibilities to ease translation
4998 troublemsg = {
5000 troublemsg = {
4999 "orphan": _("orphan: %d changesets"),
5001 "orphan": _("orphan: %d changesets"),
5000 "contentdivergent": _("content-divergent: %d changesets"),
5002 "contentdivergent": _("content-divergent: %d changesets"),
5001 "phasedivergent": _("phase-divergent: %d changesets"),
5003 "phasedivergent": _("phase-divergent: %d changesets"),
5002 }
5004 }
5003 if numtrouble > 0:
5005 if numtrouble > 0:
5004 ui.status(troublemsg[trouble] % numtrouble + "\n")
5006 ui.status(troublemsg[trouble] % numtrouble + "\n")
5005
5007
5006 cmdutil.summaryhooks(ui, repo)
5008 cmdutil.summaryhooks(ui, repo)
5007
5009
5008 if opts.get('remote'):
5010 if opts.get('remote'):
5009 needsincoming, needsoutgoing = True, True
5011 needsincoming, needsoutgoing = True, True
5010 else:
5012 else:
5011 needsincoming, needsoutgoing = False, False
5013 needsincoming, needsoutgoing = False, False
5012 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5014 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5013 if i:
5015 if i:
5014 needsincoming = True
5016 needsincoming = True
5015 if o:
5017 if o:
5016 needsoutgoing = True
5018 needsoutgoing = True
5017 if not needsincoming and not needsoutgoing:
5019 if not needsincoming and not needsoutgoing:
5018 return
5020 return
5019
5021
5020 def getincoming():
5022 def getincoming():
5021 source, branches = hg.parseurl(ui.expandpath('default'))
5023 source, branches = hg.parseurl(ui.expandpath('default'))
5022 sbranch = branches[0]
5024 sbranch = branches[0]
5023 try:
5025 try:
5024 other = hg.peer(repo, {}, source)
5026 other = hg.peer(repo, {}, source)
5025 except error.RepoError:
5027 except error.RepoError:
5026 if opts.get('remote'):
5028 if opts.get('remote'):
5027 raise
5029 raise
5028 return source, sbranch, None, None, None
5030 return source, sbranch, None, None, None
5029 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5031 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5030 if revs:
5032 if revs:
5031 revs = [other.lookup(rev) for rev in revs]
5033 revs = [other.lookup(rev) for rev in revs]
5032 ui.debug('comparing with %s\n' % util.hidepassword(source))
5034 ui.debug('comparing with %s\n' % util.hidepassword(source))
5033 repo.ui.pushbuffer()
5035 repo.ui.pushbuffer()
5034 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5036 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5035 repo.ui.popbuffer()
5037 repo.ui.popbuffer()
5036 return source, sbranch, other, commoninc, commoninc[1]
5038 return source, sbranch, other, commoninc, commoninc[1]
5037
5039
5038 if needsincoming:
5040 if needsincoming:
5039 source, sbranch, sother, commoninc, incoming = getincoming()
5041 source, sbranch, sother, commoninc, incoming = getincoming()
5040 else:
5042 else:
5041 source = sbranch = sother = commoninc = incoming = None
5043 source = sbranch = sother = commoninc = incoming = None
5042
5044
5043 def getoutgoing():
5045 def getoutgoing():
5044 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5046 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5045 dbranch = branches[0]
5047 dbranch = branches[0]
5046 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5048 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5047 if source != dest:
5049 if source != dest:
5048 try:
5050 try:
5049 dother = hg.peer(repo, {}, dest)
5051 dother = hg.peer(repo, {}, dest)
5050 except error.RepoError:
5052 except error.RepoError:
5051 if opts.get('remote'):
5053 if opts.get('remote'):
5052 raise
5054 raise
5053 return dest, dbranch, None, None
5055 return dest, dbranch, None, None
5054 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5056 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5055 elif sother is None:
5057 elif sother is None:
5056 # there is no explicit destination peer, but source one is invalid
5058 # there is no explicit destination peer, but source one is invalid
5057 return dest, dbranch, None, None
5059 return dest, dbranch, None, None
5058 else:
5060 else:
5059 dother = sother
5061 dother = sother
5060 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5062 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5061 common = None
5063 common = None
5062 else:
5064 else:
5063 common = commoninc
5065 common = commoninc
5064 if revs:
5066 if revs:
5065 revs = [repo.lookup(rev) for rev in revs]
5067 revs = [repo.lookup(rev) for rev in revs]
5066 repo.ui.pushbuffer()
5068 repo.ui.pushbuffer()
5067 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5069 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5068 commoninc=common)
5070 commoninc=common)
5069 repo.ui.popbuffer()
5071 repo.ui.popbuffer()
5070 return dest, dbranch, dother, outgoing
5072 return dest, dbranch, dother, outgoing
5071
5073
5072 if needsoutgoing:
5074 if needsoutgoing:
5073 dest, dbranch, dother, outgoing = getoutgoing()
5075 dest, dbranch, dother, outgoing = getoutgoing()
5074 else:
5076 else:
5075 dest = dbranch = dother = outgoing = None
5077 dest = dbranch = dother = outgoing = None
5076
5078
5077 if opts.get('remote'):
5079 if opts.get('remote'):
5078 t = []
5080 t = []
5079 if incoming:
5081 if incoming:
5080 t.append(_('1 or more incoming'))
5082 t.append(_('1 or more incoming'))
5081 o = outgoing.missing
5083 o = outgoing.missing
5082 if o:
5084 if o:
5083 t.append(_('%d outgoing') % len(o))
5085 t.append(_('%d outgoing') % len(o))
5084 other = dother or sother
5086 other = dother or sother
5085 if 'bookmarks' in other.listkeys('namespaces'):
5087 if 'bookmarks' in other.listkeys('namespaces'):
5086 counts = bookmarks.summary(repo, other)
5088 counts = bookmarks.summary(repo, other)
5087 if counts[0] > 0:
5089 if counts[0] > 0:
5088 t.append(_('%d incoming bookmarks') % counts[0])
5090 t.append(_('%d incoming bookmarks') % counts[0])
5089 if counts[1] > 0:
5091 if counts[1] > 0:
5090 t.append(_('%d outgoing bookmarks') % counts[1])
5092 t.append(_('%d outgoing bookmarks') % counts[1])
5091
5093
5092 if t:
5094 if t:
5093 # i18n: column positioning for "hg summary"
5095 # i18n: column positioning for "hg summary"
5094 ui.write(_('remote: %s\n') % (', '.join(t)))
5096 ui.write(_('remote: %s\n') % (', '.join(t)))
5095 else:
5097 else:
5096 # i18n: column positioning for "hg summary"
5098 # i18n: column positioning for "hg summary"
5097 ui.status(_('remote: (synced)\n'))
5099 ui.status(_('remote: (synced)\n'))
5098
5100
5099 cmdutil.summaryremotehooks(ui, repo, opts,
5101 cmdutil.summaryremotehooks(ui, repo, opts,
5100 ((source, sbranch, sother, commoninc),
5102 ((source, sbranch, sother, commoninc),
5101 (dest, dbranch, dother, outgoing)))
5103 (dest, dbranch, dother, outgoing)))
5102
5104
5103 @command('tag',
5105 @command('tag',
5104 [('f', 'force', None, _('force tag')),
5106 [('f', 'force', None, _('force tag')),
5105 ('l', 'local', None, _('make the tag local')),
5107 ('l', 'local', None, _('make the tag local')),
5106 ('r', 'rev', '', _('revision to tag'), _('REV')),
5108 ('r', 'rev', '', _('revision to tag'), _('REV')),
5107 ('', 'remove', None, _('remove a tag')),
5109 ('', 'remove', None, _('remove a tag')),
5108 # -l/--local is already there, commitopts cannot be used
5110 # -l/--local is already there, commitopts cannot be used
5109 ('e', 'edit', None, _('invoke editor on commit messages')),
5111 ('e', 'edit', None, _('invoke editor on commit messages')),
5110 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5112 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5111 ] + commitopts2,
5113 ] + commitopts2,
5112 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5114 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5113 def tag(ui, repo, name1, *names, **opts):
5115 def tag(ui, repo, name1, *names, **opts):
5114 """add one or more tags for the current or given revision
5116 """add one or more tags for the current or given revision
5115
5117
5116 Name a particular revision using <name>.
5118 Name a particular revision using <name>.
5117
5119
5118 Tags are used to name particular revisions of the repository and are
5120 Tags are used to name particular revisions of the repository and are
5119 very useful to compare different revisions, to go back to significant
5121 very useful to compare different revisions, to go back to significant
5120 earlier versions or to mark branch points as releases, etc. Changing
5122 earlier versions or to mark branch points as releases, etc. Changing
5121 an existing tag is normally disallowed; use -f/--force to override.
5123 an existing tag is normally disallowed; use -f/--force to override.
5122
5124
5123 If no revision is given, the parent of the working directory is
5125 If no revision is given, the parent of the working directory is
5124 used.
5126 used.
5125
5127
5126 To facilitate version control, distribution, and merging of tags,
5128 To facilitate version control, distribution, and merging of tags,
5127 they are stored as a file named ".hgtags" which is managed similarly
5129 they are stored as a file named ".hgtags" which is managed similarly
5128 to other project files and can be hand-edited if necessary. This
5130 to other project files and can be hand-edited if necessary. This
5129 also means that tagging creates a new commit. The file
5131 also means that tagging creates a new commit. The file
5130 ".hg/localtags" is used for local tags (not shared among
5132 ".hg/localtags" is used for local tags (not shared among
5131 repositories).
5133 repositories).
5132
5134
5133 Tag commits are usually made at the head of a branch. If the parent
5135 Tag commits are usually made at the head of a branch. If the parent
5134 of the working directory is not a branch head, :hg:`tag` aborts; use
5136 of the working directory is not a branch head, :hg:`tag` aborts; use
5135 -f/--force to force the tag commit to be based on a non-head
5137 -f/--force to force the tag commit to be based on a non-head
5136 changeset.
5138 changeset.
5137
5139
5138 See :hg:`help dates` for a list of formats valid for -d/--date.
5140 See :hg:`help dates` for a list of formats valid for -d/--date.
5139
5141
5140 Since tag names have priority over branch names during revision
5142 Since tag names have priority over branch names during revision
5141 lookup, using an existing branch name as a tag name is discouraged.
5143 lookup, using an existing branch name as a tag name is discouraged.
5142
5144
5143 Returns 0 on success.
5145 Returns 0 on success.
5144 """
5146 """
5145 opts = pycompat.byteskwargs(opts)
5147 opts = pycompat.byteskwargs(opts)
5146 wlock = lock = None
5148 wlock = lock = None
5147 try:
5149 try:
5148 wlock = repo.wlock()
5150 wlock = repo.wlock()
5149 lock = repo.lock()
5151 lock = repo.lock()
5150 rev_ = "."
5152 rev_ = "."
5151 names = [t.strip() for t in (name1,) + names]
5153 names = [t.strip() for t in (name1,) + names]
5152 if len(names) != len(set(names)):
5154 if len(names) != len(set(names)):
5153 raise error.Abort(_('tag names must be unique'))
5155 raise error.Abort(_('tag names must be unique'))
5154 for n in names:
5156 for n in names:
5155 scmutil.checknewlabel(repo, n, 'tag')
5157 scmutil.checknewlabel(repo, n, 'tag')
5156 if not n:
5158 if not n:
5157 raise error.Abort(_('tag names cannot consist entirely of '
5159 raise error.Abort(_('tag names cannot consist entirely of '
5158 'whitespace'))
5160 'whitespace'))
5159 if opts.get('rev') and opts.get('remove'):
5161 if opts.get('rev') and opts.get('remove'):
5160 raise error.Abort(_("--rev and --remove are incompatible"))
5162 raise error.Abort(_("--rev and --remove are incompatible"))
5161 if opts.get('rev'):
5163 if opts.get('rev'):
5162 rev_ = opts['rev']
5164 rev_ = opts['rev']
5163 message = opts.get('message')
5165 message = opts.get('message')
5164 if opts.get('remove'):
5166 if opts.get('remove'):
5165 if opts.get('local'):
5167 if opts.get('local'):
5166 expectedtype = 'local'
5168 expectedtype = 'local'
5167 else:
5169 else:
5168 expectedtype = 'global'
5170 expectedtype = 'global'
5169
5171
5170 for n in names:
5172 for n in names:
5171 if not repo.tagtype(n):
5173 if not repo.tagtype(n):
5172 raise error.Abort(_("tag '%s' does not exist") % n)
5174 raise error.Abort(_("tag '%s' does not exist") % n)
5173 if repo.tagtype(n) != expectedtype:
5175 if repo.tagtype(n) != expectedtype:
5174 if expectedtype == 'global':
5176 if expectedtype == 'global':
5175 raise error.Abort(_("tag '%s' is not a global tag") % n)
5177 raise error.Abort(_("tag '%s' is not a global tag") % n)
5176 else:
5178 else:
5177 raise error.Abort(_("tag '%s' is not a local tag") % n)
5179 raise error.Abort(_("tag '%s' is not a local tag") % n)
5178 rev_ = 'null'
5180 rev_ = 'null'
5179 if not message:
5181 if not message:
5180 # we don't translate commit messages
5182 # we don't translate commit messages
5181 message = 'Removed tag %s' % ', '.join(names)
5183 message = 'Removed tag %s' % ', '.join(names)
5182 elif not opts.get('force'):
5184 elif not opts.get('force'):
5183 for n in names:
5185 for n in names:
5184 if n in repo.tags():
5186 if n in repo.tags():
5185 raise error.Abort(_("tag '%s' already exists "
5187 raise error.Abort(_("tag '%s' already exists "
5186 "(use -f to force)") % n)
5188 "(use -f to force)") % n)
5187 if not opts.get('local'):
5189 if not opts.get('local'):
5188 p1, p2 = repo.dirstate.parents()
5190 p1, p2 = repo.dirstate.parents()
5189 if p2 != nullid:
5191 if p2 != nullid:
5190 raise error.Abort(_('uncommitted merge'))
5192 raise error.Abort(_('uncommitted merge'))
5191 bheads = repo.branchheads()
5193 bheads = repo.branchheads()
5192 if not opts.get('force') and bheads and p1 not in bheads:
5194 if not opts.get('force') and bheads and p1 not in bheads:
5193 raise error.Abort(_('working directory is not at a branch head '
5195 raise error.Abort(_('working directory is not at a branch head '
5194 '(use -f to force)'))
5196 '(use -f to force)'))
5195 r = scmutil.revsingle(repo, rev_).node()
5197 r = scmutil.revsingle(repo, rev_).node()
5196
5198
5197 if not message:
5199 if not message:
5198 # we don't translate commit messages
5200 # we don't translate commit messages
5199 message = ('Added tag %s for changeset %s' %
5201 message = ('Added tag %s for changeset %s' %
5200 (', '.join(names), short(r)))
5202 (', '.join(names), short(r)))
5201
5203
5202 date = opts.get('date')
5204 date = opts.get('date')
5203 if date:
5205 if date:
5204 date = util.parsedate(date)
5206 date = util.parsedate(date)
5205
5207
5206 if opts.get('remove'):
5208 if opts.get('remove'):
5207 editform = 'tag.remove'
5209 editform = 'tag.remove'
5208 else:
5210 else:
5209 editform = 'tag.add'
5211 editform = 'tag.add'
5210 editor = cmdutil.getcommiteditor(editform=editform,
5212 editor = cmdutil.getcommiteditor(editform=editform,
5211 **pycompat.strkwargs(opts))
5213 **pycompat.strkwargs(opts))
5212
5214
5213 # don't allow tagging the null rev
5215 # don't allow tagging the null rev
5214 if (not opts.get('remove') and
5216 if (not opts.get('remove') and
5215 scmutil.revsingle(repo, rev_).rev() == nullrev):
5217 scmutil.revsingle(repo, rev_).rev() == nullrev):
5216 raise error.Abort(_("cannot tag null revision"))
5218 raise error.Abort(_("cannot tag null revision"))
5217
5219
5218 tagsmod.tag(repo, names, r, message, opts.get('local'),
5220 tagsmod.tag(repo, names, r, message, opts.get('local'),
5219 opts.get('user'), date, editor=editor)
5221 opts.get('user'), date, editor=editor)
5220 finally:
5222 finally:
5221 release(lock, wlock)
5223 release(lock, wlock)
5222
5224
5223 @command('tags', formatteropts, '')
5225 @command('tags', formatteropts, '')
5224 def tags(ui, repo, **opts):
5226 def tags(ui, repo, **opts):
5225 """list repository tags
5227 """list repository tags
5226
5228
5227 This lists both regular and local tags. When the -v/--verbose
5229 This lists both regular and local tags. When the -v/--verbose
5228 switch is used, a third column "local" is printed for local tags.
5230 switch is used, a third column "local" is printed for local tags.
5229 When the -q/--quiet switch is used, only the tag name is printed.
5231 When the -q/--quiet switch is used, only the tag name is printed.
5230
5232
5231 Returns 0 on success.
5233 Returns 0 on success.
5232 """
5234 """
5233
5235
5234 opts = pycompat.byteskwargs(opts)
5236 opts = pycompat.byteskwargs(opts)
5235 ui.pager('tags')
5237 ui.pager('tags')
5236 fm = ui.formatter('tags', opts)
5238 fm = ui.formatter('tags', opts)
5237 hexfunc = fm.hexfunc
5239 hexfunc = fm.hexfunc
5238 tagtype = ""
5240 tagtype = ""
5239
5241
5240 for t, n in reversed(repo.tagslist()):
5242 for t, n in reversed(repo.tagslist()):
5241 hn = hexfunc(n)
5243 hn = hexfunc(n)
5242 label = 'tags.normal'
5244 label = 'tags.normal'
5243 tagtype = ''
5245 tagtype = ''
5244 if repo.tagtype(t) == 'local':
5246 if repo.tagtype(t) == 'local':
5245 label = 'tags.local'
5247 label = 'tags.local'
5246 tagtype = 'local'
5248 tagtype = 'local'
5247
5249
5248 fm.startitem()
5250 fm.startitem()
5249 fm.write('tag', '%s', t, label=label)
5251 fm.write('tag', '%s', t, label=label)
5250 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5252 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5251 fm.condwrite(not ui.quiet, 'rev node', fmt,
5253 fm.condwrite(not ui.quiet, 'rev node', fmt,
5252 repo.changelog.rev(n), hn, label=label)
5254 repo.changelog.rev(n), hn, label=label)
5253 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5255 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5254 tagtype, label=label)
5256 tagtype, label=label)
5255 fm.plain('\n')
5257 fm.plain('\n')
5256 fm.end()
5258 fm.end()
5257
5259
5258 @command('tip',
5260 @command('tip',
5259 [('p', 'patch', None, _('show patch')),
5261 [('p', 'patch', None, _('show patch')),
5260 ('g', 'git', None, _('use git extended diff format')),
5262 ('g', 'git', None, _('use git extended diff format')),
5261 ] + templateopts,
5263 ] + templateopts,
5262 _('[-p] [-g]'))
5264 _('[-p] [-g]'))
5263 def tip(ui, repo, **opts):
5265 def tip(ui, repo, **opts):
5264 """show the tip revision (DEPRECATED)
5266 """show the tip revision (DEPRECATED)
5265
5267
5266 The tip revision (usually just called the tip) is the changeset
5268 The tip revision (usually just called the tip) is the changeset
5267 most recently added to the repository (and therefore the most
5269 most recently added to the repository (and therefore the most
5268 recently changed head).
5270 recently changed head).
5269
5271
5270 If you have just made a commit, that commit will be the tip. If
5272 If you have just made a commit, that commit will be the tip. If
5271 you have just pulled changes from another repository, the tip of
5273 you have just pulled changes from another repository, the tip of
5272 that repository becomes the current tip. The "tip" tag is special
5274 that repository becomes the current tip. The "tip" tag is special
5273 and cannot be renamed or assigned to a different changeset.
5275 and cannot be renamed or assigned to a different changeset.
5274
5276
5275 This command is deprecated, please use :hg:`heads` instead.
5277 This command is deprecated, please use :hg:`heads` instead.
5276
5278
5277 Returns 0 on success.
5279 Returns 0 on success.
5278 """
5280 """
5279 opts = pycompat.byteskwargs(opts)
5281 opts = pycompat.byteskwargs(opts)
5280 displayer = cmdutil.show_changeset(ui, repo, opts)
5282 displayer = cmdutil.show_changeset(ui, repo, opts)
5281 displayer.show(repo['tip'])
5283 displayer.show(repo['tip'])
5282 displayer.close()
5284 displayer.close()
5283
5285
5284 @command('unbundle',
5286 @command('unbundle',
5285 [('u', 'update', None,
5287 [('u', 'update', None,
5286 _('update to new branch head if changesets were unbundled'))],
5288 _('update to new branch head if changesets were unbundled'))],
5287 _('[-u] FILE...'))
5289 _('[-u] FILE...'))
5288 def unbundle(ui, repo, fname1, *fnames, **opts):
5290 def unbundle(ui, repo, fname1, *fnames, **opts):
5289 """apply one or more bundle files
5291 """apply one or more bundle files
5290
5292
5291 Apply one or more bundle files generated by :hg:`bundle`.
5293 Apply one or more bundle files generated by :hg:`bundle`.
5292
5294
5293 Returns 0 on success, 1 if an update has unresolved files.
5295 Returns 0 on success, 1 if an update has unresolved files.
5294 """
5296 """
5295 fnames = (fname1,) + fnames
5297 fnames = (fname1,) + fnames
5296
5298
5297 with repo.lock():
5299 with repo.lock():
5298 for fname in fnames:
5300 for fname in fnames:
5299 f = hg.openpath(ui, fname)
5301 f = hg.openpath(ui, fname)
5300 gen = exchange.readbundle(ui, f, fname)
5302 gen = exchange.readbundle(ui, f, fname)
5301 if isinstance(gen, streamclone.streamcloneapplier):
5303 if isinstance(gen, streamclone.streamcloneapplier):
5302 raise error.Abort(
5304 raise error.Abort(
5303 _('packed bundles cannot be applied with '
5305 _('packed bundles cannot be applied with '
5304 '"hg unbundle"'),
5306 '"hg unbundle"'),
5305 hint=_('use "hg debugapplystreamclonebundle"'))
5307 hint=_('use "hg debugapplystreamclonebundle"'))
5306 url = 'bundle:' + fname
5308 url = 'bundle:' + fname
5307 try:
5309 try:
5308 txnname = 'unbundle'
5310 txnname = 'unbundle'
5309 if not isinstance(gen, bundle2.unbundle20):
5311 if not isinstance(gen, bundle2.unbundle20):
5310 txnname = 'unbundle\n%s' % util.hidepassword(url)
5312 txnname = 'unbundle\n%s' % util.hidepassword(url)
5311 with repo.transaction(txnname) as tr:
5313 with repo.transaction(txnname) as tr:
5312 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
5314 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
5313 url=url)
5315 url=url)
5314 except error.BundleUnknownFeatureError as exc:
5316 except error.BundleUnknownFeatureError as exc:
5315 raise error.Abort(
5317 raise error.Abort(
5316 _('%s: unknown bundle feature, %s') % (fname, exc),
5318 _('%s: unknown bundle feature, %s') % (fname, exc),
5317 hint=_("see https://mercurial-scm.org/"
5319 hint=_("see https://mercurial-scm.org/"
5318 "wiki/BundleFeature for more "
5320 "wiki/BundleFeature for more "
5319 "information"))
5321 "information"))
5320 modheads = bundle2.combinechangegroupresults(op)
5322 modheads = bundle2.combinechangegroupresults(op)
5321
5323
5322 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
5324 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
5323
5325
5324 @command('^update|up|checkout|co',
5326 @command('^update|up|checkout|co',
5325 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5327 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5326 ('c', 'check', None, _('require clean working directory')),
5328 ('c', 'check', None, _('require clean working directory')),
5327 ('m', 'merge', None, _('merge uncommitted changes')),
5329 ('m', 'merge', None, _('merge uncommitted changes')),
5328 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5330 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5329 ('r', 'rev', '', _('revision'), _('REV'))
5331 ('r', 'rev', '', _('revision'), _('REV'))
5330 ] + mergetoolopts,
5332 ] + mergetoolopts,
5331 _('[-C|-c|-m] [-d DATE] [[-r] REV]'))
5333 _('[-C|-c|-m] [-d DATE] [[-r] REV]'))
5332 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
5334 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
5333 merge=None, tool=None):
5335 merge=None, tool=None):
5334 """update working directory (or switch revisions)
5336 """update working directory (or switch revisions)
5335
5337
5336 Update the repository's working directory to the specified
5338 Update the repository's working directory to the specified
5337 changeset. If no changeset is specified, update to the tip of the
5339 changeset. If no changeset is specified, update to the tip of the
5338 current named branch and move the active bookmark (see :hg:`help
5340 current named branch and move the active bookmark (see :hg:`help
5339 bookmarks`).
5341 bookmarks`).
5340
5342
5341 Update sets the working directory's parent revision to the specified
5343 Update sets the working directory's parent revision to the specified
5342 changeset (see :hg:`help parents`).
5344 changeset (see :hg:`help parents`).
5343
5345
5344 If the changeset is not a descendant or ancestor of the working
5346 If the changeset is not a descendant or ancestor of the working
5345 directory's parent and there are uncommitted changes, the update is
5347 directory's parent and there are uncommitted changes, the update is
5346 aborted. With the -c/--check option, the working directory is checked
5348 aborted. With the -c/--check option, the working directory is checked
5347 for uncommitted changes; if none are found, the working directory is
5349 for uncommitted changes; if none are found, the working directory is
5348 updated to the specified changeset.
5350 updated to the specified changeset.
5349
5351
5350 .. container:: verbose
5352 .. container:: verbose
5351
5353
5352 The -C/--clean, -c/--check, and -m/--merge options control what
5354 The -C/--clean, -c/--check, and -m/--merge options control what
5353 happens if the working directory contains uncommitted changes.
5355 happens if the working directory contains uncommitted changes.
5354 At most of one of them can be specified.
5356 At most of one of them can be specified.
5355
5357
5356 1. If no option is specified, and if
5358 1. If no option is specified, and if
5357 the requested changeset is an ancestor or descendant of
5359 the requested changeset is an ancestor or descendant of
5358 the working directory's parent, the uncommitted changes
5360 the working directory's parent, the uncommitted changes
5359 are merged into the requested changeset and the merged
5361 are merged into the requested changeset and the merged
5360 result is left uncommitted. If the requested changeset is
5362 result is left uncommitted. If the requested changeset is
5361 not an ancestor or descendant (that is, it is on another
5363 not an ancestor or descendant (that is, it is on another
5362 branch), the update is aborted and the uncommitted changes
5364 branch), the update is aborted and the uncommitted changes
5363 are preserved.
5365 are preserved.
5364
5366
5365 2. With the -m/--merge option, the update is allowed even if the
5367 2. With the -m/--merge option, the update is allowed even if the
5366 requested changeset is not an ancestor or descendant of
5368 requested changeset is not an ancestor or descendant of
5367 the working directory's parent.
5369 the working directory's parent.
5368
5370
5369 3. With the -c/--check option, the update is aborted and the
5371 3. With the -c/--check option, the update is aborted and the
5370 uncommitted changes are preserved.
5372 uncommitted changes are preserved.
5371
5373
5372 4. With the -C/--clean option, uncommitted changes are discarded and
5374 4. With the -C/--clean option, uncommitted changes are discarded and
5373 the working directory is updated to the requested changeset.
5375 the working directory is updated to the requested changeset.
5374
5376
5375 To cancel an uncommitted merge (and lose your changes), use
5377 To cancel an uncommitted merge (and lose your changes), use
5376 :hg:`update --clean .`.
5378 :hg:`update --clean .`.
5377
5379
5378 Use null as the changeset to remove the working directory (like
5380 Use null as the changeset to remove the working directory (like
5379 :hg:`clone -U`).
5381 :hg:`clone -U`).
5380
5382
5381 If you want to revert just one file to an older revision, use
5383 If you want to revert just one file to an older revision, use
5382 :hg:`revert [-r REV] NAME`.
5384 :hg:`revert [-r REV] NAME`.
5383
5385
5384 See :hg:`help dates` for a list of formats valid for -d/--date.
5386 See :hg:`help dates` for a list of formats valid for -d/--date.
5385
5387
5386 Returns 0 on success, 1 if there are unresolved files.
5388 Returns 0 on success, 1 if there are unresolved files.
5387 """
5389 """
5388 if rev and node:
5390 if rev and node:
5389 raise error.Abort(_("please specify just one revision"))
5391 raise error.Abort(_("please specify just one revision"))
5390
5392
5391 if ui.configbool('commands', 'update.requiredest'):
5393 if ui.configbool('commands', 'update.requiredest'):
5392 if not node and not rev and not date:
5394 if not node and not rev and not date:
5393 raise error.Abort(_('you must specify a destination'),
5395 raise error.Abort(_('you must specify a destination'),
5394 hint=_('for example: hg update ".::"'))
5396 hint=_('for example: hg update ".::"'))
5395
5397
5396 if rev is None or rev == '':
5398 if rev is None or rev == '':
5397 rev = node
5399 rev = node
5398
5400
5399 if date and rev is not None:
5401 if date and rev is not None:
5400 raise error.Abort(_("you can't specify a revision and a date"))
5402 raise error.Abort(_("you can't specify a revision and a date"))
5401
5403
5402 if len([x for x in (clean, check, merge) if x]) > 1:
5404 if len([x for x in (clean, check, merge) if x]) > 1:
5403 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
5405 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
5404 "or -m/merge"))
5406 "or -m/merge"))
5405
5407
5406 updatecheck = None
5408 updatecheck = None
5407 if check:
5409 if check:
5408 updatecheck = 'abort'
5410 updatecheck = 'abort'
5409 elif merge:
5411 elif merge:
5410 updatecheck = 'none'
5412 updatecheck = 'none'
5411
5413
5412 with repo.wlock():
5414 with repo.wlock():
5413 cmdutil.clearunfinished(repo)
5415 cmdutil.clearunfinished(repo)
5414
5416
5415 if date:
5417 if date:
5416 rev = cmdutil.finddate(ui, repo, date)
5418 rev = cmdutil.finddate(ui, repo, date)
5417
5419
5418 # if we defined a bookmark, we have to remember the original name
5420 # if we defined a bookmark, we have to remember the original name
5419 brev = rev
5421 brev = rev
5420 rev = scmutil.revsingle(repo, rev, rev).rev()
5422 rev = scmutil.revsingle(repo, rev, rev).rev()
5421
5423
5422 repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
5424 repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
5423
5425
5424 return hg.updatetotally(ui, repo, rev, brev, clean=clean,
5426 return hg.updatetotally(ui, repo, rev, brev, clean=clean,
5425 updatecheck=updatecheck)
5427 updatecheck=updatecheck)
5426
5428
5427 @command('verify', [])
5429 @command('verify', [])
5428 def verify(ui, repo):
5430 def verify(ui, repo):
5429 """verify the integrity of the repository
5431 """verify the integrity of the repository
5430
5432
5431 Verify the integrity of the current repository.
5433 Verify the integrity of the current repository.
5432
5434
5433 This will perform an extensive check of the repository's
5435 This will perform an extensive check of the repository's
5434 integrity, validating the hashes and checksums of each entry in
5436 integrity, validating the hashes and checksums of each entry in
5435 the changelog, manifest, and tracked files, as well as the
5437 the changelog, manifest, and tracked files, as well as the
5436 integrity of their crosslinks and indices.
5438 integrity of their crosslinks and indices.
5437
5439
5438 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
5440 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
5439 for more information about recovery from corruption of the
5441 for more information about recovery from corruption of the
5440 repository.
5442 repository.
5441
5443
5442 Returns 0 on success, 1 if errors are encountered.
5444 Returns 0 on success, 1 if errors are encountered.
5443 """
5445 """
5444 return hg.verify(repo)
5446 return hg.verify(repo)
5445
5447
5446 @command('version', [] + formatteropts, norepo=True)
5448 @command('version', [] + formatteropts, norepo=True)
5447 def version_(ui, **opts):
5449 def version_(ui, **opts):
5448 """output version and copyright information"""
5450 """output version and copyright information"""
5449 opts = pycompat.byteskwargs(opts)
5451 opts = pycompat.byteskwargs(opts)
5450 if ui.verbose:
5452 if ui.verbose:
5451 ui.pager('version')
5453 ui.pager('version')
5452 fm = ui.formatter("version", opts)
5454 fm = ui.formatter("version", opts)
5453 fm.startitem()
5455 fm.startitem()
5454 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
5456 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
5455 util.version())
5457 util.version())
5456 license = _(
5458 license = _(
5457 "(see https://mercurial-scm.org for more information)\n"
5459 "(see https://mercurial-scm.org for more information)\n"
5458 "\nCopyright (C) 2005-2017 Matt Mackall and others\n"
5460 "\nCopyright (C) 2005-2017 Matt Mackall and others\n"
5459 "This is free software; see the source for copying conditions. "
5461 "This is free software; see the source for copying conditions. "
5460 "There is NO\nwarranty; "
5462 "There is NO\nwarranty; "
5461 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5463 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5462 )
5464 )
5463 if not ui.quiet:
5465 if not ui.quiet:
5464 fm.plain(license)
5466 fm.plain(license)
5465
5467
5466 if ui.verbose:
5468 if ui.verbose:
5467 fm.plain(_("\nEnabled extensions:\n\n"))
5469 fm.plain(_("\nEnabled extensions:\n\n"))
5468 # format names and versions into columns
5470 # format names and versions into columns
5469 names = []
5471 names = []
5470 vers = []
5472 vers = []
5471 isinternals = []
5473 isinternals = []
5472 for name, module in extensions.extensions():
5474 for name, module in extensions.extensions():
5473 names.append(name)
5475 names.append(name)
5474 vers.append(extensions.moduleversion(module) or None)
5476 vers.append(extensions.moduleversion(module) or None)
5475 isinternals.append(extensions.ismoduleinternal(module))
5477 isinternals.append(extensions.ismoduleinternal(module))
5476 fn = fm.nested("extensions")
5478 fn = fm.nested("extensions")
5477 if names:
5479 if names:
5478 namefmt = " %%-%ds " % max(len(n) for n in names)
5480 namefmt = " %%-%ds " % max(len(n) for n in names)
5479 places = [_("external"), _("internal")]
5481 places = [_("external"), _("internal")]
5480 for n, v, p in zip(names, vers, isinternals):
5482 for n, v, p in zip(names, vers, isinternals):
5481 fn.startitem()
5483 fn.startitem()
5482 fn.condwrite(ui.verbose, "name", namefmt, n)
5484 fn.condwrite(ui.verbose, "name", namefmt, n)
5483 if ui.verbose:
5485 if ui.verbose:
5484 fn.plain("%s " % places[p])
5486 fn.plain("%s " % places[p])
5485 fn.data(bundled=p)
5487 fn.data(bundled=p)
5486 fn.condwrite(ui.verbose and v, "ver", "%s", v)
5488 fn.condwrite(ui.verbose and v, "ver", "%s", v)
5487 if ui.verbose:
5489 if ui.verbose:
5488 fn.plain("\n")
5490 fn.plain("\n")
5489 fn.end()
5491 fn.end()
5490 fm.end()
5492 fm.end()
5491
5493
5492 def loadcmdtable(ui, name, cmdtable):
5494 def loadcmdtable(ui, name, cmdtable):
5493 """Load command functions from specified cmdtable
5495 """Load command functions from specified cmdtable
5494 """
5496 """
5495 overrides = [cmd for cmd in cmdtable if cmd in table]
5497 overrides = [cmd for cmd in cmdtable if cmd in table]
5496 if overrides:
5498 if overrides:
5497 ui.warn(_("extension '%s' overrides commands: %s\n")
5499 ui.warn(_("extension '%s' overrides commands: %s\n")
5498 % (name, " ".join(overrides)))
5500 % (name, " ".join(overrides)))
5499 table.update(cmdtable)
5501 table.update(cmdtable)
@@ -1,797 +1,809 b''
1 # templatekw.py - common changeset template keywords
1 # templatekw.py - common changeset template keywords
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2009 Matt Mackall <mpm@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 from .i18n import _
10 from .i18n import _
11 from .node import (
11 from .node import (
12 hex,
12 hex,
13 nullid,
13 nullid,
14 short,
14 short,
15 )
15 )
16
16
17 from . import (
17 from . import (
18 encoding,
18 encoding,
19 error,
19 error,
20 hbisect,
20 hbisect,
21 obsutil,
21 obsutil,
22 patch,
22 patch,
23 pycompat,
23 pycompat,
24 registrar,
24 registrar,
25 scmutil,
25 scmutil,
26 util,
26 util,
27 )
27 )
28
28
29 class _hybrid(object):
29 class _hybrid(object):
30 """Wrapper for list or dict to support legacy template
30 """Wrapper for list or dict to support legacy template
31
31
32 This class allows us to handle both:
32 This class allows us to handle both:
33 - "{files}" (legacy command-line-specific list hack) and
33 - "{files}" (legacy command-line-specific list hack) and
34 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
34 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
35 and to access raw values:
35 and to access raw values:
36 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
36 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
37 - "{get(extras, key)}"
37 - "{get(extras, key)}"
38 - "{files|json}"
38 - "{files|json}"
39 """
39 """
40
40
41 def __init__(self, gen, values, makemap, joinfmt):
41 def __init__(self, gen, values, makemap, joinfmt):
42 if gen is not None:
42 if gen is not None:
43 self.gen = gen
43 self.gen = gen
44 self._values = values
44 self._values = values
45 self._makemap = makemap
45 self._makemap = makemap
46 self.joinfmt = joinfmt
46 self.joinfmt = joinfmt
47 @util.propertycache
47 @util.propertycache
48 def gen(self):
48 def gen(self):
49 return self._defaultgen()
49 return self._defaultgen()
50 def _defaultgen(self):
50 def _defaultgen(self):
51 """Generator to stringify this as {join(self, ' ')}"""
51 """Generator to stringify this as {join(self, ' ')}"""
52 for i, d in enumerate(self.itermaps()):
52 for i, d in enumerate(self.itermaps()):
53 if i > 0:
53 if i > 0:
54 yield ' '
54 yield ' '
55 yield self.joinfmt(d)
55 yield self.joinfmt(d)
56 def itermaps(self):
56 def itermaps(self):
57 makemap = self._makemap
57 makemap = self._makemap
58 for x in self._values:
58 for x in self._values:
59 yield makemap(x)
59 yield makemap(x)
60 def __contains__(self, x):
60 def __contains__(self, x):
61 return x in self._values
61 return x in self._values
62 def __getitem__(self, key):
62 def __getitem__(self, key):
63 return self._values[key]
63 return self._values[key]
64 def __len__(self):
64 def __len__(self):
65 return len(self._values)
65 return len(self._values)
66 def __iter__(self):
66 def __iter__(self):
67 return iter(self._values)
67 return iter(self._values)
68 def __getattr__(self, name):
68 def __getattr__(self, name):
69 if name not in ('get', 'items', 'iteritems', 'iterkeys', 'itervalues',
69 if name not in ('get', 'items', 'iteritems', 'iterkeys', 'itervalues',
70 'keys', 'values'):
70 'keys', 'values'):
71 raise AttributeError(name)
71 raise AttributeError(name)
72 return getattr(self._values, name)
72 return getattr(self._values, name)
73
73
74 def hybriddict(data, key='key', value='value', fmt='%s=%s', gen=None):
74 def hybriddict(data, key='key', value='value', fmt='%s=%s', gen=None):
75 """Wrap data to support both dict-like and string-like operations"""
75 """Wrap data to support both dict-like and string-like operations"""
76 return _hybrid(gen, data, lambda k: {key: k, value: data[k]},
76 return _hybrid(gen, data, lambda k: {key: k, value: data[k]},
77 lambda d: fmt % (d[key], d[value]))
77 lambda d: fmt % (d[key], d[value]))
78
78
79 def hybridlist(data, name, fmt='%s', gen=None):
79 def hybridlist(data, name, fmt='%s', gen=None):
80 """Wrap data to support both list-like and string-like operations"""
80 """Wrap data to support both list-like and string-like operations"""
81 return _hybrid(gen, data, lambda x: {name: x}, lambda d: fmt % d[name])
81 return _hybrid(gen, data, lambda x: {name: x}, lambda d: fmt % d[name])
82
82
83 def unwraphybrid(thing):
83 def unwraphybrid(thing):
84 """Return an object which can be stringified possibly by using a legacy
84 """Return an object which can be stringified possibly by using a legacy
85 template"""
85 template"""
86 if not util.safehasattr(thing, 'gen'):
86 if not util.safehasattr(thing, 'gen'):
87 return thing
87 return thing
88 return thing.gen
88 return thing.gen
89
89
90 def showdict(name, data, mapping, plural=None, key='key', value='value',
90 def showdict(name, data, mapping, plural=None, key='key', value='value',
91 fmt='%s=%s', separator=' '):
91 fmt='%s=%s', separator=' '):
92 c = [{key: k, value: v} for k, v in data.iteritems()]
92 c = [{key: k, value: v} for k, v in data.iteritems()]
93 f = _showlist(name, c, mapping, plural, separator)
93 f = _showlist(name, c, mapping, plural, separator)
94 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
94 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
95
95
96 def showlist(name, values, mapping, plural=None, element=None, separator=' '):
96 def showlist(name, values, mapping, plural=None, element=None, separator=' '):
97 if not element:
97 if not element:
98 element = name
98 element = name
99 f = _showlist(name, values, mapping, plural, separator)
99 f = _showlist(name, values, mapping, plural, separator)
100 return hybridlist(values, name=element, gen=f)
100 return hybridlist(values, name=element, gen=f)
101
101
102 def _showlist(name, values, mapping, plural=None, separator=' '):
102 def _showlist(name, values, mapping, plural=None, separator=' '):
103 '''expand set of values.
103 '''expand set of values.
104 name is name of key in template map.
104 name is name of key in template map.
105 values is list of strings or dicts.
105 values is list of strings or dicts.
106 plural is plural of name, if not simply name + 's'.
106 plural is plural of name, if not simply name + 's'.
107 separator is used to join values as a string
107 separator is used to join values as a string
108
108
109 expansion works like this, given name 'foo'.
109 expansion works like this, given name 'foo'.
110
110
111 if values is empty, expand 'no_foos'.
111 if values is empty, expand 'no_foos'.
112
112
113 if 'foo' not in template map, return values as a string,
113 if 'foo' not in template map, return values as a string,
114 joined by 'separator'.
114 joined by 'separator'.
115
115
116 expand 'start_foos'.
116 expand 'start_foos'.
117
117
118 for each value, expand 'foo'. if 'last_foo' in template
118 for each value, expand 'foo'. if 'last_foo' in template
119 map, expand it instead of 'foo' for last key.
119 map, expand it instead of 'foo' for last key.
120
120
121 expand 'end_foos'.
121 expand 'end_foos'.
122 '''
122 '''
123 templ = mapping['templ']
123 templ = mapping['templ']
124 strmapping = pycompat.strkwargs(mapping)
124 strmapping = pycompat.strkwargs(mapping)
125 if not plural:
125 if not plural:
126 plural = name + 's'
126 plural = name + 's'
127 if not values:
127 if not values:
128 noname = 'no_' + plural
128 noname = 'no_' + plural
129 if noname in templ:
129 if noname in templ:
130 yield templ(noname, **strmapping)
130 yield templ(noname, **strmapping)
131 return
131 return
132 if name not in templ:
132 if name not in templ:
133 if isinstance(values[0], bytes):
133 if isinstance(values[0], bytes):
134 yield separator.join(values)
134 yield separator.join(values)
135 else:
135 else:
136 for v in values:
136 for v in values:
137 yield dict(v, **strmapping)
137 yield dict(v, **strmapping)
138 return
138 return
139 startname = 'start_' + plural
139 startname = 'start_' + plural
140 if startname in templ:
140 if startname in templ:
141 yield templ(startname, **strmapping)
141 yield templ(startname, **strmapping)
142 vmapping = mapping.copy()
142 vmapping = mapping.copy()
143 def one(v, tag=name):
143 def one(v, tag=name):
144 try:
144 try:
145 vmapping.update(v)
145 vmapping.update(v)
146 except (AttributeError, ValueError):
146 except (AttributeError, ValueError):
147 try:
147 try:
148 for a, b in v:
148 for a, b in v:
149 vmapping[a] = b
149 vmapping[a] = b
150 except ValueError:
150 except ValueError:
151 vmapping[name] = v
151 vmapping[name] = v
152 return templ(tag, **pycompat.strkwargs(vmapping))
152 return templ(tag, **pycompat.strkwargs(vmapping))
153 lastname = 'last_' + name
153 lastname = 'last_' + name
154 if lastname in templ:
154 if lastname in templ:
155 last = values.pop()
155 last = values.pop()
156 else:
156 else:
157 last = None
157 last = None
158 for v in values:
158 for v in values:
159 yield one(v)
159 yield one(v)
160 if last is not None:
160 if last is not None:
161 yield one(last, tag=lastname)
161 yield one(last, tag=lastname)
162 endname = 'end_' + plural
162 endname = 'end_' + plural
163 if endname in templ:
163 if endname in templ:
164 yield templ(endname, **strmapping)
164 yield templ(endname, **strmapping)
165
165
166 def _formatrevnode(ctx):
166 def _formatrevnode(ctx):
167 """Format changeset as '{rev}:{node|formatnode}', which is the default
167 """Format changeset as '{rev}:{node|formatnode}', which is the default
168 template provided by cmdutil.changeset_templater"""
168 template provided by cmdutil.changeset_templater"""
169 repo = ctx.repo()
169 repo = ctx.repo()
170 if repo.ui.debugflag:
170 if repo.ui.debugflag:
171 hexfunc = hex
171 hexfunc = hex
172 else:
172 else:
173 hexfunc = short
173 hexfunc = short
174 return '%d:%s' % (scmutil.intrev(ctx), hexfunc(scmutil.binnode(ctx)))
174 return '%d:%s' % (scmutil.intrev(ctx), hexfunc(scmutil.binnode(ctx)))
175
175
176 def getfiles(repo, ctx, revcache):
176 def getfiles(repo, ctx, revcache):
177 if 'files' not in revcache:
177 if 'files' not in revcache:
178 revcache['files'] = repo.status(ctx.p1(), ctx)[:3]
178 revcache['files'] = repo.status(ctx.p1(), ctx)[:3]
179 return revcache['files']
179 return revcache['files']
180
180
181 def getlatesttags(repo, ctx, cache, pattern=None):
181 def getlatesttags(repo, ctx, cache, pattern=None):
182 '''return date, distance and name for the latest tag of rev'''
182 '''return date, distance and name for the latest tag of rev'''
183
183
184 cachename = 'latesttags'
184 cachename = 'latesttags'
185 if pattern is not None:
185 if pattern is not None:
186 cachename += '-' + pattern
186 cachename += '-' + pattern
187 match = util.stringmatcher(pattern)[2]
187 match = util.stringmatcher(pattern)[2]
188 else:
188 else:
189 match = util.always
189 match = util.always
190
190
191 if cachename not in cache:
191 if cachename not in cache:
192 # Cache mapping from rev to a tuple with tag date, tag
192 # Cache mapping from rev to a tuple with tag date, tag
193 # distance and tag name
193 # distance and tag name
194 cache[cachename] = {-1: (0, 0, ['null'])}
194 cache[cachename] = {-1: (0, 0, ['null'])}
195 latesttags = cache[cachename]
195 latesttags = cache[cachename]
196
196
197 rev = ctx.rev()
197 rev = ctx.rev()
198 todo = [rev]
198 todo = [rev]
199 while todo:
199 while todo:
200 rev = todo.pop()
200 rev = todo.pop()
201 if rev in latesttags:
201 if rev in latesttags:
202 continue
202 continue
203 ctx = repo[rev]
203 ctx = repo[rev]
204 tags = [t for t in ctx.tags()
204 tags = [t for t in ctx.tags()
205 if (repo.tagtype(t) and repo.tagtype(t) != 'local'
205 if (repo.tagtype(t) and repo.tagtype(t) != 'local'
206 and match(t))]
206 and match(t))]
207 if tags:
207 if tags:
208 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
208 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
209 continue
209 continue
210 try:
210 try:
211 # The tuples are laid out so the right one can be found by
211 ptags = [latesttags[p.rev()] for p in ctx.parents()]
212 # comparison.
212 if len(ptags) > 1:
213 pdate, pdist, ptag = max(
213 if ptags[0][2] == ptags[1][2]:
214 latesttags[p.rev()] for p in ctx.parents())
214 # The tuples are laid out so the right one can be found by
215 # comparison in this case.
216 pdate, pdist, ptag = max(ptags)
217 else:
218 def key(x):
219 changessincetag = len(repo.revs('only(%d, %s)',
220 ctx.rev(), x[2][0]))
221 # Smallest number of changes since tag wins. Date is
222 # used as tiebreaker.
223 return [-changessincetag, x[0]]
224 pdate, pdist, ptag = max(ptags, key=key)
225 else:
226 pdate, pdist, ptag = ptags[0]
215 except KeyError:
227 except KeyError:
216 # Cache miss - recurse
228 # Cache miss - recurse
217 todo.append(rev)
229 todo.append(rev)
218 todo.extend(p.rev() for p in ctx.parents())
230 todo.extend(p.rev() for p in ctx.parents())
219 continue
231 continue
220 latesttags[rev] = pdate, pdist + 1, ptag
232 latesttags[rev] = pdate, pdist + 1, ptag
221 return latesttags[rev]
233 return latesttags[rev]
222
234
223 def getrenamedfn(repo, endrev=None):
235 def getrenamedfn(repo, endrev=None):
224 rcache = {}
236 rcache = {}
225 if endrev is None:
237 if endrev is None:
226 endrev = len(repo)
238 endrev = len(repo)
227
239
228 def getrenamed(fn, rev):
240 def getrenamed(fn, rev):
229 '''looks up all renames for a file (up to endrev) the first
241 '''looks up all renames for a file (up to endrev) the first
230 time the file is given. It indexes on the changerev and only
242 time the file is given. It indexes on the changerev and only
231 parses the manifest if linkrev != changerev.
243 parses the manifest if linkrev != changerev.
232 Returns rename info for fn at changerev rev.'''
244 Returns rename info for fn at changerev rev.'''
233 if fn not in rcache:
245 if fn not in rcache:
234 rcache[fn] = {}
246 rcache[fn] = {}
235 fl = repo.file(fn)
247 fl = repo.file(fn)
236 for i in fl:
248 for i in fl:
237 lr = fl.linkrev(i)
249 lr = fl.linkrev(i)
238 renamed = fl.renamed(fl.node(i))
250 renamed = fl.renamed(fl.node(i))
239 rcache[fn][lr] = renamed
251 rcache[fn][lr] = renamed
240 if lr >= endrev:
252 if lr >= endrev:
241 break
253 break
242 if rev in rcache[fn]:
254 if rev in rcache[fn]:
243 return rcache[fn][rev]
255 return rcache[fn][rev]
244
256
245 # If linkrev != rev (i.e. rev not found in rcache) fallback to
257 # If linkrev != rev (i.e. rev not found in rcache) fallback to
246 # filectx logic.
258 # filectx logic.
247 try:
259 try:
248 return repo[rev][fn].renamed()
260 return repo[rev][fn].renamed()
249 except error.LookupError:
261 except error.LookupError:
250 return None
262 return None
251
263
252 return getrenamed
264 return getrenamed
253
265
254 # default templates internally used for rendering of lists
266 # default templates internally used for rendering of lists
255 defaulttempl = {
267 defaulttempl = {
256 'parent': '{rev}:{node|formatnode} ',
268 'parent': '{rev}:{node|formatnode} ',
257 'manifest': '{rev}:{node|formatnode}',
269 'manifest': '{rev}:{node|formatnode}',
258 'file_copy': '{name} ({source})',
270 'file_copy': '{name} ({source})',
259 'envvar': '{key}={value}',
271 'envvar': '{key}={value}',
260 'extra': '{key}={value|stringescape}'
272 'extra': '{key}={value|stringescape}'
261 }
273 }
262 # filecopy is preserved for compatibility reasons
274 # filecopy is preserved for compatibility reasons
263 defaulttempl['filecopy'] = defaulttempl['file_copy']
275 defaulttempl['filecopy'] = defaulttempl['file_copy']
264
276
265 # keywords are callables like:
277 # keywords are callables like:
266 # fn(repo, ctx, templ, cache, revcache, **args)
278 # fn(repo, ctx, templ, cache, revcache, **args)
267 # with:
279 # with:
268 # repo - current repository instance
280 # repo - current repository instance
269 # ctx - the changectx being displayed
281 # ctx - the changectx being displayed
270 # templ - the templater instance
282 # templ - the templater instance
271 # cache - a cache dictionary for the whole templater run
283 # cache - a cache dictionary for the whole templater run
272 # revcache - a cache dictionary for the current revision
284 # revcache - a cache dictionary for the current revision
273 keywords = {}
285 keywords = {}
274
286
275 templatekeyword = registrar.templatekeyword(keywords)
287 templatekeyword = registrar.templatekeyword(keywords)
276
288
277 @templatekeyword('author')
289 @templatekeyword('author')
278 def showauthor(repo, ctx, templ, **args):
290 def showauthor(repo, ctx, templ, **args):
279 """String. The unmodified author of the changeset."""
291 """String. The unmodified author of the changeset."""
280 return ctx.user()
292 return ctx.user()
281
293
282 @templatekeyword('bisect')
294 @templatekeyword('bisect')
283 def showbisect(repo, ctx, templ, **args):
295 def showbisect(repo, ctx, templ, **args):
284 """String. The changeset bisection status."""
296 """String. The changeset bisection status."""
285 return hbisect.label(repo, ctx.node())
297 return hbisect.label(repo, ctx.node())
286
298
287 @templatekeyword('branch')
299 @templatekeyword('branch')
288 def showbranch(**args):
300 def showbranch(**args):
289 """String. The name of the branch on which the changeset was
301 """String. The name of the branch on which the changeset was
290 committed.
302 committed.
291 """
303 """
292 return args[r'ctx'].branch()
304 return args[r'ctx'].branch()
293
305
294 @templatekeyword('branches')
306 @templatekeyword('branches')
295 def showbranches(**args):
307 def showbranches(**args):
296 """List of strings. The name of the branch on which the
308 """List of strings. The name of the branch on which the
297 changeset was committed. Will be empty if the branch name was
309 changeset was committed. Will be empty if the branch name was
298 default. (DEPRECATED)
310 default. (DEPRECATED)
299 """
311 """
300 args = pycompat.byteskwargs(args)
312 args = pycompat.byteskwargs(args)
301 branch = args['ctx'].branch()
313 branch = args['ctx'].branch()
302 if branch != 'default':
314 if branch != 'default':
303 return showlist('branch', [branch], args, plural='branches')
315 return showlist('branch', [branch], args, plural='branches')
304 return showlist('branch', [], args, plural='branches')
316 return showlist('branch', [], args, plural='branches')
305
317
306 @templatekeyword('bookmarks')
318 @templatekeyword('bookmarks')
307 def showbookmarks(**args):
319 def showbookmarks(**args):
308 """List of strings. Any bookmarks associated with the
320 """List of strings. Any bookmarks associated with the
309 changeset. Also sets 'active', the name of the active bookmark.
321 changeset. Also sets 'active', the name of the active bookmark.
310 """
322 """
311 args = pycompat.byteskwargs(args)
323 args = pycompat.byteskwargs(args)
312 repo = args['ctx']._repo
324 repo = args['ctx']._repo
313 bookmarks = args['ctx'].bookmarks()
325 bookmarks = args['ctx'].bookmarks()
314 active = repo._activebookmark
326 active = repo._activebookmark
315 makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
327 makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
316 f = _showlist('bookmark', bookmarks, args)
328 f = _showlist('bookmark', bookmarks, args)
317 return _hybrid(f, bookmarks, makemap, lambda x: x['bookmark'])
329 return _hybrid(f, bookmarks, makemap, lambda x: x['bookmark'])
318
330
319 @templatekeyword('children')
331 @templatekeyword('children')
320 def showchildren(**args):
332 def showchildren(**args):
321 """List of strings. The children of the changeset."""
333 """List of strings. The children of the changeset."""
322 args = pycompat.byteskwargs(args)
334 args = pycompat.byteskwargs(args)
323 ctx = args['ctx']
335 ctx = args['ctx']
324 childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()]
336 childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()]
325 return showlist('children', childrevs, args, element='child')
337 return showlist('children', childrevs, args, element='child')
326
338
327 # Deprecated, but kept alive for help generation a purpose.
339 # Deprecated, but kept alive for help generation a purpose.
328 @templatekeyword('currentbookmark')
340 @templatekeyword('currentbookmark')
329 def showcurrentbookmark(**args):
341 def showcurrentbookmark(**args):
330 """String. The active bookmark, if it is
342 """String. The active bookmark, if it is
331 associated with the changeset (DEPRECATED)"""
343 associated with the changeset (DEPRECATED)"""
332 return showactivebookmark(**args)
344 return showactivebookmark(**args)
333
345
334 @templatekeyword('activebookmark')
346 @templatekeyword('activebookmark')
335 def showactivebookmark(**args):
347 def showactivebookmark(**args):
336 """String. The active bookmark, if it is
348 """String. The active bookmark, if it is
337 associated with the changeset"""
349 associated with the changeset"""
338 active = args[r'repo']._activebookmark
350 active = args[r'repo']._activebookmark
339 if active and active in args[r'ctx'].bookmarks():
351 if active and active in args[r'ctx'].bookmarks():
340 return active
352 return active
341 return ''
353 return ''
342
354
343 @templatekeyword('date')
355 @templatekeyword('date')
344 def showdate(repo, ctx, templ, **args):
356 def showdate(repo, ctx, templ, **args):
345 """Date information. The date when the changeset was committed."""
357 """Date information. The date when the changeset was committed."""
346 return ctx.date()
358 return ctx.date()
347
359
348 @templatekeyword('desc')
360 @templatekeyword('desc')
349 def showdescription(repo, ctx, templ, **args):
361 def showdescription(repo, ctx, templ, **args):
350 """String. The text of the changeset description."""
362 """String. The text of the changeset description."""
351 s = ctx.description()
363 s = ctx.description()
352 if isinstance(s, encoding.localstr):
364 if isinstance(s, encoding.localstr):
353 # try hard to preserve utf-8 bytes
365 # try hard to preserve utf-8 bytes
354 return encoding.tolocal(encoding.fromlocal(s).strip())
366 return encoding.tolocal(encoding.fromlocal(s).strip())
355 else:
367 else:
356 return s.strip()
368 return s.strip()
357
369
358 @templatekeyword('diffstat')
370 @templatekeyword('diffstat')
359 def showdiffstat(repo, ctx, templ, **args):
371 def showdiffstat(repo, ctx, templ, **args):
360 """String. Statistics of changes with the following format:
372 """String. Statistics of changes with the following format:
361 "modified files: +added/-removed lines"
373 "modified files: +added/-removed lines"
362 """
374 """
363 stats = patch.diffstatdata(util.iterlines(ctx.diff(noprefix=False)))
375 stats = patch.diffstatdata(util.iterlines(ctx.diff(noprefix=False)))
364 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
376 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
365 return '%s: +%s/-%s' % (len(stats), adds, removes)
377 return '%s: +%s/-%s' % (len(stats), adds, removes)
366
378
367 @templatekeyword('envvars')
379 @templatekeyword('envvars')
368 def showenvvars(repo, **args):
380 def showenvvars(repo, **args):
369 """A dictionary of environment variables. (EXPERIMENTAL)"""
381 """A dictionary of environment variables. (EXPERIMENTAL)"""
370 args = pycompat.byteskwargs(args)
382 args = pycompat.byteskwargs(args)
371 env = repo.ui.exportableenviron()
383 env = repo.ui.exportableenviron()
372 env = util.sortdict((k, env[k]) for k in sorted(env))
384 env = util.sortdict((k, env[k]) for k in sorted(env))
373 return showdict('envvar', env, args, plural='envvars')
385 return showdict('envvar', env, args, plural='envvars')
374
386
375 @templatekeyword('extras')
387 @templatekeyword('extras')
376 def showextras(**args):
388 def showextras(**args):
377 """List of dicts with key, value entries of the 'extras'
389 """List of dicts with key, value entries of the 'extras'
378 field of this changeset."""
390 field of this changeset."""
379 args = pycompat.byteskwargs(args)
391 args = pycompat.byteskwargs(args)
380 extras = args['ctx'].extra()
392 extras = args['ctx'].extra()
381 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
393 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
382 makemap = lambda k: {'key': k, 'value': extras[k]}
394 makemap = lambda k: {'key': k, 'value': extras[k]}
383 c = [makemap(k) for k in extras]
395 c = [makemap(k) for k in extras]
384 f = _showlist('extra', c, args, plural='extras')
396 f = _showlist('extra', c, args, plural='extras')
385 return _hybrid(f, extras, makemap,
397 return _hybrid(f, extras, makemap,
386 lambda x: '%s=%s' % (x['key'], util.escapestr(x['value'])))
398 lambda x: '%s=%s' % (x['key'], util.escapestr(x['value'])))
387
399
388 @templatekeyword('file_adds')
400 @templatekeyword('file_adds')
389 def showfileadds(**args):
401 def showfileadds(**args):
390 """List of strings. Files added by this changeset."""
402 """List of strings. Files added by this changeset."""
391 args = pycompat.byteskwargs(args)
403 args = pycompat.byteskwargs(args)
392 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
404 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
393 return showlist('file_add', getfiles(repo, ctx, revcache)[1], args,
405 return showlist('file_add', getfiles(repo, ctx, revcache)[1], args,
394 element='file')
406 element='file')
395
407
396 @templatekeyword('file_copies')
408 @templatekeyword('file_copies')
397 def showfilecopies(**args):
409 def showfilecopies(**args):
398 """List of strings. Files copied in this changeset with
410 """List of strings. Files copied in this changeset with
399 their sources.
411 their sources.
400 """
412 """
401 args = pycompat.byteskwargs(args)
413 args = pycompat.byteskwargs(args)
402 cache, ctx = args['cache'], args['ctx']
414 cache, ctx = args['cache'], args['ctx']
403 copies = args['revcache'].get('copies')
415 copies = args['revcache'].get('copies')
404 if copies is None:
416 if copies is None:
405 if 'getrenamed' not in cache:
417 if 'getrenamed' not in cache:
406 cache['getrenamed'] = getrenamedfn(args['repo'])
418 cache['getrenamed'] = getrenamedfn(args['repo'])
407 copies = []
419 copies = []
408 getrenamed = cache['getrenamed']
420 getrenamed = cache['getrenamed']
409 for fn in ctx.files():
421 for fn in ctx.files():
410 rename = getrenamed(fn, ctx.rev())
422 rename = getrenamed(fn, ctx.rev())
411 if rename:
423 if rename:
412 copies.append((fn, rename[0]))
424 copies.append((fn, rename[0]))
413
425
414 copies = util.sortdict(copies)
426 copies = util.sortdict(copies)
415 return showdict('file_copy', copies, args, plural='file_copies',
427 return showdict('file_copy', copies, args, plural='file_copies',
416 key='name', value='source', fmt='%s (%s)')
428 key='name', value='source', fmt='%s (%s)')
417
429
418 # showfilecopiesswitch() displays file copies only if copy records are
430 # showfilecopiesswitch() displays file copies only if copy records are
419 # provided before calling the templater, usually with a --copies
431 # provided before calling the templater, usually with a --copies
420 # command line switch.
432 # command line switch.
421 @templatekeyword('file_copies_switch')
433 @templatekeyword('file_copies_switch')
422 def showfilecopiesswitch(**args):
434 def showfilecopiesswitch(**args):
423 """List of strings. Like "file_copies" but displayed
435 """List of strings. Like "file_copies" but displayed
424 only if the --copied switch is set.
436 only if the --copied switch is set.
425 """
437 """
426 args = pycompat.byteskwargs(args)
438 args = pycompat.byteskwargs(args)
427 copies = args['revcache'].get('copies') or []
439 copies = args['revcache'].get('copies') or []
428 copies = util.sortdict(copies)
440 copies = util.sortdict(copies)
429 return showdict('file_copy', copies, args, plural='file_copies',
441 return showdict('file_copy', copies, args, plural='file_copies',
430 key='name', value='source', fmt='%s (%s)')
442 key='name', value='source', fmt='%s (%s)')
431
443
432 @templatekeyword('file_dels')
444 @templatekeyword('file_dels')
433 def showfiledels(**args):
445 def showfiledels(**args):
434 """List of strings. Files removed by this changeset."""
446 """List of strings. Files removed by this changeset."""
435 args = pycompat.byteskwargs(args)
447 args = pycompat.byteskwargs(args)
436 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
448 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
437 return showlist('file_del', getfiles(repo, ctx, revcache)[2], args,
449 return showlist('file_del', getfiles(repo, ctx, revcache)[2], args,
438 element='file')
450 element='file')
439
451
440 @templatekeyword('file_mods')
452 @templatekeyword('file_mods')
441 def showfilemods(**args):
453 def showfilemods(**args):
442 """List of strings. Files modified by this changeset."""
454 """List of strings. Files modified by this changeset."""
443 args = pycompat.byteskwargs(args)
455 args = pycompat.byteskwargs(args)
444 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
456 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
445 return showlist('file_mod', getfiles(repo, ctx, revcache)[0], args,
457 return showlist('file_mod', getfiles(repo, ctx, revcache)[0], args,
446 element='file')
458 element='file')
447
459
448 @templatekeyword('files')
460 @templatekeyword('files')
449 def showfiles(**args):
461 def showfiles(**args):
450 """List of strings. All files modified, added, or removed by this
462 """List of strings. All files modified, added, or removed by this
451 changeset.
463 changeset.
452 """
464 """
453 args = pycompat.byteskwargs(args)
465 args = pycompat.byteskwargs(args)
454 return showlist('file', args['ctx'].files(), args)
466 return showlist('file', args['ctx'].files(), args)
455
467
456 @templatekeyword('graphnode')
468 @templatekeyword('graphnode')
457 def showgraphnode(repo, ctx, **args):
469 def showgraphnode(repo, ctx, **args):
458 """String. The character representing the changeset node in
470 """String. The character representing the changeset node in
459 an ASCII revision graph"""
471 an ASCII revision graph"""
460 wpnodes = repo.dirstate.parents()
472 wpnodes = repo.dirstate.parents()
461 if wpnodes[1] == nullid:
473 if wpnodes[1] == nullid:
462 wpnodes = wpnodes[:1]
474 wpnodes = wpnodes[:1]
463 if ctx.node() in wpnodes:
475 if ctx.node() in wpnodes:
464 return '@'
476 return '@'
465 elif ctx.obsolete():
477 elif ctx.obsolete():
466 return 'x'
478 return 'x'
467 elif ctx.closesbranch():
479 elif ctx.closesbranch():
468 return '_'
480 return '_'
469 else:
481 else:
470 return 'o'
482 return 'o'
471
483
472 @templatekeyword('graphwidth')
484 @templatekeyword('graphwidth')
473 def showgraphwidth(repo, ctx, templ, **args):
485 def showgraphwidth(repo, ctx, templ, **args):
474 """Integer. The width of the graph drawn by 'log --graph' or zero."""
486 """Integer. The width of the graph drawn by 'log --graph' or zero."""
475 # The value args['graphwidth'] will be this function, so we use an internal
487 # The value args['graphwidth'] will be this function, so we use an internal
476 # name to pass the value through props into this function.
488 # name to pass the value through props into this function.
477 return args.get('_graphwidth', 0)
489 return args.get('_graphwidth', 0)
478
490
479 @templatekeyword('index')
491 @templatekeyword('index')
480 def showindex(**args):
492 def showindex(**args):
481 """Integer. The current iteration of the loop. (0 indexed)"""
493 """Integer. The current iteration of the loop. (0 indexed)"""
482 # just hosts documentation; should be overridden by template mapping
494 # just hosts documentation; should be overridden by template mapping
483 raise error.Abort(_("can't use index in this context"))
495 raise error.Abort(_("can't use index in this context"))
484
496
485 @templatekeyword('latesttag')
497 @templatekeyword('latesttag')
486 def showlatesttag(**args):
498 def showlatesttag(**args):
487 """List of strings. The global tags on the most recent globally
499 """List of strings. The global tags on the most recent globally
488 tagged ancestor of this changeset. If no such tags exist, the list
500 tagged ancestor of this changeset. If no such tags exist, the list
489 consists of the single string "null".
501 consists of the single string "null".
490 """
502 """
491 return showlatesttags(None, **args)
503 return showlatesttags(None, **args)
492
504
493 def showlatesttags(pattern, **args):
505 def showlatesttags(pattern, **args):
494 """helper method for the latesttag keyword and function"""
506 """helper method for the latesttag keyword and function"""
495 args = pycompat.byteskwargs(args)
507 args = pycompat.byteskwargs(args)
496 repo, ctx = args['repo'], args['ctx']
508 repo, ctx = args['repo'], args['ctx']
497 cache = args['cache']
509 cache = args['cache']
498 latesttags = getlatesttags(repo, ctx, cache, pattern)
510 latesttags = getlatesttags(repo, ctx, cache, pattern)
499
511
500 # latesttag[0] is an implementation detail for sorting csets on different
512 # latesttag[0] is an implementation detail for sorting csets on different
501 # branches in a stable manner- it is the date the tagged cset was created,
513 # branches in a stable manner- it is the date the tagged cset was created,
502 # not the date the tag was created. Therefore it isn't made visible here.
514 # not the date the tag was created. Therefore it isn't made visible here.
503 makemap = lambda v: {
515 makemap = lambda v: {
504 'changes': _showchangessincetag,
516 'changes': _showchangessincetag,
505 'distance': latesttags[1],
517 'distance': latesttags[1],
506 'latesttag': v, # BC with {latesttag % '{latesttag}'}
518 'latesttag': v, # BC with {latesttag % '{latesttag}'}
507 'tag': v
519 'tag': v
508 }
520 }
509
521
510 tags = latesttags[2]
522 tags = latesttags[2]
511 f = _showlist('latesttag', tags, args, separator=':')
523 f = _showlist('latesttag', tags, args, separator=':')
512 return _hybrid(f, tags, makemap, lambda x: x['latesttag'])
524 return _hybrid(f, tags, makemap, lambda x: x['latesttag'])
513
525
514 @templatekeyword('latesttagdistance')
526 @templatekeyword('latesttagdistance')
515 def showlatesttagdistance(repo, ctx, templ, cache, **args):
527 def showlatesttagdistance(repo, ctx, templ, cache, **args):
516 """Integer. Longest path to the latest tag."""
528 """Integer. Longest path to the latest tag."""
517 return getlatesttags(repo, ctx, cache)[1]
529 return getlatesttags(repo, ctx, cache)[1]
518
530
519 @templatekeyword('changessincelatesttag')
531 @templatekeyword('changessincelatesttag')
520 def showchangessincelatesttag(repo, ctx, templ, cache, **args):
532 def showchangessincelatesttag(repo, ctx, templ, cache, **args):
521 """Integer. All ancestors not in the latest tag."""
533 """Integer. All ancestors not in the latest tag."""
522 latesttag = getlatesttags(repo, ctx, cache)[2][0]
534 latesttag = getlatesttags(repo, ctx, cache)[2][0]
523
535
524 return _showchangessincetag(repo, ctx, tag=latesttag, **args)
536 return _showchangessincetag(repo, ctx, tag=latesttag, **args)
525
537
526 def _showchangessincetag(repo, ctx, **args):
538 def _showchangessincetag(repo, ctx, **args):
527 offset = 0
539 offset = 0
528 revs = [ctx.rev()]
540 revs = [ctx.rev()]
529 tag = args[r'tag']
541 tag = args[r'tag']
530
542
531 # The only() revset doesn't currently support wdir()
543 # The only() revset doesn't currently support wdir()
532 if ctx.rev() is None:
544 if ctx.rev() is None:
533 offset = 1
545 offset = 1
534 revs = [p.rev() for p in ctx.parents()]
546 revs = [p.rev() for p in ctx.parents()]
535
547
536 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
548 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
537
549
538 @templatekeyword('manifest')
550 @templatekeyword('manifest')
539 def showmanifest(**args):
551 def showmanifest(**args):
540 repo, ctx, templ = args[r'repo'], args[r'ctx'], args[r'templ']
552 repo, ctx, templ = args[r'repo'], args[r'ctx'], args[r'templ']
541 mnode = ctx.manifestnode()
553 mnode = ctx.manifestnode()
542 if mnode is None:
554 if mnode is None:
543 # just avoid crash, we might want to use the 'ff...' hash in future
555 # just avoid crash, we might want to use the 'ff...' hash in future
544 return
556 return
545 args = args.copy()
557 args = args.copy()
546 args.update({r'rev': repo.manifestlog._revlog.rev(mnode),
558 args.update({r'rev': repo.manifestlog._revlog.rev(mnode),
547 r'node': hex(mnode)})
559 r'node': hex(mnode)})
548 return templ('manifest', **args)
560 return templ('manifest', **args)
549
561
550 def shownames(namespace, **args):
562 def shownames(namespace, **args):
551 """helper method to generate a template keyword for a namespace"""
563 """helper method to generate a template keyword for a namespace"""
552 args = pycompat.byteskwargs(args)
564 args = pycompat.byteskwargs(args)
553 ctx = args['ctx']
565 ctx = args['ctx']
554 repo = ctx.repo()
566 repo = ctx.repo()
555 ns = repo.names[namespace]
567 ns = repo.names[namespace]
556 names = ns.names(repo, ctx.node())
568 names = ns.names(repo, ctx.node())
557 return showlist(ns.templatename, names, args, plural=namespace)
569 return showlist(ns.templatename, names, args, plural=namespace)
558
570
559 @templatekeyword('namespaces')
571 @templatekeyword('namespaces')
560 def shownamespaces(**args):
572 def shownamespaces(**args):
561 """Dict of lists. Names attached to this changeset per
573 """Dict of lists. Names attached to this changeset per
562 namespace."""
574 namespace."""
563 args = pycompat.byteskwargs(args)
575 args = pycompat.byteskwargs(args)
564 ctx = args['ctx']
576 ctx = args['ctx']
565 repo = ctx.repo()
577 repo = ctx.repo()
566
578
567 namespaces = util.sortdict()
579 namespaces = util.sortdict()
568 colornames = {}
580 colornames = {}
569 builtins = {}
581 builtins = {}
570
582
571 for k, ns in repo.names.iteritems():
583 for k, ns in repo.names.iteritems():
572 namespaces[k] = showlist('name', ns.names(repo, ctx.node()), args)
584 namespaces[k] = showlist('name', ns.names(repo, ctx.node()), args)
573 colornames[k] = ns.colorname
585 colornames[k] = ns.colorname
574 builtins[k] = ns.builtin
586 builtins[k] = ns.builtin
575
587
576 f = _showlist('namespace', list(namespaces), args)
588 f = _showlist('namespace', list(namespaces), args)
577
589
578 def makemap(ns):
590 def makemap(ns):
579 return {
591 return {
580 'namespace': ns,
592 'namespace': ns,
581 'names': namespaces[ns],
593 'names': namespaces[ns],
582 'builtin': builtins[ns],
594 'builtin': builtins[ns],
583 'colorname': colornames[ns],
595 'colorname': colornames[ns],
584 }
596 }
585
597
586 return _hybrid(f, namespaces, makemap, lambda x: x['namespace'])
598 return _hybrid(f, namespaces, makemap, lambda x: x['namespace'])
587
599
588 @templatekeyword('node')
600 @templatekeyword('node')
589 def shownode(repo, ctx, templ, **args):
601 def shownode(repo, ctx, templ, **args):
590 """String. The changeset identification hash, as a 40 hexadecimal
602 """String. The changeset identification hash, as a 40 hexadecimal
591 digit string.
603 digit string.
592 """
604 """
593 return ctx.hex()
605 return ctx.hex()
594
606
595 @templatekeyword('obsolete')
607 @templatekeyword('obsolete')
596 def showobsolete(repo, ctx, templ, **args):
608 def showobsolete(repo, ctx, templ, **args):
597 """String. Whether the changeset is obsolete.
609 """String. Whether the changeset is obsolete.
598 """
610 """
599 if ctx.obsolete():
611 if ctx.obsolete():
600 return 'obsolete'
612 return 'obsolete'
601 return ''
613 return ''
602
614
603 @templatekeyword('peerpaths')
615 @templatekeyword('peerpaths')
604 def showpeerpaths(repo, **args):
616 def showpeerpaths(repo, **args):
605 """A dictionary of repository locations defined in the [paths] section
617 """A dictionary of repository locations defined in the [paths] section
606 of your configuration file. (EXPERIMENTAL)"""
618 of your configuration file. (EXPERIMENTAL)"""
607 # see commands.paths() for naming of dictionary keys
619 # see commands.paths() for naming of dictionary keys
608 paths = util.sortdict()
620 paths = util.sortdict()
609 for k, p in sorted(repo.ui.paths.iteritems()):
621 for k, p in sorted(repo.ui.paths.iteritems()):
610 d = util.sortdict()
622 d = util.sortdict()
611 d['url'] = p.rawloc
623 d['url'] = p.rawloc
612 d.update((o, v) for o, v in sorted(p.suboptions.iteritems()))
624 d.update((o, v) for o, v in sorted(p.suboptions.iteritems()))
613 def f():
625 def f():
614 yield d['url']
626 yield d['url']
615 paths[k] = hybriddict(d, gen=f())
627 paths[k] = hybriddict(d, gen=f())
616
628
617 # no hybriddict() since d['path'] can't be formatted as a string. perhaps
629 # no hybriddict() since d['path'] can't be formatted as a string. perhaps
618 # hybriddict() should call templatefilters.stringify(d[value]).
630 # hybriddict() should call templatefilters.stringify(d[value]).
619 return _hybrid(None, paths, lambda k: {'name': k, 'path': paths[k]},
631 return _hybrid(None, paths, lambda k: {'name': k, 'path': paths[k]},
620 lambda d: '%s=%s' % (d['name'], d['path']['url']))
632 lambda d: '%s=%s' % (d['name'], d['path']['url']))
621
633
622 @templatekeyword("predecessors")
634 @templatekeyword("predecessors")
623 def showpredecessors(repo, ctx, **args):
635 def showpredecessors(repo, ctx, **args):
624 """Returns the list if the closest visible successors
636 """Returns the list if the closest visible successors
625 """
637 """
626 predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
638 predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
627 predecessors = map(hex, predecessors)
639 predecessors = map(hex, predecessors)
628
640
629 return _hybrid(None, predecessors,
641 return _hybrid(None, predecessors,
630 lambda x: {'ctx': repo[x], 'revcache': {}},
642 lambda x: {'ctx': repo[x], 'revcache': {}},
631 lambda d: _formatrevnode(d['ctx']))
643 lambda d: _formatrevnode(d['ctx']))
632
644
633 @templatekeyword("successorssets")
645 @templatekeyword("successorssets")
634 def showsuccessorssets(repo, ctx, **args):
646 def showsuccessorssets(repo, ctx, **args):
635 """Returns a string of sets of successors for a changectx
647 """Returns a string of sets of successors for a changectx
636
648
637 Format used is: [ctx1, ctx2], [ctx3] if ctx has been splitted into ctx1 and
649 Format used is: [ctx1, ctx2], [ctx3] if ctx has been splitted into ctx1 and
638 ctx2 while also diverged into ctx3"""
650 ctx2 while also diverged into ctx3"""
639 if not ctx.obsolete():
651 if not ctx.obsolete():
640 return ''
652 return ''
641 args = pycompat.byteskwargs(args)
653 args = pycompat.byteskwargs(args)
642
654
643 ssets = obsutil.successorssets(repo, ctx.node(), closest=True)
655 ssets = obsutil.successorssets(repo, ctx.node(), closest=True)
644 ssets = [[hex(n) for n in ss] for ss in ssets]
656 ssets = [[hex(n) for n in ss] for ss in ssets]
645
657
646 data = []
658 data = []
647 for ss in ssets:
659 for ss in ssets:
648 h = _hybrid(None, ss, lambda x: {'ctx': repo[x], 'revcache': {}},
660 h = _hybrid(None, ss, lambda x: {'ctx': repo[x], 'revcache': {}},
649 lambda d: _formatrevnode(d['ctx']))
661 lambda d: _formatrevnode(d['ctx']))
650 data.append(h)
662 data.append(h)
651
663
652 # Format the successorssets
664 # Format the successorssets
653 def render(d):
665 def render(d):
654 t = []
666 t = []
655 for i in d.gen:
667 for i in d.gen:
656 t.append(i)
668 t.append(i)
657 return "".join(t)
669 return "".join(t)
658
670
659 def gen(data):
671 def gen(data):
660 yield "; ".join(render(d) for d in data)
672 yield "; ".join(render(d) for d in data)
661
673
662 return _hybrid(gen(data), data, lambda x: {'successorset': x},
674 return _hybrid(gen(data), data, lambda x: {'successorset': x},
663 lambda d: d["successorset"])
675 lambda d: d["successorset"])
664
676
665 @templatekeyword('p1rev')
677 @templatekeyword('p1rev')
666 def showp1rev(repo, ctx, templ, **args):
678 def showp1rev(repo, ctx, templ, **args):
667 """Integer. The repository-local revision number of the changeset's
679 """Integer. The repository-local revision number of the changeset's
668 first parent, or -1 if the changeset has no parents."""
680 first parent, or -1 if the changeset has no parents."""
669 return ctx.p1().rev()
681 return ctx.p1().rev()
670
682
671 @templatekeyword('p2rev')
683 @templatekeyword('p2rev')
672 def showp2rev(repo, ctx, templ, **args):
684 def showp2rev(repo, ctx, templ, **args):
673 """Integer. The repository-local revision number of the changeset's
685 """Integer. The repository-local revision number of the changeset's
674 second parent, or -1 if the changeset has no second parent."""
686 second parent, or -1 if the changeset has no second parent."""
675 return ctx.p2().rev()
687 return ctx.p2().rev()
676
688
677 @templatekeyword('p1node')
689 @templatekeyword('p1node')
678 def showp1node(repo, ctx, templ, **args):
690 def showp1node(repo, ctx, templ, **args):
679 """String. The identification hash of the changeset's first parent,
691 """String. The identification hash of the changeset's first parent,
680 as a 40 digit hexadecimal string. If the changeset has no parents, all
692 as a 40 digit hexadecimal string. If the changeset has no parents, all
681 digits are 0."""
693 digits are 0."""
682 return ctx.p1().hex()
694 return ctx.p1().hex()
683
695
684 @templatekeyword('p2node')
696 @templatekeyword('p2node')
685 def showp2node(repo, ctx, templ, **args):
697 def showp2node(repo, ctx, templ, **args):
686 """String. The identification hash of the changeset's second
698 """String. The identification hash of the changeset's second
687 parent, as a 40 digit hexadecimal string. If the changeset has no second
699 parent, as a 40 digit hexadecimal string. If the changeset has no second
688 parent, all digits are 0."""
700 parent, all digits are 0."""
689 return ctx.p2().hex()
701 return ctx.p2().hex()
690
702
691 @templatekeyword('parents')
703 @templatekeyword('parents')
692 def showparents(**args):
704 def showparents(**args):
693 """List of strings. The parents of the changeset in "rev:node"
705 """List of strings. The parents of the changeset in "rev:node"
694 format. If the changeset has only one "natural" parent (the predecessor
706 format. If the changeset has only one "natural" parent (the predecessor
695 revision) nothing is shown."""
707 revision) nothing is shown."""
696 args = pycompat.byteskwargs(args)
708 args = pycompat.byteskwargs(args)
697 repo = args['repo']
709 repo = args['repo']
698 ctx = args['ctx']
710 ctx = args['ctx']
699 pctxs = scmutil.meaningfulparents(repo, ctx)
711 pctxs = scmutil.meaningfulparents(repo, ctx)
700 # ifcontains() needs a list of str
712 # ifcontains() needs a list of str
701 prevs = ["%d" % p.rev() for p in pctxs]
713 prevs = ["%d" % p.rev() for p in pctxs]
702 parents = [[('rev', p.rev()),
714 parents = [[('rev', p.rev()),
703 ('node', p.hex()),
715 ('node', p.hex()),
704 ('phase', p.phasestr())]
716 ('phase', p.phasestr())]
705 for p in pctxs]
717 for p in pctxs]
706 f = _showlist('parent', parents, args)
718 f = _showlist('parent', parents, args)
707 return _hybrid(f, prevs, lambda x: {'ctx': repo[int(x)], 'revcache': {}},
719 return _hybrid(f, prevs, lambda x: {'ctx': repo[int(x)], 'revcache': {}},
708 lambda d: _formatrevnode(d['ctx']))
720 lambda d: _formatrevnode(d['ctx']))
709
721
710 @templatekeyword('phase')
722 @templatekeyword('phase')
711 def showphase(repo, ctx, templ, **args):
723 def showphase(repo, ctx, templ, **args):
712 """String. The changeset phase name."""
724 """String. The changeset phase name."""
713 return ctx.phasestr()
725 return ctx.phasestr()
714
726
715 @templatekeyword('phaseidx')
727 @templatekeyword('phaseidx')
716 def showphaseidx(repo, ctx, templ, **args):
728 def showphaseidx(repo, ctx, templ, **args):
717 """Integer. The changeset phase index."""
729 """Integer. The changeset phase index."""
718 return ctx.phase()
730 return ctx.phase()
719
731
720 @templatekeyword('rev')
732 @templatekeyword('rev')
721 def showrev(repo, ctx, templ, **args):
733 def showrev(repo, ctx, templ, **args):
722 """Integer. The repository-local changeset revision number."""
734 """Integer. The repository-local changeset revision number."""
723 return scmutil.intrev(ctx)
735 return scmutil.intrev(ctx)
724
736
725 def showrevslist(name, revs, **args):
737 def showrevslist(name, revs, **args):
726 """helper to generate a list of revisions in which a mapped template will
738 """helper to generate a list of revisions in which a mapped template will
727 be evaluated"""
739 be evaluated"""
728 args = pycompat.byteskwargs(args)
740 args = pycompat.byteskwargs(args)
729 repo = args['ctx'].repo()
741 repo = args['ctx'].repo()
730 # ifcontains() needs a list of str
742 # ifcontains() needs a list of str
731 revs = ["%d" % r for r in revs]
743 revs = ["%d" % r for r in revs]
732 f = _showlist(name, revs, args)
744 f = _showlist(name, revs, args)
733 return _hybrid(f, revs,
745 return _hybrid(f, revs,
734 lambda x: {name: x, 'ctx': repo[int(x)], 'revcache': {}},
746 lambda x: {name: x, 'ctx': repo[int(x)], 'revcache': {}},
735 lambda d: d[name])
747 lambda d: d[name])
736
748
737 @templatekeyword('subrepos')
749 @templatekeyword('subrepos')
738 def showsubrepos(**args):
750 def showsubrepos(**args):
739 """List of strings. Updated subrepositories in the changeset."""
751 """List of strings. Updated subrepositories in the changeset."""
740 args = pycompat.byteskwargs(args)
752 args = pycompat.byteskwargs(args)
741 ctx = args['ctx']
753 ctx = args['ctx']
742 substate = ctx.substate
754 substate = ctx.substate
743 if not substate:
755 if not substate:
744 return showlist('subrepo', [], args)
756 return showlist('subrepo', [], args)
745 psubstate = ctx.parents()[0].substate or {}
757 psubstate = ctx.parents()[0].substate or {}
746 subrepos = []
758 subrepos = []
747 for sub in substate:
759 for sub in substate:
748 if sub not in psubstate or substate[sub] != psubstate[sub]:
760 if sub not in psubstate or substate[sub] != psubstate[sub]:
749 subrepos.append(sub) # modified or newly added in ctx
761 subrepos.append(sub) # modified or newly added in ctx
750 for sub in psubstate:
762 for sub in psubstate:
751 if sub not in substate:
763 if sub not in substate:
752 subrepos.append(sub) # removed in ctx
764 subrepos.append(sub) # removed in ctx
753 return showlist('subrepo', sorted(subrepos), args)
765 return showlist('subrepo', sorted(subrepos), args)
754
766
755 # don't remove "showtags" definition, even though namespaces will put
767 # don't remove "showtags" definition, even though namespaces will put
756 # a helper function for "tags" keyword into "keywords" map automatically,
768 # a helper function for "tags" keyword into "keywords" map automatically,
757 # because online help text is built without namespaces initialization
769 # because online help text is built without namespaces initialization
758 @templatekeyword('tags')
770 @templatekeyword('tags')
759 def showtags(**args):
771 def showtags(**args):
760 """List of strings. Any tags associated with the changeset."""
772 """List of strings. Any tags associated with the changeset."""
761 return shownames('tags', **args)
773 return shownames('tags', **args)
762
774
763 def loadkeyword(ui, extname, registrarobj):
775 def loadkeyword(ui, extname, registrarobj):
764 """Load template keyword from specified registrarobj
776 """Load template keyword from specified registrarobj
765 """
777 """
766 for name, func in registrarobj._table.iteritems():
778 for name, func in registrarobj._table.iteritems():
767 keywords[name] = func
779 keywords[name] = func
768
780
769 @templatekeyword('termwidth')
781 @templatekeyword('termwidth')
770 def showtermwidth(repo, ctx, templ, **args):
782 def showtermwidth(repo, ctx, templ, **args):
771 """Integer. The width of the current terminal."""
783 """Integer. The width of the current terminal."""
772 return repo.ui.termwidth()
784 return repo.ui.termwidth()
773
785
774 @templatekeyword('troubles')
786 @templatekeyword('troubles')
775 def showtroubles(repo, **args):
787 def showtroubles(repo, **args):
776 """List of strings. Evolution troubles affecting the changeset.
788 """List of strings. Evolution troubles affecting the changeset.
777
789
778 (DEPRECATED)
790 (DEPRECATED)
779 """
791 """
780 msg = ("'troubles' is deprecated, "
792 msg = ("'troubles' is deprecated, "
781 "use 'instabilities'")
793 "use 'instabilities'")
782 repo.ui.deprecwarn(msg, '4.4')
794 repo.ui.deprecwarn(msg, '4.4')
783
795
784 return showinstabilities(repo=repo, **args)
796 return showinstabilities(repo=repo, **args)
785
797
786 @templatekeyword('instabilities')
798 @templatekeyword('instabilities')
787 def showinstabilities(**args):
799 def showinstabilities(**args):
788 """List of strings. Evolution instabilities affecting the changeset.
800 """List of strings. Evolution instabilities affecting the changeset.
789
801
790 (EXPERIMENTAL)
802 (EXPERIMENTAL)
791 """
803 """
792 args = pycompat.byteskwargs(args)
804 args = pycompat.byteskwargs(args)
793 return showlist('instability', args['ctx'].instabilities(), args,
805 return showlist('instability', args['ctx'].instabilities(), args,
794 plural='instabilities')
806 plural='instabilities')
795
807
796 # tell hggettext to extract docstrings from these functions:
808 # tell hggettext to extract docstrings from these functions:
797 i18nfunctions = keywords.values()
809 i18nfunctions = keywords.values()
@@ -1,1777 +1,1780 b''
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@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 collections
10 import collections
11 import contextlib
11 import contextlib
12 import errno
12 import errno
13 import getpass
13 import getpass
14 import inspect
14 import inspect
15 import os
15 import os
16 import re
16 import re
17 import signal
17 import signal
18 import socket
18 import socket
19 import subprocess
19 import subprocess
20 import sys
20 import sys
21 import tempfile
21 import tempfile
22 import traceback
22 import traceback
23
23
24 from .i18n import _
24 from .i18n import _
25 from .node import hex
25 from .node import hex
26
26
27 from . import (
27 from . import (
28 color,
28 color,
29 config,
29 config,
30 configitems,
30 configitems,
31 encoding,
31 encoding,
32 error,
32 error,
33 formatter,
33 formatter,
34 progress,
34 progress,
35 pycompat,
35 pycompat,
36 rcutil,
36 rcutil,
37 scmutil,
37 scmutil,
38 util,
38 util,
39 )
39 )
40
40
41 urlreq = util.urlreq
41 urlreq = util.urlreq
42
42
43 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
43 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
44 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
44 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
45 if not c.isalnum())
45 if not c.isalnum())
46
46
47 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
47 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
48 tweakrc = """
48 tweakrc = """
49 [ui]
49 [ui]
50 # The rollback command is dangerous. As a rule, don't use it.
50 # The rollback command is dangerous. As a rule, don't use it.
51 rollback = False
51 rollback = False
52
52
53 [commands]
53 [commands]
54 # Make `hg status` emit cwd-relative paths by default.
54 # Make `hg status` emit cwd-relative paths by default.
55 status.relative = yes
55 status.relative = yes
56
56
57 [diff]
57 [diff]
58 git = 1
58 git = 1
59 """
59 """
60
60
61 samplehgrcs = {
61 samplehgrcs = {
62 'user':
62 'user':
63 b"""# example user config (see 'hg help config' for more info)
63 b"""# example user config (see 'hg help config' for more info)
64 [ui]
64 [ui]
65 # name and email, e.g.
65 # name and email, e.g.
66 # username = Jane Doe <jdoe@example.com>
66 # username = Jane Doe <jdoe@example.com>
67 username =
67 username =
68
68
69 # uncomment to disable color in command output
69 # uncomment to disable color in command output
70 # (see 'hg help color' for details)
70 # (see 'hg help color' for details)
71 # color = never
71 # color = never
72
72
73 # uncomment to disable command output pagination
73 # uncomment to disable command output pagination
74 # (see 'hg help pager' for details)
74 # (see 'hg help pager' for details)
75 # paginate = never
75 # paginate = never
76
76
77 [extensions]
77 [extensions]
78 # uncomment these lines to enable some popular extensions
78 # uncomment these lines to enable some popular extensions
79 # (see 'hg help extensions' for more info)
79 # (see 'hg help extensions' for more info)
80 #
80 #
81 # churn =
81 # churn =
82 """,
82 """,
83
83
84 'cloned':
84 'cloned':
85 b"""# example repository config (see 'hg help config' for more info)
85 b"""# example repository config (see 'hg help config' for more info)
86 [paths]
86 [paths]
87 default = %s
87 default = %s
88
88
89 # path aliases to other clones of this repo in URLs or filesystem paths
89 # path aliases to other clones of this repo in URLs or filesystem paths
90 # (see 'hg help config.paths' for more info)
90 # (see 'hg help config.paths' for more info)
91 #
91 #
92 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
92 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
93 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
93 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
94 # my-clone = /home/jdoe/jdoes-clone
94 # my-clone = /home/jdoe/jdoes-clone
95
95
96 [ui]
96 [ui]
97 # name and email (local to this repository, optional), e.g.
97 # name and email (local to this repository, optional), e.g.
98 # username = Jane Doe <jdoe@example.com>
98 # username = Jane Doe <jdoe@example.com>
99 """,
99 """,
100
100
101 'local':
101 'local':
102 b"""# example repository config (see 'hg help config' for more info)
102 b"""# example repository config (see 'hg help config' for more info)
103 [paths]
103 [paths]
104 # path aliases to other clones of this repo in URLs or filesystem paths
104 # path aliases to other clones of this repo in URLs or filesystem paths
105 # (see 'hg help config.paths' for more info)
105 # (see 'hg help config.paths' for more info)
106 #
106 #
107 # default = http://example.com/hg/example-repo
107 # default = http://example.com/hg/example-repo
108 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
108 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
109 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
109 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
110 # my-clone = /home/jdoe/jdoes-clone
110 # my-clone = /home/jdoe/jdoes-clone
111
111
112 [ui]
112 [ui]
113 # name and email (local to this repository, optional), e.g.
113 # name and email (local to this repository, optional), e.g.
114 # username = Jane Doe <jdoe@example.com>
114 # username = Jane Doe <jdoe@example.com>
115 """,
115 """,
116
116
117 'global':
117 'global':
118 b"""# example system-wide hg config (see 'hg help config' for more info)
118 b"""# example system-wide hg config (see 'hg help config' for more info)
119
119
120 [ui]
120 [ui]
121 # uncomment to disable color in command output
121 # uncomment to disable color in command output
122 # (see 'hg help color' for details)
122 # (see 'hg help color' for details)
123 # color = never
123 # color = never
124
124
125 # uncomment to disable command output pagination
125 # uncomment to disable command output pagination
126 # (see 'hg help pager' for details)
126 # (see 'hg help pager' for details)
127 # paginate = never
127 # paginate = never
128
128
129 [extensions]
129 [extensions]
130 # uncomment these lines to enable some popular extensions
130 # uncomment these lines to enable some popular extensions
131 # (see 'hg help extensions' for more info)
131 # (see 'hg help extensions' for more info)
132 #
132 #
133 # blackbox =
133 # blackbox =
134 # churn =
134 # churn =
135 """,
135 """,
136 }
136 }
137
137
138
138
139 class httppasswordmgrdbproxy(object):
139 class httppasswordmgrdbproxy(object):
140 """Delays loading urllib2 until it's needed."""
140 """Delays loading urllib2 until it's needed."""
141 def __init__(self):
141 def __init__(self):
142 self._mgr = None
142 self._mgr = None
143
143
144 def _get_mgr(self):
144 def _get_mgr(self):
145 if self._mgr is None:
145 if self._mgr is None:
146 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
146 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
147 return self._mgr
147 return self._mgr
148
148
149 def add_password(self, *args, **kwargs):
149 def add_password(self, *args, **kwargs):
150 return self._get_mgr().add_password(*args, **kwargs)
150 return self._get_mgr().add_password(*args, **kwargs)
151
151
152 def find_user_password(self, *args, **kwargs):
152 def find_user_password(self, *args, **kwargs):
153 return self._get_mgr().find_user_password(*args, **kwargs)
153 return self._get_mgr().find_user_password(*args, **kwargs)
154
154
155 def _catchterm(*args):
155 def _catchterm(*args):
156 raise error.SignalInterrupt
156 raise error.SignalInterrupt
157
157
158 # unique object used to detect no default value has been provided when
158 # unique object used to detect no default value has been provided when
159 # retrieving configuration value.
159 # retrieving configuration value.
160 _unset = object()
160 _unset = object()
161
161
162 class ui(object):
162 class ui(object):
163 def __init__(self, src=None):
163 def __init__(self, src=None):
164 """Create a fresh new ui object if no src given
164 """Create a fresh new ui object if no src given
165
165
166 Use uimod.ui.load() to create a ui which knows global and user configs.
166 Use uimod.ui.load() to create a ui which knows global and user configs.
167 In most cases, you should use ui.copy() to create a copy of an existing
167 In most cases, you should use ui.copy() to create a copy of an existing
168 ui object.
168 ui object.
169 """
169 """
170 # _buffers: used for temporary capture of output
170 # _buffers: used for temporary capture of output
171 self._buffers = []
171 self._buffers = []
172 # _exithandlers: callbacks run at the end of a request
172 # _exithandlers: callbacks run at the end of a request
173 self._exithandlers = []
173 self._exithandlers = []
174 # 3-tuple describing how each buffer in the stack behaves.
174 # 3-tuple describing how each buffer in the stack behaves.
175 # Values are (capture stderr, capture subprocesses, apply labels).
175 # Values are (capture stderr, capture subprocesses, apply labels).
176 self._bufferstates = []
176 self._bufferstates = []
177 # When a buffer is active, defines whether we are expanding labels.
177 # When a buffer is active, defines whether we are expanding labels.
178 # This exists to prevent an extra list lookup.
178 # This exists to prevent an extra list lookup.
179 self._bufferapplylabels = None
179 self._bufferapplylabels = None
180 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
180 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
181 self._reportuntrusted = True
181 self._reportuntrusted = True
182 self._knownconfig = configitems.coreitems
182 self._knownconfig = configitems.coreitems
183 self._ocfg = config.config() # overlay
183 self._ocfg = config.config() # overlay
184 self._tcfg = config.config() # trusted
184 self._tcfg = config.config() # trusted
185 self._ucfg = config.config() # untrusted
185 self._ucfg = config.config() # untrusted
186 self._trustusers = set()
186 self._trustusers = set()
187 self._trustgroups = set()
187 self._trustgroups = set()
188 self.callhooks = True
188 self.callhooks = True
189 # Insecure server connections requested.
189 # Insecure server connections requested.
190 self.insecureconnections = False
190 self.insecureconnections = False
191 # Blocked time
191 # Blocked time
192 self.logblockedtimes = False
192 self.logblockedtimes = False
193 # color mode: see mercurial/color.py for possible value
193 # color mode: see mercurial/color.py for possible value
194 self._colormode = None
194 self._colormode = None
195 self._terminfoparams = {}
195 self._terminfoparams = {}
196 self._styles = {}
196 self._styles = {}
197
197
198 if src:
198 if src:
199 self._exithandlers = src._exithandlers
199 self._exithandlers = src._exithandlers
200 self.fout = src.fout
200 self.fout = src.fout
201 self.ferr = src.ferr
201 self.ferr = src.ferr
202 self.fin = src.fin
202 self.fin = src.fin
203 self.pageractive = src.pageractive
203 self.pageractive = src.pageractive
204 self._disablepager = src._disablepager
204 self._disablepager = src._disablepager
205 self._tweaked = src._tweaked
205 self._tweaked = src._tweaked
206
206
207 self._tcfg = src._tcfg.copy()
207 self._tcfg = src._tcfg.copy()
208 self._ucfg = src._ucfg.copy()
208 self._ucfg = src._ucfg.copy()
209 self._ocfg = src._ocfg.copy()
209 self._ocfg = src._ocfg.copy()
210 self._trustusers = src._trustusers.copy()
210 self._trustusers = src._trustusers.copy()
211 self._trustgroups = src._trustgroups.copy()
211 self._trustgroups = src._trustgroups.copy()
212 self.environ = src.environ
212 self.environ = src.environ
213 self.callhooks = src.callhooks
213 self.callhooks = src.callhooks
214 self.insecureconnections = src.insecureconnections
214 self.insecureconnections = src.insecureconnections
215 self._colormode = src._colormode
215 self._colormode = src._colormode
216 self._terminfoparams = src._terminfoparams.copy()
216 self._terminfoparams = src._terminfoparams.copy()
217 self._styles = src._styles.copy()
217 self._styles = src._styles.copy()
218
218
219 self.fixconfig()
219 self.fixconfig()
220
220
221 self.httppasswordmgrdb = src.httppasswordmgrdb
221 self.httppasswordmgrdb = src.httppasswordmgrdb
222 self._blockedtimes = src._blockedtimes
222 self._blockedtimes = src._blockedtimes
223 else:
223 else:
224 self.fout = util.stdout
224 self.fout = util.stdout
225 self.ferr = util.stderr
225 self.ferr = util.stderr
226 self.fin = util.stdin
226 self.fin = util.stdin
227 self.pageractive = False
227 self.pageractive = False
228 self._disablepager = False
228 self._disablepager = False
229 self._tweaked = False
229 self._tweaked = False
230
230
231 # shared read-only environment
231 # shared read-only environment
232 self.environ = encoding.environ
232 self.environ = encoding.environ
233
233
234 self.httppasswordmgrdb = httppasswordmgrdbproxy()
234 self.httppasswordmgrdb = httppasswordmgrdbproxy()
235 self._blockedtimes = collections.defaultdict(int)
235 self._blockedtimes = collections.defaultdict(int)
236
236
237 allowed = self.configlist('experimental', 'exportableenviron')
237 allowed = self.configlist('experimental', 'exportableenviron')
238 if '*' in allowed:
238 if '*' in allowed:
239 self._exportableenviron = self.environ
239 self._exportableenviron = self.environ
240 else:
240 else:
241 self._exportableenviron = {}
241 self._exportableenviron = {}
242 for k in allowed:
242 for k in allowed:
243 if k in self.environ:
243 if k in self.environ:
244 self._exportableenviron[k] = self.environ[k]
244 self._exportableenviron[k] = self.environ[k]
245
245
246 @classmethod
246 @classmethod
247 def load(cls):
247 def load(cls):
248 """Create a ui and load global and user configs"""
248 """Create a ui and load global and user configs"""
249 u = cls()
249 u = cls()
250 # we always trust global config files and environment variables
250 # we always trust global config files and environment variables
251 for t, f in rcutil.rccomponents():
251 for t, f in rcutil.rccomponents():
252 if t == 'path':
252 if t == 'path':
253 u.readconfig(f, trust=True)
253 u.readconfig(f, trust=True)
254 elif t == 'items':
254 elif t == 'items':
255 sections = set()
255 sections = set()
256 for section, name, value, source in f:
256 for section, name, value, source in f:
257 # do not set u._ocfg
257 # do not set u._ocfg
258 # XXX clean this up once immutable config object is a thing
258 # XXX clean this up once immutable config object is a thing
259 u._tcfg.set(section, name, value, source)
259 u._tcfg.set(section, name, value, source)
260 u._ucfg.set(section, name, value, source)
260 u._ucfg.set(section, name, value, source)
261 sections.add(section)
261 sections.add(section)
262 for section in sections:
262 for section in sections:
263 u.fixconfig(section=section)
263 u.fixconfig(section=section)
264 else:
264 else:
265 raise error.ProgrammingError('unknown rctype: %s' % t)
265 raise error.ProgrammingError('unknown rctype: %s' % t)
266 u._maybetweakdefaults()
266 u._maybetweakdefaults()
267 return u
267 return u
268
268
269 def _maybetweakdefaults(self):
269 def _maybetweakdefaults(self):
270 if not self.configbool('ui', 'tweakdefaults'):
270 if not self.configbool('ui', 'tweakdefaults'):
271 return
271 return
272 if self._tweaked or self.plain('tweakdefaults'):
272 if self._tweaked or self.plain('tweakdefaults'):
273 return
273 return
274
274
275 # Note: it is SUPER IMPORTANT that you set self._tweaked to
275 # Note: it is SUPER IMPORTANT that you set self._tweaked to
276 # True *before* any calls to setconfig(), otherwise you'll get
276 # True *before* any calls to setconfig(), otherwise you'll get
277 # infinite recursion between setconfig and this method.
277 # infinite recursion between setconfig and this method.
278 #
278 #
279 # TODO: We should extract an inner method in setconfig() to
279 # TODO: We should extract an inner method in setconfig() to
280 # avoid this weirdness.
280 # avoid this weirdness.
281 self._tweaked = True
281 self._tweaked = True
282 tmpcfg = config.config()
282 tmpcfg = config.config()
283 tmpcfg.parse('<tweakdefaults>', tweakrc)
283 tmpcfg.parse('<tweakdefaults>', tweakrc)
284 for section in tmpcfg:
284 for section in tmpcfg:
285 for name, value in tmpcfg.items(section):
285 for name, value in tmpcfg.items(section):
286 if not self.hasconfig(section, name):
286 if not self.hasconfig(section, name):
287 self.setconfig(section, name, value, "<tweakdefaults>")
287 self.setconfig(section, name, value, "<tweakdefaults>")
288
288
289 def copy(self):
289 def copy(self):
290 return self.__class__(self)
290 return self.__class__(self)
291
291
292 def resetstate(self):
292 def resetstate(self):
293 """Clear internal state that shouldn't persist across commands"""
293 """Clear internal state that shouldn't persist across commands"""
294 if self._progbar:
294 if self._progbar:
295 self._progbar.resetstate() # reset last-print time of progress bar
295 self._progbar.resetstate() # reset last-print time of progress bar
296 self.httppasswordmgrdb = httppasswordmgrdbproxy()
296 self.httppasswordmgrdb = httppasswordmgrdbproxy()
297
297
298 @contextlib.contextmanager
298 @contextlib.contextmanager
299 def timeblockedsection(self, key):
299 def timeblockedsection(self, key):
300 # this is open-coded below - search for timeblockedsection to find them
300 # this is open-coded below - search for timeblockedsection to find them
301 starttime = util.timer()
301 starttime = util.timer()
302 try:
302 try:
303 yield
303 yield
304 finally:
304 finally:
305 self._blockedtimes[key + '_blocked'] += \
305 self._blockedtimes[key + '_blocked'] += \
306 (util.timer() - starttime) * 1000
306 (util.timer() - starttime) * 1000
307
307
308 def formatter(self, topic, opts):
308 def formatter(self, topic, opts):
309 return formatter.formatter(self, self, topic, opts)
309 return formatter.formatter(self, self, topic, opts)
310
310
311 def _trusted(self, fp, f):
311 def _trusted(self, fp, f):
312 st = util.fstat(fp)
312 st = util.fstat(fp)
313 if util.isowner(st):
313 if util.isowner(st):
314 return True
314 return True
315
315
316 tusers, tgroups = self._trustusers, self._trustgroups
316 tusers, tgroups = self._trustusers, self._trustgroups
317 if '*' in tusers or '*' in tgroups:
317 if '*' in tusers or '*' in tgroups:
318 return True
318 return True
319
319
320 user = util.username(st.st_uid)
320 user = util.username(st.st_uid)
321 group = util.groupname(st.st_gid)
321 group = util.groupname(st.st_gid)
322 if user in tusers or group in tgroups or user == util.username():
322 if user in tusers or group in tgroups or user == util.username():
323 return True
323 return True
324
324
325 if self._reportuntrusted:
325 if self._reportuntrusted:
326 self.warn(_('not trusting file %s from untrusted '
326 self.warn(_('not trusting file %s from untrusted '
327 'user %s, group %s\n') % (f, user, group))
327 'user %s, group %s\n') % (f, user, group))
328 return False
328 return False
329
329
330 def readconfig(self, filename, root=None, trust=False,
330 def readconfig(self, filename, root=None, trust=False,
331 sections=None, remap=None):
331 sections=None, remap=None):
332 try:
332 try:
333 fp = open(filename, u'rb')
333 fp = open(filename, u'rb')
334 except IOError:
334 except IOError:
335 if not sections: # ignore unless we were looking for something
335 if not sections: # ignore unless we were looking for something
336 return
336 return
337 raise
337 raise
338
338
339 cfg = config.config()
339 cfg = config.config()
340 trusted = sections or trust or self._trusted(fp, filename)
340 trusted = sections or trust or self._trusted(fp, filename)
341
341
342 try:
342 try:
343 cfg.read(filename, fp, sections=sections, remap=remap)
343 cfg.read(filename, fp, sections=sections, remap=remap)
344 fp.close()
344 fp.close()
345 except error.ConfigError as inst:
345 except error.ConfigError as inst:
346 if trusted:
346 if trusted:
347 raise
347 raise
348 self.warn(_("ignored: %s\n") % str(inst))
348 self.warn(_("ignored: %s\n") % str(inst))
349
349
350 if self.plain():
350 if self.plain():
351 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
351 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
352 'logtemplate', 'statuscopies', 'style',
352 'logtemplate', 'statuscopies', 'style',
353 'traceback', 'verbose'):
353 'traceback', 'verbose'):
354 if k in cfg['ui']:
354 if k in cfg['ui']:
355 del cfg['ui'][k]
355 del cfg['ui'][k]
356 for k, v in cfg.items('defaults'):
356 for k, v in cfg.items('defaults'):
357 del cfg['defaults'][k]
357 del cfg['defaults'][k]
358 for k, v in cfg.items('commands'):
358 for k, v in cfg.items('commands'):
359 del cfg['commands'][k]
359 del cfg['commands'][k]
360 # Don't remove aliases from the configuration if in the exceptionlist
360 # Don't remove aliases from the configuration if in the exceptionlist
361 if self.plain('alias'):
361 if self.plain('alias'):
362 for k, v in cfg.items('alias'):
362 for k, v in cfg.items('alias'):
363 del cfg['alias'][k]
363 del cfg['alias'][k]
364 if self.plain('revsetalias'):
364 if self.plain('revsetalias'):
365 for k, v in cfg.items('revsetalias'):
365 for k, v in cfg.items('revsetalias'):
366 del cfg['revsetalias'][k]
366 del cfg['revsetalias'][k]
367 if self.plain('templatealias'):
367 if self.plain('templatealias'):
368 for k, v in cfg.items('templatealias'):
368 for k, v in cfg.items('templatealias'):
369 del cfg['templatealias'][k]
369 del cfg['templatealias'][k]
370
370
371 if trusted:
371 if trusted:
372 self._tcfg.update(cfg)
372 self._tcfg.update(cfg)
373 self._tcfg.update(self._ocfg)
373 self._tcfg.update(self._ocfg)
374 self._ucfg.update(cfg)
374 self._ucfg.update(cfg)
375 self._ucfg.update(self._ocfg)
375 self._ucfg.update(self._ocfg)
376
376
377 if root is None:
377 if root is None:
378 root = os.path.expanduser('~')
378 root = os.path.expanduser('~')
379 self.fixconfig(root=root)
379 self.fixconfig(root=root)
380
380
381 def fixconfig(self, root=None, section=None):
381 def fixconfig(self, root=None, section=None):
382 if section in (None, 'paths'):
382 if section in (None, 'paths'):
383 # expand vars and ~
383 # expand vars and ~
384 # translate paths relative to root (or home) into absolute paths
384 # translate paths relative to root (or home) into absolute paths
385 root = root or pycompat.getcwd()
385 root = root or pycompat.getcwd()
386 for c in self._tcfg, self._ucfg, self._ocfg:
386 for c in self._tcfg, self._ucfg, self._ocfg:
387 for n, p in c.items('paths'):
387 for n, p in c.items('paths'):
388 # Ignore sub-options.
388 # Ignore sub-options.
389 if ':' in n:
389 if ':' in n:
390 continue
390 continue
391 if not p:
391 if not p:
392 continue
392 continue
393 if '%%' in p:
393 if '%%' in p:
394 s = self.configsource('paths', n) or 'none'
394 s = self.configsource('paths', n) or 'none'
395 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
395 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
396 % (n, p, s))
396 % (n, p, s))
397 p = p.replace('%%', '%')
397 p = p.replace('%%', '%')
398 p = util.expandpath(p)
398 p = util.expandpath(p)
399 if not util.hasscheme(p) and not os.path.isabs(p):
399 if not util.hasscheme(p) and not os.path.isabs(p):
400 p = os.path.normpath(os.path.join(root, p))
400 p = os.path.normpath(os.path.join(root, p))
401 c.set("paths", n, p)
401 c.set("paths", n, p)
402
402
403 if section in (None, 'ui'):
403 if section in (None, 'ui'):
404 # update ui options
404 # update ui options
405 self.debugflag = self.configbool('ui', 'debug')
405 self.debugflag = self.configbool('ui', 'debug')
406 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
406 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
407 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
407 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
408 if self.verbose and self.quiet:
408 if self.verbose and self.quiet:
409 self.quiet = self.verbose = False
409 self.quiet = self.verbose = False
410 self._reportuntrusted = self.debugflag or self.configbool("ui",
410 self._reportuntrusted = self.debugflag or self.configbool("ui",
411 "report_untrusted")
411 "report_untrusted")
412 self.tracebackflag = self.configbool('ui', 'traceback')
412 self.tracebackflag = self.configbool('ui', 'traceback')
413 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
413 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
414
414
415 if section in (None, 'trusted'):
415 if section in (None, 'trusted'):
416 # update trust information
416 # update trust information
417 self._trustusers.update(self.configlist('trusted', 'users'))
417 self._trustusers.update(self.configlist('trusted', 'users'))
418 self._trustgroups.update(self.configlist('trusted', 'groups'))
418 self._trustgroups.update(self.configlist('trusted', 'groups'))
419
419
420 def backupconfig(self, section, item):
420 def backupconfig(self, section, item):
421 return (self._ocfg.backup(section, item),
421 return (self._ocfg.backup(section, item),
422 self._tcfg.backup(section, item),
422 self._tcfg.backup(section, item),
423 self._ucfg.backup(section, item),)
423 self._ucfg.backup(section, item),)
424 def restoreconfig(self, data):
424 def restoreconfig(self, data):
425 self._ocfg.restore(data[0])
425 self._ocfg.restore(data[0])
426 self._tcfg.restore(data[1])
426 self._tcfg.restore(data[1])
427 self._ucfg.restore(data[2])
427 self._ucfg.restore(data[2])
428
428
429 def setconfig(self, section, name, value, source=''):
429 def setconfig(self, section, name, value, source=''):
430 for cfg in (self._ocfg, self._tcfg, self._ucfg):
430 for cfg in (self._ocfg, self._tcfg, self._ucfg):
431 cfg.set(section, name, value, source)
431 cfg.set(section, name, value, source)
432 self.fixconfig(section=section)
432 self.fixconfig(section=section)
433 self._maybetweakdefaults()
433 self._maybetweakdefaults()
434
434
435 def _data(self, untrusted):
435 def _data(self, untrusted):
436 return untrusted and self._ucfg or self._tcfg
436 return untrusted and self._ucfg or self._tcfg
437
437
438 def configsource(self, section, name, untrusted=False):
438 def configsource(self, section, name, untrusted=False):
439 return self._data(untrusted).source(section, name)
439 return self._data(untrusted).source(section, name)
440
440
441 def config(self, section, name, default=_unset, untrusted=False):
441 def config(self, section, name, default=_unset, untrusted=False):
442 """return the plain string version of a config"""
442 """return the plain string version of a config"""
443 value = self._config(section, name, default=default,
443 value = self._config(section, name, default=default,
444 untrusted=untrusted)
444 untrusted=untrusted)
445 if value is _unset:
445 if value is _unset:
446 return None
446 return None
447 return value
447 return value
448
448
449 def _config(self, section, name, default=_unset, untrusted=False):
449 def _config(self, section, name, default=_unset, untrusted=False):
450 value = default
450 value = default
451 item = self._knownconfig.get(section, {}).get(name)
451 item = self._knownconfig.get(section, {}).get(name)
452 alternates = [(section, name)]
452 alternates = [(section, name)]
453
453
454 if item is not None:
454 if item is not None:
455 alternates.extend(item.alias)
455 alternates.extend(item.alias)
456
456
457 if default is _unset:
457 if default is _unset:
458 if item is None:
458 if item is None:
459 value = default
459 value = default
460 elif item.default is configitems.dynamicdefault:
460 elif item.default is configitems.dynamicdefault:
461 value = None
461 value = None
462 msg = "config item requires an explicit default value: '%s.%s'"
462 msg = "config item requires an explicit default value: '%s.%s'"
463 msg %= (section, name)
463 msg %= (section, name)
464 self.develwarn(msg, 2, 'warn-config-default')
464 self.develwarn(msg, 2, 'warn-config-default')
465 elif callable(item.default):
465 elif callable(item.default):
466 value = item.default()
466 value = item.default()
467 else:
467 else:
468 value = item.default
468 value = item.default
469 elif (item is not None
469 elif (item is not None
470 and item.default is not configitems.dynamicdefault):
470 and item.default is not configitems.dynamicdefault):
471 msg = ("specifying a default value for a registered "
471 msg = ("specifying a default value for a registered "
472 "config item: '%s.%s' '%s'")
472 "config item: '%s.%s' '%s'")
473 msg %= (section, name, default)
473 msg %= (section, name, default)
474 self.develwarn(msg, 2, 'warn-config-default')
474 self.develwarn(msg, 2, 'warn-config-default')
475
475
476 for s, n in alternates:
476 for s, n in alternates:
477 candidate = self._data(untrusted).get(s, n, None)
477 candidate = self._data(untrusted).get(s, n, None)
478 if candidate is not None:
478 if candidate is not None:
479 value = candidate
479 value = candidate
480 section = s
480 section = s
481 name = n
481 name = n
482 break
482 break
483
483
484 if self.debugflag and not untrusted and self._reportuntrusted:
484 if self.debugflag and not untrusted and self._reportuntrusted:
485 for s, n in alternates:
485 for s, n in alternates:
486 uvalue = self._ucfg.get(s, n)
486 uvalue = self._ucfg.get(s, n)
487 if uvalue is not None and uvalue != value:
487 if uvalue is not None and uvalue != value:
488 self.debug("ignoring untrusted configuration option "
488 self.debug("ignoring untrusted configuration option "
489 "%s.%s = %s\n" % (s, n, uvalue))
489 "%s.%s = %s\n" % (s, n, uvalue))
490 return value
490 return value
491
491
492 def configsuboptions(self, section, name, default=_unset, untrusted=False):
492 def configsuboptions(self, section, name, default=_unset, untrusted=False):
493 """Get a config option and all sub-options.
493 """Get a config option and all sub-options.
494
494
495 Some config options have sub-options that are declared with the
495 Some config options have sub-options that are declared with the
496 format "key:opt = value". This method is used to return the main
496 format "key:opt = value". This method is used to return the main
497 option and all its declared sub-options.
497 option and all its declared sub-options.
498
498
499 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
499 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
500 is a dict of defined sub-options where keys and values are strings.
500 is a dict of defined sub-options where keys and values are strings.
501 """
501 """
502 main = self.config(section, name, default, untrusted=untrusted)
502 main = self.config(section, name, default, untrusted=untrusted)
503 data = self._data(untrusted)
503 data = self._data(untrusted)
504 sub = {}
504 sub = {}
505 prefix = '%s:' % name
505 prefix = '%s:' % name
506 for k, v in data.items(section):
506 for k, v in data.items(section):
507 if k.startswith(prefix):
507 if k.startswith(prefix):
508 sub[k[len(prefix):]] = v
508 sub[k[len(prefix):]] = v
509
509
510 if self.debugflag and not untrusted and self._reportuntrusted:
510 if self.debugflag and not untrusted and self._reportuntrusted:
511 for k, v in sub.items():
511 for k, v in sub.items():
512 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
512 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
513 if uvalue is not None and uvalue != v:
513 if uvalue is not None and uvalue != v:
514 self.debug('ignoring untrusted configuration option '
514 self.debug('ignoring untrusted configuration option '
515 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
515 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
516
516
517 return main, sub
517 return main, sub
518
518
519 def configpath(self, section, name, default=_unset, untrusted=False):
519 def configpath(self, section, name, default=_unset, untrusted=False):
520 'get a path config item, expanded relative to repo root or config file'
520 'get a path config item, expanded relative to repo root or config file'
521 v = self.config(section, name, default, untrusted)
521 v = self.config(section, name, default, untrusted)
522 if v is None:
522 if v is None:
523 return None
523 return None
524 if not os.path.isabs(v) or "://" not in v:
524 if not os.path.isabs(v) or "://" not in v:
525 src = self.configsource(section, name, untrusted)
525 src = self.configsource(section, name, untrusted)
526 if ':' in src:
526 if ':' in src:
527 base = os.path.dirname(src.rsplit(':')[0])
527 base = os.path.dirname(src.rsplit(':')[0])
528 v = os.path.join(base, os.path.expanduser(v))
528 v = os.path.join(base, os.path.expanduser(v))
529 return v
529 return v
530
530
531 def configbool(self, section, name, default=_unset, untrusted=False):
531 def configbool(self, section, name, default=_unset, untrusted=False):
532 """parse a configuration element as a boolean
532 """parse a configuration element as a boolean
533
533
534 >>> u = ui(); s = 'foo'
534 >>> u = ui(); s = 'foo'
535 >>> u.setconfig(s, 'true', 'yes')
535 >>> u.setconfig(s, 'true', 'yes')
536 >>> u.configbool(s, 'true')
536 >>> u.configbool(s, 'true')
537 True
537 True
538 >>> u.setconfig(s, 'false', 'no')
538 >>> u.setconfig(s, 'false', 'no')
539 >>> u.configbool(s, 'false')
539 >>> u.configbool(s, 'false')
540 False
540 False
541 >>> u.configbool(s, 'unknown')
541 >>> u.configbool(s, 'unknown')
542 False
542 False
543 >>> u.configbool(s, 'unknown', True)
543 >>> u.configbool(s, 'unknown', True)
544 True
544 True
545 >>> u.setconfig(s, 'invalid', 'somevalue')
545 >>> u.setconfig(s, 'invalid', 'somevalue')
546 >>> u.configbool(s, 'invalid')
546 >>> u.configbool(s, 'invalid')
547 Traceback (most recent call last):
547 Traceback (most recent call last):
548 ...
548 ...
549 ConfigError: foo.invalid is not a boolean ('somevalue')
549 ConfigError: foo.invalid is not a boolean ('somevalue')
550 """
550 """
551
551
552 v = self._config(section, name, default, untrusted=untrusted)
552 v = self._config(section, name, default, untrusted=untrusted)
553 if v is None:
553 if v is None:
554 return v
554 return v
555 if v is _unset:
555 if v is _unset:
556 if default is _unset:
556 if default is _unset:
557 return False
557 return False
558 return default
558 return default
559 if isinstance(v, bool):
559 if isinstance(v, bool):
560 return v
560 return v
561 b = util.parsebool(v)
561 b = util.parsebool(v)
562 if b is None:
562 if b is None:
563 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
563 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
564 % (section, name, v))
564 % (section, name, v))
565 return b
565 return b
566
566
567 def configwith(self, convert, section, name, default=_unset,
567 def configwith(self, convert, section, name, default=_unset,
568 desc=None, untrusted=False):
568 desc=None, untrusted=False):
569 """parse a configuration element with a conversion function
569 """parse a configuration element with a conversion function
570
570
571 >>> u = ui(); s = 'foo'
571 >>> u = ui(); s = 'foo'
572 >>> u.setconfig(s, 'float1', '42')
572 >>> u.setconfig(s, 'float1', '42')
573 >>> u.configwith(float, s, 'float1')
573 >>> u.configwith(float, s, 'float1')
574 42.0
574 42.0
575 >>> u.setconfig(s, 'float2', '-4.25')
575 >>> u.setconfig(s, 'float2', '-4.25')
576 >>> u.configwith(float, s, 'float2')
576 >>> u.configwith(float, s, 'float2')
577 -4.25
577 -4.25
578 >>> u.configwith(float, s, 'unknown', 7)
578 >>> u.configwith(float, s, 'unknown', 7)
579 7.0
579 7.0
580 >>> u.setconfig(s, 'invalid', 'somevalue')
580 >>> u.setconfig(s, 'invalid', 'somevalue')
581 >>> u.configwith(float, s, 'invalid')
581 >>> u.configwith(float, s, 'invalid')
582 Traceback (most recent call last):
582 Traceback (most recent call last):
583 ...
583 ...
584 ConfigError: foo.invalid is not a valid float ('somevalue')
584 ConfigError: foo.invalid is not a valid float ('somevalue')
585 >>> u.configwith(float, s, 'invalid', desc='womble')
585 >>> u.configwith(float, s, 'invalid', desc='womble')
586 Traceback (most recent call last):
586 Traceback (most recent call last):
587 ...
587 ...
588 ConfigError: foo.invalid is not a valid womble ('somevalue')
588 ConfigError: foo.invalid is not a valid womble ('somevalue')
589 """
589 """
590
590
591 v = self.config(section, name, default, untrusted)
591 v = self.config(section, name, default, untrusted)
592 if v is None:
592 if v is None:
593 return v # do not attempt to convert None
593 return v # do not attempt to convert None
594 try:
594 try:
595 return convert(v)
595 return convert(v)
596 except (ValueError, error.ParseError):
596 except (ValueError, error.ParseError):
597 if desc is None:
597 if desc is None:
598 desc = convert.__name__
598 desc = convert.__name__
599 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
599 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
600 % (section, name, desc, v))
600 % (section, name, desc, v))
601
601
602 def configint(self, section, name, default=_unset, untrusted=False):
602 def configint(self, section, name, default=_unset, untrusted=False):
603 """parse a configuration element as an integer
603 """parse a configuration element as an integer
604
604
605 >>> u = ui(); s = 'foo'
605 >>> u = ui(); s = 'foo'
606 >>> u.setconfig(s, 'int1', '42')
606 >>> u.setconfig(s, 'int1', '42')
607 >>> u.configint(s, 'int1')
607 >>> u.configint(s, 'int1')
608 42
608 42
609 >>> u.setconfig(s, 'int2', '-42')
609 >>> u.setconfig(s, 'int2', '-42')
610 >>> u.configint(s, 'int2')
610 >>> u.configint(s, 'int2')
611 -42
611 -42
612 >>> u.configint(s, 'unknown', 7)
612 >>> u.configint(s, 'unknown', 7)
613 7
613 7
614 >>> u.setconfig(s, 'invalid', 'somevalue')
614 >>> u.setconfig(s, 'invalid', 'somevalue')
615 >>> u.configint(s, 'invalid')
615 >>> u.configint(s, 'invalid')
616 Traceback (most recent call last):
616 Traceback (most recent call last):
617 ...
617 ...
618 ConfigError: foo.invalid is not a valid integer ('somevalue')
618 ConfigError: foo.invalid is not a valid integer ('somevalue')
619 """
619 """
620
620
621 return self.configwith(int, section, name, default, 'integer',
621 return self.configwith(int, section, name, default, 'integer',
622 untrusted)
622 untrusted)
623
623
624 def configbytes(self, section, name, default=_unset, untrusted=False):
624 def configbytes(self, section, name, default=_unset, untrusted=False):
625 """parse a configuration element as a quantity in bytes
625 """parse a configuration element as a quantity in bytes
626
626
627 Units can be specified as b (bytes), k or kb (kilobytes), m or
627 Units can be specified as b (bytes), k or kb (kilobytes), m or
628 mb (megabytes), g or gb (gigabytes).
628 mb (megabytes), g or gb (gigabytes).
629
629
630 >>> u = ui(); s = 'foo'
630 >>> u = ui(); s = 'foo'
631 >>> u.setconfig(s, 'val1', '42')
631 >>> u.setconfig(s, 'val1', '42')
632 >>> u.configbytes(s, 'val1')
632 >>> u.configbytes(s, 'val1')
633 42
633 42
634 >>> u.setconfig(s, 'val2', '42.5 kb')
634 >>> u.setconfig(s, 'val2', '42.5 kb')
635 >>> u.configbytes(s, 'val2')
635 >>> u.configbytes(s, 'val2')
636 43520
636 43520
637 >>> u.configbytes(s, 'unknown', '7 MB')
637 >>> u.configbytes(s, 'unknown', '7 MB')
638 7340032
638 7340032
639 >>> u.setconfig(s, 'invalid', 'somevalue')
639 >>> u.setconfig(s, 'invalid', 'somevalue')
640 >>> u.configbytes(s, 'invalid')
640 >>> u.configbytes(s, 'invalid')
641 Traceback (most recent call last):
641 Traceback (most recent call last):
642 ...
642 ...
643 ConfigError: foo.invalid is not a byte quantity ('somevalue')
643 ConfigError: foo.invalid is not a byte quantity ('somevalue')
644 """
644 """
645
645
646 value = self._config(section, name, default, untrusted)
646 value = self._config(section, name, default, untrusted)
647 if value is _unset:
647 if value is _unset:
648 if default is _unset:
648 if default is _unset:
649 default = 0
649 default = 0
650 value = default
650 value = default
651 if not isinstance(value, bytes):
651 if not isinstance(value, bytes):
652 return value
652 return value
653 try:
653 try:
654 return util.sizetoint(value)
654 return util.sizetoint(value)
655 except error.ParseError:
655 except error.ParseError:
656 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
656 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
657 % (section, name, value))
657 % (section, name, value))
658
658
659 def configlist(self, section, name, default=_unset, untrusted=False):
659 def configlist(self, section, name, default=_unset, untrusted=False):
660 """parse a configuration element as a list of comma/space separated
660 """parse a configuration element as a list of comma/space separated
661 strings
661 strings
662
662
663 >>> u = ui(); s = 'foo'
663 >>> u = ui(); s = 'foo'
664 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
664 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
665 >>> u.configlist(s, 'list1')
665 >>> u.configlist(s, 'list1')
666 ['this', 'is', 'a small', 'test']
666 ['this', 'is', 'a small', 'test']
667 """
667 """
668 # default is not always a list
668 # default is not always a list
669 v = self.configwith(config.parselist, section, name, default,
669 v = self.configwith(config.parselist, section, name, default,
670 'list', untrusted)
670 'list', untrusted)
671 if isinstance(v, bytes):
671 if isinstance(v, bytes):
672 return config.parselist(v)
672 return config.parselist(v)
673 elif v is None:
673 elif v is None:
674 return []
674 return []
675 return v
675 return v
676
676
677 def configdate(self, section, name, default=_unset, untrusted=False):
677 def configdate(self, section, name, default=_unset, untrusted=False):
678 """parse a configuration element as a tuple of ints
678 """parse a configuration element as a tuple of ints
679
679
680 >>> u = ui(); s = 'foo'
680 >>> u = ui(); s = 'foo'
681 >>> u.setconfig(s, 'date', '0 0')
681 >>> u.setconfig(s, 'date', '0 0')
682 >>> u.configdate(s, 'date')
682 >>> u.configdate(s, 'date')
683 (0, 0)
683 (0, 0)
684 """
684 """
685 if self.config(section, name, default, untrusted):
685 if self.config(section, name, default, untrusted):
686 return self.configwith(util.parsedate, section, name, default,
686 return self.configwith(util.parsedate, section, name, default,
687 'date', untrusted)
687 'date', untrusted)
688 if default is _unset:
688 if default is _unset:
689 return None
689 return None
690 return default
690 return default
691
691
692 def hasconfig(self, section, name, untrusted=False):
692 def hasconfig(self, section, name, untrusted=False):
693 return self._data(untrusted).hasitem(section, name)
693 return self._data(untrusted).hasitem(section, name)
694
694
695 def has_section(self, section, untrusted=False):
695 def has_section(self, section, untrusted=False):
696 '''tell whether section exists in config.'''
696 '''tell whether section exists in config.'''
697 return section in self._data(untrusted)
697 return section in self._data(untrusted)
698
698
699 def configitems(self, section, untrusted=False, ignoresub=False):
699 def configitems(self, section, untrusted=False, ignoresub=False):
700 items = self._data(untrusted).items(section)
700 items = self._data(untrusted).items(section)
701 if ignoresub:
701 if ignoresub:
702 newitems = {}
702 newitems = {}
703 for k, v in items:
703 for k, v in items:
704 if ':' not in k:
704 if ':' not in k:
705 newitems[k] = v
705 newitems[k] = v
706 items = newitems.items()
706 items = newitems.items()
707 if self.debugflag and not untrusted and self._reportuntrusted:
707 if self.debugflag and not untrusted and self._reportuntrusted:
708 for k, v in self._ucfg.items(section):
708 for k, v in self._ucfg.items(section):
709 if self._tcfg.get(section, k) != v:
709 if self._tcfg.get(section, k) != v:
710 self.debug("ignoring untrusted configuration option "
710 self.debug("ignoring untrusted configuration option "
711 "%s.%s = %s\n" % (section, k, v))
711 "%s.%s = %s\n" % (section, k, v))
712 return items
712 return items
713
713
714 def walkconfig(self, untrusted=False):
714 def walkconfig(self, untrusted=False):
715 cfg = self._data(untrusted)
715 cfg = self._data(untrusted)
716 for section in cfg.sections():
716 for section in cfg.sections():
717 for name, value in self.configitems(section, untrusted):
717 for name, value in self.configitems(section, untrusted):
718 yield section, name, value
718 yield section, name, value
719
719
720 def plain(self, feature=None):
720 def plain(self, feature=None):
721 '''is plain mode active?
721 '''is plain mode active?
722
722
723 Plain mode means that all configuration variables which affect
723 Plain mode means that all configuration variables which affect
724 the behavior and output of Mercurial should be
724 the behavior and output of Mercurial should be
725 ignored. Additionally, the output should be stable,
725 ignored. Additionally, the output should be stable,
726 reproducible and suitable for use in scripts or applications.
726 reproducible and suitable for use in scripts or applications.
727
727
728 The only way to trigger plain mode is by setting either the
728 The only way to trigger plain mode is by setting either the
729 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
729 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
730
730
731 The return value can either be
731 The return value can either be
732 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
732 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
733 - True otherwise
733 - True otherwise
734 '''
734 '''
735 if ('HGPLAIN' not in encoding.environ and
735 if ('HGPLAIN' not in encoding.environ and
736 'HGPLAINEXCEPT' not in encoding.environ):
736 'HGPLAINEXCEPT' not in encoding.environ):
737 return False
737 return False
738 exceptions = encoding.environ.get('HGPLAINEXCEPT',
738 exceptions = encoding.environ.get('HGPLAINEXCEPT',
739 '').strip().split(',')
739 '').strip().split(',')
740 if feature and exceptions:
740 if feature and exceptions:
741 return feature not in exceptions
741 return feature not in exceptions
742 return True
742 return True
743
743
744 def username(self):
744 def username(self):
745 """Return default username to be used in commits.
745 """Return default username to be used in commits.
746
746
747 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
747 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
748 and stop searching if one of these is set.
748 and stop searching if one of these is set.
749 If not found and ui.askusername is True, ask the user, else use
749 If not found and ui.askusername is True, ask the user, else use
750 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
750 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
751 """
751 """
752 user = encoding.environ.get("HGUSER")
752 user = encoding.environ.get("HGUSER")
753 if user is None:
753 if user is None:
754 user = self.config("ui", "username")
754 user = self.config("ui", "username")
755 if user is not None:
755 if user is not None:
756 user = os.path.expandvars(user)
756 user = os.path.expandvars(user)
757 if user is None:
757 if user is None:
758 user = encoding.environ.get("EMAIL")
758 user = encoding.environ.get("EMAIL")
759 if user is None and self.configbool("ui", "askusername"):
759 if user is None and self.configbool("ui", "askusername"):
760 user = self.prompt(_("enter a commit username:"), default=None)
760 user = self.prompt(_("enter a commit username:"), default=None)
761 if user is None and not self.interactive():
761 if user is None and not self.interactive():
762 try:
762 try:
763 user = '%s@%s' % (util.getuser(), socket.getfqdn())
763 user = '%s@%s' % (util.getuser(), socket.getfqdn())
764 self.warn(_("no username found, using '%s' instead\n") % user)
764 self.warn(_("no username found, using '%s' instead\n") % user)
765 except KeyError:
765 except KeyError:
766 pass
766 pass
767 if not user:
767 if not user:
768 raise error.Abort(_('no username supplied'),
768 raise error.Abort(_('no username supplied'),
769 hint=_("use 'hg config --edit' "
769 hint=_("use 'hg config --edit' "
770 'to set your username'))
770 'to set your username'))
771 if "\n" in user:
771 if "\n" in user:
772 raise error.Abort(_("username %s contains a newline\n")
772 raise error.Abort(_("username %s contains a newline\n")
773 % repr(user))
773 % repr(user))
774 return user
774 return user
775
775
776 def shortuser(self, user):
776 def shortuser(self, user):
777 """Return a short representation of a user name or email address."""
777 """Return a short representation of a user name or email address."""
778 if not self.verbose:
778 if not self.verbose:
779 user = util.shortuser(user)
779 user = util.shortuser(user)
780 return user
780 return user
781
781
782 def expandpath(self, loc, default=None):
782 def expandpath(self, loc, default=None):
783 """Return repository location relative to cwd or from [paths]"""
783 """Return repository location relative to cwd or from [paths]"""
784 try:
784 try:
785 p = self.paths.getpath(loc)
785 p = self.paths.getpath(loc)
786 if p:
786 if p:
787 return p.rawloc
787 return p.rawloc
788 except error.RepoError:
788 except error.RepoError:
789 pass
789 pass
790
790
791 if default:
791 if default:
792 try:
792 try:
793 p = self.paths.getpath(default)
793 p = self.paths.getpath(default)
794 if p:
794 if p:
795 return p.rawloc
795 return p.rawloc
796 except error.RepoError:
796 except error.RepoError:
797 pass
797 pass
798
798
799 return loc
799 return loc
800
800
801 @util.propertycache
801 @util.propertycache
802 def paths(self):
802 def paths(self):
803 return paths(self)
803 return paths(self)
804
804
805 def pushbuffer(self, error=False, subproc=False, labeled=False):
805 def pushbuffer(self, error=False, subproc=False, labeled=False):
806 """install a buffer to capture standard output of the ui object
806 """install a buffer to capture standard output of the ui object
807
807
808 If error is True, the error output will be captured too.
808 If error is True, the error output will be captured too.
809
809
810 If subproc is True, output from subprocesses (typically hooks) will be
810 If subproc is True, output from subprocesses (typically hooks) will be
811 captured too.
811 captured too.
812
812
813 If labeled is True, any labels associated with buffered
813 If labeled is True, any labels associated with buffered
814 output will be handled. By default, this has no effect
814 output will be handled. By default, this has no effect
815 on the output returned, but extensions and GUI tools may
815 on the output returned, but extensions and GUI tools may
816 handle this argument and returned styled output. If output
816 handle this argument and returned styled output. If output
817 is being buffered so it can be captured and parsed or
817 is being buffered so it can be captured and parsed or
818 processed, labeled should not be set to True.
818 processed, labeled should not be set to True.
819 """
819 """
820 self._buffers.append([])
820 self._buffers.append([])
821 self._bufferstates.append((error, subproc, labeled))
821 self._bufferstates.append((error, subproc, labeled))
822 self._bufferapplylabels = labeled
822 self._bufferapplylabels = labeled
823
823
824 def popbuffer(self):
824 def popbuffer(self):
825 '''pop the last buffer and return the buffered output'''
825 '''pop the last buffer and return the buffered output'''
826 self._bufferstates.pop()
826 self._bufferstates.pop()
827 if self._bufferstates:
827 if self._bufferstates:
828 self._bufferapplylabels = self._bufferstates[-1][2]
828 self._bufferapplylabels = self._bufferstates[-1][2]
829 else:
829 else:
830 self._bufferapplylabels = None
830 self._bufferapplylabels = None
831
831
832 return "".join(self._buffers.pop())
832 return "".join(self._buffers.pop())
833
833
834 def write(self, *args, **opts):
834 def write(self, *args, **opts):
835 '''write args to output
835 '''write args to output
836
836
837 By default, this method simply writes to the buffer or stdout.
837 By default, this method simply writes to the buffer or stdout.
838 Color mode can be set on the UI class to have the output decorated
838 Color mode can be set on the UI class to have the output decorated
839 with color modifier before being written to stdout.
839 with color modifier before being written to stdout.
840
840
841 The color used is controlled by an optional keyword argument, "label".
841 The color used is controlled by an optional keyword argument, "label".
842 This should be a string containing label names separated by space.
842 This should be a string containing label names separated by space.
843 Label names take the form of "topic.type". For example, ui.debug()
843 Label names take the form of "topic.type". For example, ui.debug()
844 issues a label of "ui.debug".
844 issues a label of "ui.debug".
845
845
846 When labeling output for a specific command, a label of
846 When labeling output for a specific command, a label of
847 "cmdname.type" is recommended. For example, status issues
847 "cmdname.type" is recommended. For example, status issues
848 a label of "status.modified" for modified files.
848 a label of "status.modified" for modified files.
849 '''
849 '''
850 if self._buffers and not opts.get('prompt', False):
850 if self._buffers and not opts.get('prompt', False):
851 if self._bufferapplylabels:
851 if self._bufferapplylabels:
852 label = opts.get('label', '')
852 label = opts.get('label', '')
853 self._buffers[-1].extend(self.label(a, label) for a in args)
853 self._buffers[-1].extend(self.label(a, label) for a in args)
854 else:
854 else:
855 self._buffers[-1].extend(args)
855 self._buffers[-1].extend(args)
856 elif self._colormode == 'win32':
856 elif self._colormode == 'win32':
857 # windows color printing is its own can of crab, defer to
857 # windows color printing is its own can of crab, defer to
858 # the color module and that is it.
858 # the color module and that is it.
859 color.win32print(self, self._write, *args, **opts)
859 color.win32print(self, self._write, *args, **opts)
860 else:
860 else:
861 msgs = args
861 msgs = args
862 if self._colormode is not None:
862 if self._colormode is not None:
863 label = opts.get('label', '')
863 label = opts.get('label', '')
864 msgs = [self.label(a, label) for a in args]
864 msgs = [self.label(a, label) for a in args]
865 self._write(*msgs, **opts)
865 self._write(*msgs, **opts)
866
866
867 def _write(self, *msgs, **opts):
867 def _write(self, *msgs, **opts):
868 self._progclear()
868 self._progclear()
869 # opencode timeblockedsection because this is a critical path
869 # opencode timeblockedsection because this is a critical path
870 starttime = util.timer()
870 starttime = util.timer()
871 try:
871 try:
872 for a in msgs:
872 for a in msgs:
873 self.fout.write(a)
873 self.fout.write(a)
874 except IOError as err:
874 except IOError as err:
875 raise error.StdioError(err)
875 raise error.StdioError(err)
876 finally:
876 finally:
877 self._blockedtimes['stdio_blocked'] += \
877 self._blockedtimes['stdio_blocked'] += \
878 (util.timer() - starttime) * 1000
878 (util.timer() - starttime) * 1000
879
879
880 def write_err(self, *args, **opts):
880 def write_err(self, *args, **opts):
881 self._progclear()
881 self._progclear()
882 if self._bufferstates and self._bufferstates[-1][0]:
882 if self._bufferstates and self._bufferstates[-1][0]:
883 self.write(*args, **opts)
883 self.write(*args, **opts)
884 elif self._colormode == 'win32':
884 elif self._colormode == 'win32':
885 # windows color printing is its own can of crab, defer to
885 # windows color printing is its own can of crab, defer to
886 # the color module and that is it.
886 # the color module and that is it.
887 color.win32print(self, self._write_err, *args, **opts)
887 color.win32print(self, self._write_err, *args, **opts)
888 else:
888 else:
889 msgs = args
889 msgs = args
890 if self._colormode is not None:
890 if self._colormode is not None:
891 label = opts.get('label', '')
891 label = opts.get('label', '')
892 msgs = [self.label(a, label) for a in args]
892 msgs = [self.label(a, label) for a in args]
893 self._write_err(*msgs, **opts)
893 self._write_err(*msgs, **opts)
894
894
895 def _write_err(self, *msgs, **opts):
895 def _write_err(self, *msgs, **opts):
896 try:
896 try:
897 with self.timeblockedsection('stdio'):
897 with self.timeblockedsection('stdio'):
898 if not getattr(self.fout, 'closed', False):
898 if not getattr(self.fout, 'closed', False):
899 self.fout.flush()
899 self.fout.flush()
900 for a in msgs:
900 for a in msgs:
901 self.ferr.write(a)
901 self.ferr.write(a)
902 # stderr may be buffered under win32 when redirected to files,
902 # stderr may be buffered under win32 when redirected to files,
903 # including stdout.
903 # including stdout.
904 if not getattr(self.ferr, 'closed', False):
904 if not getattr(self.ferr, 'closed', False):
905 self.ferr.flush()
905 self.ferr.flush()
906 except IOError as inst:
906 except IOError as inst:
907 raise error.StdioError(inst)
907 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
908 raise error.StdioError(inst)
908
909
909 def flush(self):
910 def flush(self):
910 # opencode timeblockedsection because this is a critical path
911 # opencode timeblockedsection because this is a critical path
911 starttime = util.timer()
912 starttime = util.timer()
912 try:
913 try:
913 try:
914 try:
914 self.fout.flush()
915 self.fout.flush()
915 except IOError as err:
916 except IOError as err:
916 raise error.StdioError(err)
917 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
918 raise error.StdioError(err)
917 finally:
919 finally:
918 try:
920 try:
919 self.ferr.flush()
921 self.ferr.flush()
920 except IOError as err:
922 except IOError as err:
921 raise error.StdioError(err)
923 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
924 raise error.StdioError(err)
922 finally:
925 finally:
923 self._blockedtimes['stdio_blocked'] += \
926 self._blockedtimes['stdio_blocked'] += \
924 (util.timer() - starttime) * 1000
927 (util.timer() - starttime) * 1000
925
928
926 def _isatty(self, fh):
929 def _isatty(self, fh):
927 if self.configbool('ui', 'nontty'):
930 if self.configbool('ui', 'nontty'):
928 return False
931 return False
929 return util.isatty(fh)
932 return util.isatty(fh)
930
933
931 def disablepager(self):
934 def disablepager(self):
932 self._disablepager = True
935 self._disablepager = True
933
936
934 def pager(self, command):
937 def pager(self, command):
935 """Start a pager for subsequent command output.
938 """Start a pager for subsequent command output.
936
939
937 Commands which produce a long stream of output should call
940 Commands which produce a long stream of output should call
938 this function to activate the user's preferred pagination
941 this function to activate the user's preferred pagination
939 mechanism (which may be no pager). Calling this function
942 mechanism (which may be no pager). Calling this function
940 precludes any future use of interactive functionality, such as
943 precludes any future use of interactive functionality, such as
941 prompting the user or activating curses.
944 prompting the user or activating curses.
942
945
943 Args:
946 Args:
944 command: The full, non-aliased name of the command. That is, "log"
947 command: The full, non-aliased name of the command. That is, "log"
945 not "history, "summary" not "summ", etc.
948 not "history, "summary" not "summ", etc.
946 """
949 """
947 if (self._disablepager
950 if (self._disablepager
948 or self.pageractive):
951 or self.pageractive):
949 # how pager should do is already determined
952 # how pager should do is already determined
950 return
953 return
951
954
952 if not command.startswith('internal-always-') and (
955 if not command.startswith('internal-always-') and (
953 # explicit --pager=on (= 'internal-always-' prefix) should
956 # explicit --pager=on (= 'internal-always-' prefix) should
954 # take precedence over disabling factors below
957 # take precedence over disabling factors below
955 command in self.configlist('pager', 'ignore')
958 command in self.configlist('pager', 'ignore')
956 or not self.configbool('ui', 'paginate')
959 or not self.configbool('ui', 'paginate')
957 or not self.configbool('pager', 'attend-' + command, True)
960 or not self.configbool('pager', 'attend-' + command, True)
958 # TODO: if we want to allow HGPLAINEXCEPT=pager,
961 # TODO: if we want to allow HGPLAINEXCEPT=pager,
959 # formatted() will need some adjustment.
962 # formatted() will need some adjustment.
960 or not self.formatted()
963 or not self.formatted()
961 or self.plain()
964 or self.plain()
962 # TODO: expose debugger-enabled on the UI object
965 # TODO: expose debugger-enabled on the UI object
963 or '--debugger' in pycompat.sysargv):
966 or '--debugger' in pycompat.sysargv):
964 # We only want to paginate if the ui appears to be
967 # We only want to paginate if the ui appears to be
965 # interactive, the user didn't say HGPLAIN or
968 # interactive, the user didn't say HGPLAIN or
966 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
969 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
967 return
970 return
968
971
969 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
972 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
970 if not pagercmd:
973 if not pagercmd:
971 return
974 return
972
975
973 pagerenv = {}
976 pagerenv = {}
974 for name, value in rcutil.defaultpagerenv().items():
977 for name, value in rcutil.defaultpagerenv().items():
975 if name not in encoding.environ:
978 if name not in encoding.environ:
976 pagerenv[name] = value
979 pagerenv[name] = value
977
980
978 self.debug('starting pager for command %r\n' % command)
981 self.debug('starting pager for command %r\n' % command)
979 self.flush()
982 self.flush()
980
983
981 wasformatted = self.formatted()
984 wasformatted = self.formatted()
982 if util.safehasattr(signal, "SIGPIPE"):
985 if util.safehasattr(signal, "SIGPIPE"):
983 signal.signal(signal.SIGPIPE, _catchterm)
986 signal.signal(signal.SIGPIPE, _catchterm)
984 if self._runpager(pagercmd, pagerenv):
987 if self._runpager(pagercmd, pagerenv):
985 self.pageractive = True
988 self.pageractive = True
986 # Preserve the formatted-ness of the UI. This is important
989 # Preserve the formatted-ness of the UI. This is important
987 # because we mess with stdout, which might confuse
990 # because we mess with stdout, which might confuse
988 # auto-detection of things being formatted.
991 # auto-detection of things being formatted.
989 self.setconfig('ui', 'formatted', wasformatted, 'pager')
992 self.setconfig('ui', 'formatted', wasformatted, 'pager')
990 self.setconfig('ui', 'interactive', False, 'pager')
993 self.setconfig('ui', 'interactive', False, 'pager')
991
994
992 # If pagermode differs from color.mode, reconfigure color now that
995 # If pagermode differs from color.mode, reconfigure color now that
993 # pageractive is set.
996 # pageractive is set.
994 cm = self._colormode
997 cm = self._colormode
995 if cm != self.config('color', 'pagermode', cm):
998 if cm != self.config('color', 'pagermode', cm):
996 color.setup(self)
999 color.setup(self)
997 else:
1000 else:
998 # If the pager can't be spawned in dispatch when --pager=on is
1001 # If the pager can't be spawned in dispatch when --pager=on is
999 # given, don't try again when the command runs, to avoid a duplicate
1002 # given, don't try again when the command runs, to avoid a duplicate
1000 # warning about a missing pager command.
1003 # warning about a missing pager command.
1001 self.disablepager()
1004 self.disablepager()
1002
1005
1003 def _runpager(self, command, env=None):
1006 def _runpager(self, command, env=None):
1004 """Actually start the pager and set up file descriptors.
1007 """Actually start the pager and set up file descriptors.
1005
1008
1006 This is separate in part so that extensions (like chg) can
1009 This is separate in part so that extensions (like chg) can
1007 override how a pager is invoked.
1010 override how a pager is invoked.
1008 """
1011 """
1009 if command == 'cat':
1012 if command == 'cat':
1010 # Save ourselves some work.
1013 # Save ourselves some work.
1011 return False
1014 return False
1012 # If the command doesn't contain any of these characters, we
1015 # If the command doesn't contain any of these characters, we
1013 # assume it's a binary and exec it directly. This means for
1016 # assume it's a binary and exec it directly. This means for
1014 # simple pager command configurations, we can degrade
1017 # simple pager command configurations, we can degrade
1015 # gracefully and tell the user about their broken pager.
1018 # gracefully and tell the user about their broken pager.
1016 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1019 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1017
1020
1018 if pycompat.osname == 'nt' and not shell:
1021 if pycompat.osname == 'nt' and not shell:
1019 # Window's built-in `more` cannot be invoked with shell=False, but
1022 # Window's built-in `more` cannot be invoked with shell=False, but
1020 # its `more.com` can. Hide this implementation detail from the
1023 # its `more.com` can. Hide this implementation detail from the
1021 # user so we can also get sane bad PAGER behavior. MSYS has
1024 # user so we can also get sane bad PAGER behavior. MSYS has
1022 # `more.exe`, so do a cmd.exe style resolution of the executable to
1025 # `more.exe`, so do a cmd.exe style resolution of the executable to
1023 # determine which one to use.
1026 # determine which one to use.
1024 fullcmd = util.findexe(command)
1027 fullcmd = util.findexe(command)
1025 if not fullcmd:
1028 if not fullcmd:
1026 self.warn(_("missing pager command '%s', skipping pager\n")
1029 self.warn(_("missing pager command '%s', skipping pager\n")
1027 % command)
1030 % command)
1028 return False
1031 return False
1029
1032
1030 command = fullcmd
1033 command = fullcmd
1031
1034
1032 try:
1035 try:
1033 pager = subprocess.Popen(
1036 pager = subprocess.Popen(
1034 command, shell=shell, bufsize=-1,
1037 command, shell=shell, bufsize=-1,
1035 close_fds=util.closefds, stdin=subprocess.PIPE,
1038 close_fds=util.closefds, stdin=subprocess.PIPE,
1036 stdout=util.stdout, stderr=util.stderr,
1039 stdout=util.stdout, stderr=util.stderr,
1037 env=util.shellenviron(env))
1040 env=util.shellenviron(env))
1038 except OSError as e:
1041 except OSError as e:
1039 if e.errno == errno.ENOENT and not shell:
1042 if e.errno == errno.ENOENT and not shell:
1040 self.warn(_("missing pager command '%s', skipping pager\n")
1043 self.warn(_("missing pager command '%s', skipping pager\n")
1041 % command)
1044 % command)
1042 return False
1045 return False
1043 raise
1046 raise
1044
1047
1045 # back up original file descriptors
1048 # back up original file descriptors
1046 stdoutfd = os.dup(util.stdout.fileno())
1049 stdoutfd = os.dup(util.stdout.fileno())
1047 stderrfd = os.dup(util.stderr.fileno())
1050 stderrfd = os.dup(util.stderr.fileno())
1048
1051
1049 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
1052 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
1050 if self._isatty(util.stderr):
1053 if self._isatty(util.stderr):
1051 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
1054 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
1052
1055
1053 @self.atexit
1056 @self.atexit
1054 def killpager():
1057 def killpager():
1055 if util.safehasattr(signal, "SIGINT"):
1058 if util.safehasattr(signal, "SIGINT"):
1056 signal.signal(signal.SIGINT, signal.SIG_IGN)
1059 signal.signal(signal.SIGINT, signal.SIG_IGN)
1057 # restore original fds, closing pager.stdin copies in the process
1060 # restore original fds, closing pager.stdin copies in the process
1058 os.dup2(stdoutfd, util.stdout.fileno())
1061 os.dup2(stdoutfd, util.stdout.fileno())
1059 os.dup2(stderrfd, util.stderr.fileno())
1062 os.dup2(stderrfd, util.stderr.fileno())
1060 pager.stdin.close()
1063 pager.stdin.close()
1061 pager.wait()
1064 pager.wait()
1062
1065
1063 return True
1066 return True
1064
1067
1065 def atexit(self, func, *args, **kwargs):
1068 def atexit(self, func, *args, **kwargs):
1066 '''register a function to run after dispatching a request
1069 '''register a function to run after dispatching a request
1067
1070
1068 Handlers do not stay registered across request boundaries.'''
1071 Handlers do not stay registered across request boundaries.'''
1069 self._exithandlers.append((func, args, kwargs))
1072 self._exithandlers.append((func, args, kwargs))
1070 return func
1073 return func
1071
1074
1072 def interface(self, feature):
1075 def interface(self, feature):
1073 """what interface to use for interactive console features?
1076 """what interface to use for interactive console features?
1074
1077
1075 The interface is controlled by the value of `ui.interface` but also by
1078 The interface is controlled by the value of `ui.interface` but also by
1076 the value of feature-specific configuration. For example:
1079 the value of feature-specific configuration. For example:
1077
1080
1078 ui.interface.histedit = text
1081 ui.interface.histedit = text
1079 ui.interface.chunkselector = curses
1082 ui.interface.chunkselector = curses
1080
1083
1081 Here the features are "histedit" and "chunkselector".
1084 Here the features are "histedit" and "chunkselector".
1082
1085
1083 The configuration above means that the default interfaces for commands
1086 The configuration above means that the default interfaces for commands
1084 is curses, the interface for histedit is text and the interface for
1087 is curses, the interface for histedit is text and the interface for
1085 selecting chunk is crecord (the best curses interface available).
1088 selecting chunk is crecord (the best curses interface available).
1086
1089
1087 Consider the following example:
1090 Consider the following example:
1088 ui.interface = curses
1091 ui.interface = curses
1089 ui.interface.histedit = text
1092 ui.interface.histedit = text
1090
1093
1091 Then histedit will use the text interface and chunkselector will use
1094 Then histedit will use the text interface and chunkselector will use
1092 the default curses interface (crecord at the moment).
1095 the default curses interface (crecord at the moment).
1093 """
1096 """
1094 alldefaults = frozenset(["text", "curses"])
1097 alldefaults = frozenset(["text", "curses"])
1095
1098
1096 featureinterfaces = {
1099 featureinterfaces = {
1097 "chunkselector": [
1100 "chunkselector": [
1098 "text",
1101 "text",
1099 "curses",
1102 "curses",
1100 ]
1103 ]
1101 }
1104 }
1102
1105
1103 # Feature-specific interface
1106 # Feature-specific interface
1104 if feature not in featureinterfaces.keys():
1107 if feature not in featureinterfaces.keys():
1105 # Programming error, not user error
1108 # Programming error, not user error
1106 raise ValueError("Unknown feature requested %s" % feature)
1109 raise ValueError("Unknown feature requested %s" % feature)
1107
1110
1108 availableinterfaces = frozenset(featureinterfaces[feature])
1111 availableinterfaces = frozenset(featureinterfaces[feature])
1109 if alldefaults > availableinterfaces:
1112 if alldefaults > availableinterfaces:
1110 # Programming error, not user error. We need a use case to
1113 # Programming error, not user error. We need a use case to
1111 # define the right thing to do here.
1114 # define the right thing to do here.
1112 raise ValueError(
1115 raise ValueError(
1113 "Feature %s does not handle all default interfaces" %
1116 "Feature %s does not handle all default interfaces" %
1114 feature)
1117 feature)
1115
1118
1116 if self.plain():
1119 if self.plain():
1117 return "text"
1120 return "text"
1118
1121
1119 # Default interface for all the features
1122 # Default interface for all the features
1120 defaultinterface = "text"
1123 defaultinterface = "text"
1121 i = self.config("ui", "interface")
1124 i = self.config("ui", "interface")
1122 if i in alldefaults:
1125 if i in alldefaults:
1123 defaultinterface = i
1126 defaultinterface = i
1124
1127
1125 choseninterface = defaultinterface
1128 choseninterface = defaultinterface
1126 f = self.config("ui", "interface.%s" % feature, None)
1129 f = self.config("ui", "interface.%s" % feature, None)
1127 if f in availableinterfaces:
1130 if f in availableinterfaces:
1128 choseninterface = f
1131 choseninterface = f
1129
1132
1130 if i is not None and defaultinterface != i:
1133 if i is not None and defaultinterface != i:
1131 if f is not None:
1134 if f is not None:
1132 self.warn(_("invalid value for ui.interface: %s\n") %
1135 self.warn(_("invalid value for ui.interface: %s\n") %
1133 (i,))
1136 (i,))
1134 else:
1137 else:
1135 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1138 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1136 (i, choseninterface))
1139 (i, choseninterface))
1137 if f is not None and choseninterface != f:
1140 if f is not None and choseninterface != f:
1138 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1141 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1139 (feature, f, choseninterface))
1142 (feature, f, choseninterface))
1140
1143
1141 return choseninterface
1144 return choseninterface
1142
1145
1143 def interactive(self):
1146 def interactive(self):
1144 '''is interactive input allowed?
1147 '''is interactive input allowed?
1145
1148
1146 An interactive session is a session where input can be reasonably read
1149 An interactive session is a session where input can be reasonably read
1147 from `sys.stdin'. If this function returns false, any attempt to read
1150 from `sys.stdin'. If this function returns false, any attempt to read
1148 from stdin should fail with an error, unless a sensible default has been
1151 from stdin should fail with an error, unless a sensible default has been
1149 specified.
1152 specified.
1150
1153
1151 Interactiveness is triggered by the value of the `ui.interactive'
1154 Interactiveness is triggered by the value of the `ui.interactive'
1152 configuration variable or - if it is unset - when `sys.stdin' points
1155 configuration variable or - if it is unset - when `sys.stdin' points
1153 to a terminal device.
1156 to a terminal device.
1154
1157
1155 This function refers to input only; for output, see `ui.formatted()'.
1158 This function refers to input only; for output, see `ui.formatted()'.
1156 '''
1159 '''
1157 i = self.configbool("ui", "interactive")
1160 i = self.configbool("ui", "interactive")
1158 if i is None:
1161 if i is None:
1159 # some environments replace stdin without implementing isatty
1162 # some environments replace stdin without implementing isatty
1160 # usually those are non-interactive
1163 # usually those are non-interactive
1161 return self._isatty(self.fin)
1164 return self._isatty(self.fin)
1162
1165
1163 return i
1166 return i
1164
1167
1165 def termwidth(self):
1168 def termwidth(self):
1166 '''how wide is the terminal in columns?
1169 '''how wide is the terminal in columns?
1167 '''
1170 '''
1168 if 'COLUMNS' in encoding.environ:
1171 if 'COLUMNS' in encoding.environ:
1169 try:
1172 try:
1170 return int(encoding.environ['COLUMNS'])
1173 return int(encoding.environ['COLUMNS'])
1171 except ValueError:
1174 except ValueError:
1172 pass
1175 pass
1173 return scmutil.termsize(self)[0]
1176 return scmutil.termsize(self)[0]
1174
1177
1175 def formatted(self):
1178 def formatted(self):
1176 '''should formatted output be used?
1179 '''should formatted output be used?
1177
1180
1178 It is often desirable to format the output to suite the output medium.
1181 It is often desirable to format the output to suite the output medium.
1179 Examples of this are truncating long lines or colorizing messages.
1182 Examples of this are truncating long lines or colorizing messages.
1180 However, this is not often not desirable when piping output into other
1183 However, this is not often not desirable when piping output into other
1181 utilities, e.g. `grep'.
1184 utilities, e.g. `grep'.
1182
1185
1183 Formatted output is triggered by the value of the `ui.formatted'
1186 Formatted output is triggered by the value of the `ui.formatted'
1184 configuration variable or - if it is unset - when `sys.stdout' points
1187 configuration variable or - if it is unset - when `sys.stdout' points
1185 to a terminal device. Please note that `ui.formatted' should be
1188 to a terminal device. Please note that `ui.formatted' should be
1186 considered an implementation detail; it is not intended for use outside
1189 considered an implementation detail; it is not intended for use outside
1187 Mercurial or its extensions.
1190 Mercurial or its extensions.
1188
1191
1189 This function refers to output only; for input, see `ui.interactive()'.
1192 This function refers to output only; for input, see `ui.interactive()'.
1190 This function always returns false when in plain mode, see `ui.plain()'.
1193 This function always returns false when in plain mode, see `ui.plain()'.
1191 '''
1194 '''
1192 if self.plain():
1195 if self.plain():
1193 return False
1196 return False
1194
1197
1195 i = self.configbool("ui", "formatted")
1198 i = self.configbool("ui", "formatted")
1196 if i is None:
1199 if i is None:
1197 # some environments replace stdout without implementing isatty
1200 # some environments replace stdout without implementing isatty
1198 # usually those are non-interactive
1201 # usually those are non-interactive
1199 return self._isatty(self.fout)
1202 return self._isatty(self.fout)
1200
1203
1201 return i
1204 return i
1202
1205
1203 def _readline(self, prompt=''):
1206 def _readline(self, prompt=''):
1204 if self._isatty(self.fin):
1207 if self._isatty(self.fin):
1205 try:
1208 try:
1206 # magically add command line editing support, where
1209 # magically add command line editing support, where
1207 # available
1210 # available
1208 import readline
1211 import readline
1209 # force demandimport to really load the module
1212 # force demandimport to really load the module
1210 readline.read_history_file
1213 readline.read_history_file
1211 # windows sometimes raises something other than ImportError
1214 # windows sometimes raises something other than ImportError
1212 except Exception:
1215 except Exception:
1213 pass
1216 pass
1214
1217
1215 # call write() so output goes through subclassed implementation
1218 # call write() so output goes through subclassed implementation
1216 # e.g. color extension on Windows
1219 # e.g. color extension on Windows
1217 self.write(prompt, prompt=True)
1220 self.write(prompt, prompt=True)
1218 self.flush()
1221 self.flush()
1219
1222
1220 # prompt ' ' must exist; otherwise readline may delete entire line
1223 # prompt ' ' must exist; otherwise readline may delete entire line
1221 # - http://bugs.python.org/issue12833
1224 # - http://bugs.python.org/issue12833
1222 with self.timeblockedsection('stdio'):
1225 with self.timeblockedsection('stdio'):
1223 line = util.bytesinput(self.fin, self.fout, r' ')
1226 line = util.bytesinput(self.fin, self.fout, r' ')
1224
1227
1225 # When stdin is in binary mode on Windows, it can cause
1228 # When stdin is in binary mode on Windows, it can cause
1226 # raw_input() to emit an extra trailing carriage return
1229 # raw_input() to emit an extra trailing carriage return
1227 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1230 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1228 line = line[:-1]
1231 line = line[:-1]
1229 return line
1232 return line
1230
1233
1231 def prompt(self, msg, default="y"):
1234 def prompt(self, msg, default="y"):
1232 """Prompt user with msg, read response.
1235 """Prompt user with msg, read response.
1233 If ui is not interactive, the default is returned.
1236 If ui is not interactive, the default is returned.
1234 """
1237 """
1235 if not self.interactive():
1238 if not self.interactive():
1236 self.write(msg, ' ', default or '', "\n")
1239 self.write(msg, ' ', default or '', "\n")
1237 return default
1240 return default
1238 try:
1241 try:
1239 r = self._readline(self.label(msg, 'ui.prompt'))
1242 r = self._readline(self.label(msg, 'ui.prompt'))
1240 if not r:
1243 if not r:
1241 r = default
1244 r = default
1242 if self.configbool('ui', 'promptecho'):
1245 if self.configbool('ui', 'promptecho'):
1243 self.write(r, "\n")
1246 self.write(r, "\n")
1244 return r
1247 return r
1245 except EOFError:
1248 except EOFError:
1246 raise error.ResponseExpected()
1249 raise error.ResponseExpected()
1247
1250
1248 @staticmethod
1251 @staticmethod
1249 def extractchoices(prompt):
1252 def extractchoices(prompt):
1250 """Extract prompt message and list of choices from specified prompt.
1253 """Extract prompt message and list of choices from specified prompt.
1251
1254
1252 This returns tuple "(message, choices)", and "choices" is the
1255 This returns tuple "(message, choices)", and "choices" is the
1253 list of tuple "(response character, text without &)".
1256 list of tuple "(response character, text without &)".
1254
1257
1255 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1258 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1256 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1259 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1257 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1260 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1258 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1261 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1259 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1262 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1260 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1263 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1261 """
1264 """
1262
1265
1263 # Sadly, the prompt string may have been built with a filename
1266 # Sadly, the prompt string may have been built with a filename
1264 # containing "$$" so let's try to find the first valid-looking
1267 # containing "$$" so let's try to find the first valid-looking
1265 # prompt to start parsing. Sadly, we also can't rely on
1268 # prompt to start parsing. Sadly, we also can't rely on
1266 # choices containing spaces, ASCII, or basically anything
1269 # choices containing spaces, ASCII, or basically anything
1267 # except an ampersand followed by a character.
1270 # except an ampersand followed by a character.
1268 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1271 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1269 msg = m.group(1)
1272 msg = m.group(1)
1270 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1273 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1271 def choicetuple(s):
1274 def choicetuple(s):
1272 ampidx = s.index('&')
1275 ampidx = s.index('&')
1273 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1276 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1274 return (msg, [choicetuple(s) for s in choices])
1277 return (msg, [choicetuple(s) for s in choices])
1275
1278
1276 def promptchoice(self, prompt, default=0):
1279 def promptchoice(self, prompt, default=0):
1277 """Prompt user with a message, read response, and ensure it matches
1280 """Prompt user with a message, read response, and ensure it matches
1278 one of the provided choices. The prompt is formatted as follows:
1281 one of the provided choices. The prompt is formatted as follows:
1279
1282
1280 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1283 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1281
1284
1282 The index of the choice is returned. Responses are case
1285 The index of the choice is returned. Responses are case
1283 insensitive. If ui is not interactive, the default is
1286 insensitive. If ui is not interactive, the default is
1284 returned.
1287 returned.
1285 """
1288 """
1286
1289
1287 msg, choices = self.extractchoices(prompt)
1290 msg, choices = self.extractchoices(prompt)
1288 resps = [r for r, t in choices]
1291 resps = [r for r, t in choices]
1289 while True:
1292 while True:
1290 r = self.prompt(msg, resps[default])
1293 r = self.prompt(msg, resps[default])
1291 if r.lower() in resps:
1294 if r.lower() in resps:
1292 return resps.index(r.lower())
1295 return resps.index(r.lower())
1293 self.write(_("unrecognized response\n"))
1296 self.write(_("unrecognized response\n"))
1294
1297
1295 def getpass(self, prompt=None, default=None):
1298 def getpass(self, prompt=None, default=None):
1296 if not self.interactive():
1299 if not self.interactive():
1297 return default
1300 return default
1298 try:
1301 try:
1299 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1302 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1300 # disable getpass() only if explicitly specified. it's still valid
1303 # disable getpass() only if explicitly specified. it's still valid
1301 # to interact with tty even if fin is not a tty.
1304 # to interact with tty even if fin is not a tty.
1302 with self.timeblockedsection('stdio'):
1305 with self.timeblockedsection('stdio'):
1303 if self.configbool('ui', 'nontty'):
1306 if self.configbool('ui', 'nontty'):
1304 l = self.fin.readline()
1307 l = self.fin.readline()
1305 if not l:
1308 if not l:
1306 raise EOFError
1309 raise EOFError
1307 return l.rstrip('\n')
1310 return l.rstrip('\n')
1308 else:
1311 else:
1309 return getpass.getpass('')
1312 return getpass.getpass('')
1310 except EOFError:
1313 except EOFError:
1311 raise error.ResponseExpected()
1314 raise error.ResponseExpected()
1312 def status(self, *msg, **opts):
1315 def status(self, *msg, **opts):
1313 '''write status message to output (if ui.quiet is False)
1316 '''write status message to output (if ui.quiet is False)
1314
1317
1315 This adds an output label of "ui.status".
1318 This adds an output label of "ui.status".
1316 '''
1319 '''
1317 if not self.quiet:
1320 if not self.quiet:
1318 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1321 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1319 self.write(*msg, **opts)
1322 self.write(*msg, **opts)
1320 def warn(self, *msg, **opts):
1323 def warn(self, *msg, **opts):
1321 '''write warning message to output (stderr)
1324 '''write warning message to output (stderr)
1322
1325
1323 This adds an output label of "ui.warning".
1326 This adds an output label of "ui.warning".
1324 '''
1327 '''
1325 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1328 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1326 self.write_err(*msg, **opts)
1329 self.write_err(*msg, **opts)
1327 def note(self, *msg, **opts):
1330 def note(self, *msg, **opts):
1328 '''write note to output (if ui.verbose is True)
1331 '''write note to output (if ui.verbose is True)
1329
1332
1330 This adds an output label of "ui.note".
1333 This adds an output label of "ui.note".
1331 '''
1334 '''
1332 if self.verbose:
1335 if self.verbose:
1333 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1336 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1334 self.write(*msg, **opts)
1337 self.write(*msg, **opts)
1335 def debug(self, *msg, **opts):
1338 def debug(self, *msg, **opts):
1336 '''write debug message to output (if ui.debugflag is True)
1339 '''write debug message to output (if ui.debugflag is True)
1337
1340
1338 This adds an output label of "ui.debug".
1341 This adds an output label of "ui.debug".
1339 '''
1342 '''
1340 if self.debugflag:
1343 if self.debugflag:
1341 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1344 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1342 self.write(*msg, **opts)
1345 self.write(*msg, **opts)
1343
1346
1344 def edit(self, text, user, extra=None, editform=None, pending=None,
1347 def edit(self, text, user, extra=None, editform=None, pending=None,
1345 repopath=None):
1348 repopath=None):
1346 extra_defaults = {
1349 extra_defaults = {
1347 'prefix': 'editor',
1350 'prefix': 'editor',
1348 'suffix': '.txt',
1351 'suffix': '.txt',
1349 }
1352 }
1350 if extra is not None:
1353 if extra is not None:
1351 extra_defaults.update(extra)
1354 extra_defaults.update(extra)
1352 extra = extra_defaults
1355 extra = extra_defaults
1353
1356
1354 rdir = None
1357 rdir = None
1355 if self.configbool('experimental', 'editortmpinhg'):
1358 if self.configbool('experimental', 'editortmpinhg'):
1356 rdir = repopath
1359 rdir = repopath
1357 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1360 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1358 suffix=extra['suffix'],
1361 suffix=extra['suffix'],
1359 dir=rdir)
1362 dir=rdir)
1360 try:
1363 try:
1361 f = os.fdopen(fd, r'wb')
1364 f = os.fdopen(fd, r'wb')
1362 f.write(util.tonativeeol(text))
1365 f.write(util.tonativeeol(text))
1363 f.close()
1366 f.close()
1364
1367
1365 environ = {'HGUSER': user}
1368 environ = {'HGUSER': user}
1366 if 'transplant_source' in extra:
1369 if 'transplant_source' in extra:
1367 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1370 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1368 for label in ('intermediate-source', 'source', 'rebase_source'):
1371 for label in ('intermediate-source', 'source', 'rebase_source'):
1369 if label in extra:
1372 if label in extra:
1370 environ.update({'HGREVISION': extra[label]})
1373 environ.update({'HGREVISION': extra[label]})
1371 break
1374 break
1372 if editform:
1375 if editform:
1373 environ.update({'HGEDITFORM': editform})
1376 environ.update({'HGEDITFORM': editform})
1374 if pending:
1377 if pending:
1375 environ.update({'HG_PENDING': pending})
1378 environ.update({'HG_PENDING': pending})
1376
1379
1377 editor = self.geteditor()
1380 editor = self.geteditor()
1378
1381
1379 self.system("%s \"%s\"" % (editor, name),
1382 self.system("%s \"%s\"" % (editor, name),
1380 environ=environ,
1383 environ=environ,
1381 onerr=error.Abort, errprefix=_("edit failed"),
1384 onerr=error.Abort, errprefix=_("edit failed"),
1382 blockedtag='editor')
1385 blockedtag='editor')
1383
1386
1384 f = open(name, r'rb')
1387 f = open(name, r'rb')
1385 t = util.fromnativeeol(f.read())
1388 t = util.fromnativeeol(f.read())
1386 f.close()
1389 f.close()
1387 finally:
1390 finally:
1388 os.unlink(name)
1391 os.unlink(name)
1389
1392
1390 return t
1393 return t
1391
1394
1392 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1395 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1393 blockedtag=None):
1396 blockedtag=None):
1394 '''execute shell command with appropriate output stream. command
1397 '''execute shell command with appropriate output stream. command
1395 output will be redirected if fout is not stdout.
1398 output will be redirected if fout is not stdout.
1396
1399
1397 if command fails and onerr is None, return status, else raise onerr
1400 if command fails and onerr is None, return status, else raise onerr
1398 object as exception.
1401 object as exception.
1399 '''
1402 '''
1400 if blockedtag is None:
1403 if blockedtag is None:
1401 # Long cmds tend to be because of an absolute path on cmd. Keep
1404 # Long cmds tend to be because of an absolute path on cmd. Keep
1402 # the tail end instead
1405 # the tail end instead
1403 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1406 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1404 blockedtag = 'unknown_system_' + cmdsuffix
1407 blockedtag = 'unknown_system_' + cmdsuffix
1405 out = self.fout
1408 out = self.fout
1406 if any(s[1] for s in self._bufferstates):
1409 if any(s[1] for s in self._bufferstates):
1407 out = self
1410 out = self
1408 with self.timeblockedsection(blockedtag):
1411 with self.timeblockedsection(blockedtag):
1409 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1412 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1410 if rc and onerr:
1413 if rc and onerr:
1411 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1414 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1412 util.explainexit(rc)[0])
1415 util.explainexit(rc)[0])
1413 if errprefix:
1416 if errprefix:
1414 errmsg = '%s: %s' % (errprefix, errmsg)
1417 errmsg = '%s: %s' % (errprefix, errmsg)
1415 raise onerr(errmsg)
1418 raise onerr(errmsg)
1416 return rc
1419 return rc
1417
1420
1418 def _runsystem(self, cmd, environ, cwd, out):
1421 def _runsystem(self, cmd, environ, cwd, out):
1419 """actually execute the given shell command (can be overridden by
1422 """actually execute the given shell command (can be overridden by
1420 extensions like chg)"""
1423 extensions like chg)"""
1421 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1424 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1422
1425
1423 def traceback(self, exc=None, force=False):
1426 def traceback(self, exc=None, force=False):
1424 '''print exception traceback if traceback printing enabled or forced.
1427 '''print exception traceback if traceback printing enabled or forced.
1425 only to call in exception handler. returns true if traceback
1428 only to call in exception handler. returns true if traceback
1426 printed.'''
1429 printed.'''
1427 if self.tracebackflag or force:
1430 if self.tracebackflag or force:
1428 if exc is None:
1431 if exc is None:
1429 exc = sys.exc_info()
1432 exc = sys.exc_info()
1430 cause = getattr(exc[1], 'cause', None)
1433 cause = getattr(exc[1], 'cause', None)
1431
1434
1432 if cause is not None:
1435 if cause is not None:
1433 causetb = traceback.format_tb(cause[2])
1436 causetb = traceback.format_tb(cause[2])
1434 exctb = traceback.format_tb(exc[2])
1437 exctb = traceback.format_tb(exc[2])
1435 exconly = traceback.format_exception_only(cause[0], cause[1])
1438 exconly = traceback.format_exception_only(cause[0], cause[1])
1436
1439
1437 # exclude frame where 'exc' was chained and rethrown from exctb
1440 # exclude frame where 'exc' was chained and rethrown from exctb
1438 self.write_err('Traceback (most recent call last):\n',
1441 self.write_err('Traceback (most recent call last):\n',
1439 ''.join(exctb[:-1]),
1442 ''.join(exctb[:-1]),
1440 ''.join(causetb),
1443 ''.join(causetb),
1441 ''.join(exconly))
1444 ''.join(exconly))
1442 else:
1445 else:
1443 output = traceback.format_exception(exc[0], exc[1], exc[2])
1446 output = traceback.format_exception(exc[0], exc[1], exc[2])
1444 data = r''.join(output)
1447 data = r''.join(output)
1445 if pycompat.ispy3:
1448 if pycompat.ispy3:
1446 enc = pycompat.sysstr(encoding.encoding)
1449 enc = pycompat.sysstr(encoding.encoding)
1447 data = data.encode(enc, errors=r'replace')
1450 data = data.encode(enc, errors=r'replace')
1448 self.write_err(data)
1451 self.write_err(data)
1449 return self.tracebackflag or force
1452 return self.tracebackflag or force
1450
1453
1451 def geteditor(self):
1454 def geteditor(self):
1452 '''return editor to use'''
1455 '''return editor to use'''
1453 if pycompat.sysplatform == 'plan9':
1456 if pycompat.sysplatform == 'plan9':
1454 # vi is the MIPS instruction simulator on Plan 9. We
1457 # vi is the MIPS instruction simulator on Plan 9. We
1455 # instead default to E to plumb commit messages to
1458 # instead default to E to plumb commit messages to
1456 # avoid confusion.
1459 # avoid confusion.
1457 editor = 'E'
1460 editor = 'E'
1458 else:
1461 else:
1459 editor = 'vi'
1462 editor = 'vi'
1460 return (encoding.environ.get("HGEDITOR") or
1463 return (encoding.environ.get("HGEDITOR") or
1461 self.config("ui", "editor", editor))
1464 self.config("ui", "editor", editor))
1462
1465
1463 @util.propertycache
1466 @util.propertycache
1464 def _progbar(self):
1467 def _progbar(self):
1465 """setup the progbar singleton to the ui object"""
1468 """setup the progbar singleton to the ui object"""
1466 if (self.quiet or self.debugflag
1469 if (self.quiet or self.debugflag
1467 or self.configbool('progress', 'disable')
1470 or self.configbool('progress', 'disable')
1468 or not progress.shouldprint(self)):
1471 or not progress.shouldprint(self)):
1469 return None
1472 return None
1470 return getprogbar(self)
1473 return getprogbar(self)
1471
1474
1472 def _progclear(self):
1475 def _progclear(self):
1473 """clear progress bar output if any. use it before any output"""
1476 """clear progress bar output if any. use it before any output"""
1474 if '_progbar' not in vars(self): # nothing loaded yet
1477 if '_progbar' not in vars(self): # nothing loaded yet
1475 return
1478 return
1476 if self._progbar is not None and self._progbar.printed:
1479 if self._progbar is not None and self._progbar.printed:
1477 self._progbar.clear()
1480 self._progbar.clear()
1478
1481
1479 def progress(self, topic, pos, item="", unit="", total=None):
1482 def progress(self, topic, pos, item="", unit="", total=None):
1480 '''show a progress message
1483 '''show a progress message
1481
1484
1482 By default a textual progress bar will be displayed if an operation
1485 By default a textual progress bar will be displayed if an operation
1483 takes too long. 'topic' is the current operation, 'item' is a
1486 takes too long. 'topic' is the current operation, 'item' is a
1484 non-numeric marker of the current position (i.e. the currently
1487 non-numeric marker of the current position (i.e. the currently
1485 in-process file), 'pos' is the current numeric position (i.e.
1488 in-process file), 'pos' is the current numeric position (i.e.
1486 revision, bytes, etc.), unit is a corresponding unit label,
1489 revision, bytes, etc.), unit is a corresponding unit label,
1487 and total is the highest expected pos.
1490 and total is the highest expected pos.
1488
1491
1489 Multiple nested topics may be active at a time.
1492 Multiple nested topics may be active at a time.
1490
1493
1491 All topics should be marked closed by setting pos to None at
1494 All topics should be marked closed by setting pos to None at
1492 termination.
1495 termination.
1493 '''
1496 '''
1494 if self._progbar is not None:
1497 if self._progbar is not None:
1495 self._progbar.progress(topic, pos, item=item, unit=unit,
1498 self._progbar.progress(topic, pos, item=item, unit=unit,
1496 total=total)
1499 total=total)
1497 if pos is None or not self.configbool('progress', 'debug'):
1500 if pos is None or not self.configbool('progress', 'debug'):
1498 return
1501 return
1499
1502
1500 if unit:
1503 if unit:
1501 unit = ' ' + unit
1504 unit = ' ' + unit
1502 if item:
1505 if item:
1503 item = ' ' + item
1506 item = ' ' + item
1504
1507
1505 if total:
1508 if total:
1506 pct = 100.0 * pos / total
1509 pct = 100.0 * pos / total
1507 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1510 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1508 % (topic, item, pos, total, unit, pct))
1511 % (topic, item, pos, total, unit, pct))
1509 else:
1512 else:
1510 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1513 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1511
1514
1512 def log(self, service, *msg, **opts):
1515 def log(self, service, *msg, **opts):
1513 '''hook for logging facility extensions
1516 '''hook for logging facility extensions
1514
1517
1515 service should be a readily-identifiable subsystem, which will
1518 service should be a readily-identifiable subsystem, which will
1516 allow filtering.
1519 allow filtering.
1517
1520
1518 *msg should be a newline-terminated format string to log, and
1521 *msg should be a newline-terminated format string to log, and
1519 then any values to %-format into that format string.
1522 then any values to %-format into that format string.
1520
1523
1521 **opts currently has no defined meanings.
1524 **opts currently has no defined meanings.
1522 '''
1525 '''
1523
1526
1524 def label(self, msg, label):
1527 def label(self, msg, label):
1525 '''style msg based on supplied label
1528 '''style msg based on supplied label
1526
1529
1527 If some color mode is enabled, this will add the necessary control
1530 If some color mode is enabled, this will add the necessary control
1528 characters to apply such color. In addition, 'debug' color mode adds
1531 characters to apply such color. In addition, 'debug' color mode adds
1529 markup showing which label affects a piece of text.
1532 markup showing which label affects a piece of text.
1530
1533
1531 ui.write(s, 'label') is equivalent to
1534 ui.write(s, 'label') is equivalent to
1532 ui.write(ui.label(s, 'label')).
1535 ui.write(ui.label(s, 'label')).
1533 '''
1536 '''
1534 if self._colormode is not None:
1537 if self._colormode is not None:
1535 return color.colorlabel(self, msg, label)
1538 return color.colorlabel(self, msg, label)
1536 return msg
1539 return msg
1537
1540
1538 def develwarn(self, msg, stacklevel=1, config=None):
1541 def develwarn(self, msg, stacklevel=1, config=None):
1539 """issue a developer warning message
1542 """issue a developer warning message
1540
1543
1541 Use 'stacklevel' to report the offender some layers further up in the
1544 Use 'stacklevel' to report the offender some layers further up in the
1542 stack.
1545 stack.
1543 """
1546 """
1544 if not self.configbool('devel', 'all-warnings'):
1547 if not self.configbool('devel', 'all-warnings'):
1545 if config is not None and not self.configbool('devel', config):
1548 if config is not None and not self.configbool('devel', config):
1546 return
1549 return
1547 msg = 'devel-warn: ' + msg
1550 msg = 'devel-warn: ' + msg
1548 stacklevel += 1 # get in develwarn
1551 stacklevel += 1 # get in develwarn
1549 if self.tracebackflag:
1552 if self.tracebackflag:
1550 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1553 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1551 self.log('develwarn', '%s at:\n%s' %
1554 self.log('develwarn', '%s at:\n%s' %
1552 (msg, ''.join(util.getstackframes(stacklevel))))
1555 (msg, ''.join(util.getstackframes(stacklevel))))
1553 else:
1556 else:
1554 curframe = inspect.currentframe()
1557 curframe = inspect.currentframe()
1555 calframe = inspect.getouterframes(curframe, 2)
1558 calframe = inspect.getouterframes(curframe, 2)
1556 self.write_err('%s at: %s:%s (%s)\n'
1559 self.write_err('%s at: %s:%s (%s)\n'
1557 % ((msg,) + calframe[stacklevel][1:4]))
1560 % ((msg,) + calframe[stacklevel][1:4]))
1558 self.log('develwarn', '%s at: %s:%s (%s)\n',
1561 self.log('develwarn', '%s at: %s:%s (%s)\n',
1559 msg, *calframe[stacklevel][1:4])
1562 msg, *calframe[stacklevel][1:4])
1560 curframe = calframe = None # avoid cycles
1563 curframe = calframe = None # avoid cycles
1561
1564
1562 def deprecwarn(self, msg, version):
1565 def deprecwarn(self, msg, version):
1563 """issue a deprecation warning
1566 """issue a deprecation warning
1564
1567
1565 - msg: message explaining what is deprecated and how to upgrade,
1568 - msg: message explaining what is deprecated and how to upgrade,
1566 - version: last version where the API will be supported,
1569 - version: last version where the API will be supported,
1567 """
1570 """
1568 if not (self.configbool('devel', 'all-warnings')
1571 if not (self.configbool('devel', 'all-warnings')
1569 or self.configbool('devel', 'deprec-warn')):
1572 or self.configbool('devel', 'deprec-warn')):
1570 return
1573 return
1571 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1574 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1572 " update your code.)") % version
1575 " update your code.)") % version
1573 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1576 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1574
1577
1575 def exportableenviron(self):
1578 def exportableenviron(self):
1576 """The environment variables that are safe to export, e.g. through
1579 """The environment variables that are safe to export, e.g. through
1577 hgweb.
1580 hgweb.
1578 """
1581 """
1579 return self._exportableenviron
1582 return self._exportableenviron
1580
1583
1581 @contextlib.contextmanager
1584 @contextlib.contextmanager
1582 def configoverride(self, overrides, source=""):
1585 def configoverride(self, overrides, source=""):
1583 """Context manager for temporary config overrides
1586 """Context manager for temporary config overrides
1584 `overrides` must be a dict of the following structure:
1587 `overrides` must be a dict of the following structure:
1585 {(section, name) : value}"""
1588 {(section, name) : value}"""
1586 backups = {}
1589 backups = {}
1587 try:
1590 try:
1588 for (section, name), value in overrides.items():
1591 for (section, name), value in overrides.items():
1589 backups[(section, name)] = self.backupconfig(section, name)
1592 backups[(section, name)] = self.backupconfig(section, name)
1590 self.setconfig(section, name, value, source)
1593 self.setconfig(section, name, value, source)
1591 yield
1594 yield
1592 finally:
1595 finally:
1593 for __, backup in backups.items():
1596 for __, backup in backups.items():
1594 self.restoreconfig(backup)
1597 self.restoreconfig(backup)
1595 # just restoring ui.quiet config to the previous value is not enough
1598 # just restoring ui.quiet config to the previous value is not enough
1596 # as it does not update ui.quiet class member
1599 # as it does not update ui.quiet class member
1597 if ('ui', 'quiet') in overrides:
1600 if ('ui', 'quiet') in overrides:
1598 self.fixconfig(section='ui')
1601 self.fixconfig(section='ui')
1599
1602
1600 class paths(dict):
1603 class paths(dict):
1601 """Represents a collection of paths and their configs.
1604 """Represents a collection of paths and their configs.
1602
1605
1603 Data is initially derived from ui instances and the config files they have
1606 Data is initially derived from ui instances and the config files they have
1604 loaded.
1607 loaded.
1605 """
1608 """
1606 def __init__(self, ui):
1609 def __init__(self, ui):
1607 dict.__init__(self)
1610 dict.__init__(self)
1608
1611
1609 for name, loc in ui.configitems('paths', ignoresub=True):
1612 for name, loc in ui.configitems('paths', ignoresub=True):
1610 # No location is the same as not existing.
1613 # No location is the same as not existing.
1611 if not loc:
1614 if not loc:
1612 continue
1615 continue
1613 loc, sub = ui.configsuboptions('paths', name)
1616 loc, sub = ui.configsuboptions('paths', name)
1614 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1617 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1615
1618
1616 def getpath(self, name, default=None):
1619 def getpath(self, name, default=None):
1617 """Return a ``path`` from a string, falling back to default.
1620 """Return a ``path`` from a string, falling back to default.
1618
1621
1619 ``name`` can be a named path or locations. Locations are filesystem
1622 ``name`` can be a named path or locations. Locations are filesystem
1620 paths or URIs.
1623 paths or URIs.
1621
1624
1622 Returns None if ``name`` is not a registered path, a URI, or a local
1625 Returns None if ``name`` is not a registered path, a URI, or a local
1623 path to a repo.
1626 path to a repo.
1624 """
1627 """
1625 # Only fall back to default if no path was requested.
1628 # Only fall back to default if no path was requested.
1626 if name is None:
1629 if name is None:
1627 if not default:
1630 if not default:
1628 default = ()
1631 default = ()
1629 elif not isinstance(default, (tuple, list)):
1632 elif not isinstance(default, (tuple, list)):
1630 default = (default,)
1633 default = (default,)
1631 for k in default:
1634 for k in default:
1632 try:
1635 try:
1633 return self[k]
1636 return self[k]
1634 except KeyError:
1637 except KeyError:
1635 continue
1638 continue
1636 return None
1639 return None
1637
1640
1638 # Most likely empty string.
1641 # Most likely empty string.
1639 # This may need to raise in the future.
1642 # This may need to raise in the future.
1640 if not name:
1643 if not name:
1641 return None
1644 return None
1642
1645
1643 try:
1646 try:
1644 return self[name]
1647 return self[name]
1645 except KeyError:
1648 except KeyError:
1646 # Try to resolve as a local path or URI.
1649 # Try to resolve as a local path or URI.
1647 try:
1650 try:
1648 # We don't pass sub-options in, so no need to pass ui instance.
1651 # We don't pass sub-options in, so no need to pass ui instance.
1649 return path(None, None, rawloc=name)
1652 return path(None, None, rawloc=name)
1650 except ValueError:
1653 except ValueError:
1651 raise error.RepoError(_('repository %s does not exist') %
1654 raise error.RepoError(_('repository %s does not exist') %
1652 name)
1655 name)
1653
1656
1654 _pathsuboptions = {}
1657 _pathsuboptions = {}
1655
1658
1656 def pathsuboption(option, attr):
1659 def pathsuboption(option, attr):
1657 """Decorator used to declare a path sub-option.
1660 """Decorator used to declare a path sub-option.
1658
1661
1659 Arguments are the sub-option name and the attribute it should set on
1662 Arguments are the sub-option name and the attribute it should set on
1660 ``path`` instances.
1663 ``path`` instances.
1661
1664
1662 The decorated function will receive as arguments a ``ui`` instance,
1665 The decorated function will receive as arguments a ``ui`` instance,
1663 ``path`` instance, and the string value of this option from the config.
1666 ``path`` instance, and the string value of this option from the config.
1664 The function should return the value that will be set on the ``path``
1667 The function should return the value that will be set on the ``path``
1665 instance.
1668 instance.
1666
1669
1667 This decorator can be used to perform additional verification of
1670 This decorator can be used to perform additional verification of
1668 sub-options and to change the type of sub-options.
1671 sub-options and to change the type of sub-options.
1669 """
1672 """
1670 def register(func):
1673 def register(func):
1671 _pathsuboptions[option] = (attr, func)
1674 _pathsuboptions[option] = (attr, func)
1672 return func
1675 return func
1673 return register
1676 return register
1674
1677
1675 @pathsuboption('pushurl', 'pushloc')
1678 @pathsuboption('pushurl', 'pushloc')
1676 def pushurlpathoption(ui, path, value):
1679 def pushurlpathoption(ui, path, value):
1677 u = util.url(value)
1680 u = util.url(value)
1678 # Actually require a URL.
1681 # Actually require a URL.
1679 if not u.scheme:
1682 if not u.scheme:
1680 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1683 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1681 return None
1684 return None
1682
1685
1683 # Don't support the #foo syntax in the push URL to declare branch to
1686 # Don't support the #foo syntax in the push URL to declare branch to
1684 # push.
1687 # push.
1685 if u.fragment:
1688 if u.fragment:
1686 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1689 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1687 'ignoring)\n') % path.name)
1690 'ignoring)\n') % path.name)
1688 u.fragment = None
1691 u.fragment = None
1689
1692
1690 return str(u)
1693 return str(u)
1691
1694
1692 @pathsuboption('pushrev', 'pushrev')
1695 @pathsuboption('pushrev', 'pushrev')
1693 def pushrevpathoption(ui, path, value):
1696 def pushrevpathoption(ui, path, value):
1694 return value
1697 return value
1695
1698
1696 class path(object):
1699 class path(object):
1697 """Represents an individual path and its configuration."""
1700 """Represents an individual path and its configuration."""
1698
1701
1699 def __init__(self, ui, name, rawloc=None, suboptions=None):
1702 def __init__(self, ui, name, rawloc=None, suboptions=None):
1700 """Construct a path from its config options.
1703 """Construct a path from its config options.
1701
1704
1702 ``ui`` is the ``ui`` instance the path is coming from.
1705 ``ui`` is the ``ui`` instance the path is coming from.
1703 ``name`` is the symbolic name of the path.
1706 ``name`` is the symbolic name of the path.
1704 ``rawloc`` is the raw location, as defined in the config.
1707 ``rawloc`` is the raw location, as defined in the config.
1705 ``pushloc`` is the raw locations pushes should be made to.
1708 ``pushloc`` is the raw locations pushes should be made to.
1706
1709
1707 If ``name`` is not defined, we require that the location be a) a local
1710 If ``name`` is not defined, we require that the location be a) a local
1708 filesystem path with a .hg directory or b) a URL. If not,
1711 filesystem path with a .hg directory or b) a URL. If not,
1709 ``ValueError`` is raised.
1712 ``ValueError`` is raised.
1710 """
1713 """
1711 if not rawloc:
1714 if not rawloc:
1712 raise ValueError('rawloc must be defined')
1715 raise ValueError('rawloc must be defined')
1713
1716
1714 # Locations may define branches via syntax <base>#<branch>.
1717 # Locations may define branches via syntax <base>#<branch>.
1715 u = util.url(rawloc)
1718 u = util.url(rawloc)
1716 branch = None
1719 branch = None
1717 if u.fragment:
1720 if u.fragment:
1718 branch = u.fragment
1721 branch = u.fragment
1719 u.fragment = None
1722 u.fragment = None
1720
1723
1721 self.url = u
1724 self.url = u
1722 self.branch = branch
1725 self.branch = branch
1723
1726
1724 self.name = name
1727 self.name = name
1725 self.rawloc = rawloc
1728 self.rawloc = rawloc
1726 self.loc = '%s' % u
1729 self.loc = '%s' % u
1727
1730
1728 # When given a raw location but not a symbolic name, validate the
1731 # When given a raw location but not a symbolic name, validate the
1729 # location is valid.
1732 # location is valid.
1730 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1733 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1731 raise ValueError('location is not a URL or path to a local '
1734 raise ValueError('location is not a URL or path to a local '
1732 'repo: %s' % rawloc)
1735 'repo: %s' % rawloc)
1733
1736
1734 suboptions = suboptions or {}
1737 suboptions = suboptions or {}
1735
1738
1736 # Now process the sub-options. If a sub-option is registered, its
1739 # Now process the sub-options. If a sub-option is registered, its
1737 # attribute will always be present. The value will be None if there
1740 # attribute will always be present. The value will be None if there
1738 # was no valid sub-option.
1741 # was no valid sub-option.
1739 for suboption, (attr, func) in _pathsuboptions.iteritems():
1742 for suboption, (attr, func) in _pathsuboptions.iteritems():
1740 if suboption not in suboptions:
1743 if suboption not in suboptions:
1741 setattr(self, attr, None)
1744 setattr(self, attr, None)
1742 continue
1745 continue
1743
1746
1744 value = func(ui, self, suboptions[suboption])
1747 value = func(ui, self, suboptions[suboption])
1745 setattr(self, attr, value)
1748 setattr(self, attr, value)
1746
1749
1747 def _isvalidlocalpath(self, path):
1750 def _isvalidlocalpath(self, path):
1748 """Returns True if the given path is a potentially valid repository.
1751 """Returns True if the given path is a potentially valid repository.
1749 This is its own function so that extensions can change the definition of
1752 This is its own function so that extensions can change the definition of
1750 'valid' in this case (like when pulling from a git repo into a hg
1753 'valid' in this case (like when pulling from a git repo into a hg
1751 one)."""
1754 one)."""
1752 return os.path.isdir(os.path.join(path, '.hg'))
1755 return os.path.isdir(os.path.join(path, '.hg'))
1753
1756
1754 @property
1757 @property
1755 def suboptions(self):
1758 def suboptions(self):
1756 """Return sub-options and their values for this path.
1759 """Return sub-options and their values for this path.
1757
1760
1758 This is intended to be used for presentation purposes.
1761 This is intended to be used for presentation purposes.
1759 """
1762 """
1760 d = {}
1763 d = {}
1761 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1764 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1762 value = getattr(self, attr)
1765 value = getattr(self, attr)
1763 if value is not None:
1766 if value is not None:
1764 d[subopt] = value
1767 d[subopt] = value
1765 return d
1768 return d
1766
1769
1767 # we instantiate one globally shared progress bar to avoid
1770 # we instantiate one globally shared progress bar to avoid
1768 # competing progress bars when multiple UI objects get created
1771 # competing progress bars when multiple UI objects get created
1769 _progresssingleton = None
1772 _progresssingleton = None
1770
1773
1771 def getprogbar(ui):
1774 def getprogbar(ui):
1772 global _progresssingleton
1775 global _progresssingleton
1773 if _progresssingleton is None:
1776 if _progresssingleton is None:
1774 # passing 'ui' object to the singleton is fishy,
1777 # passing 'ui' object to the singleton is fishy,
1775 # this is how the extension used to work but feel free to rework it.
1778 # this is how the extension used to work but feel free to rework it.
1776 _progresssingleton = progress.progbar(ui)
1779 _progresssingleton = progress.progbar(ui)
1777 return _progresssingleton
1780 return _progresssingleton
@@ -1,953 +1,953 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
6
7 import os
7 import os
8
8
9 supportedpy = '~= 2.7'
9 supportedpy = '~= 2.7'
10 if os.environ.get('HGALLOWPYTHON3', ''):
10 if os.environ.get('HGALLOWPYTHON3', ''):
11 # Mercurial will never work on Python 3 before 3.5 due to a lack
11 # Mercurial will never work on Python 3 before 3.5 due to a lack
12 # of % formatting on bytestrings, and can't work on 3.6.0 or 3.6.1
12 # of % formatting on bytestrings, and can't work on 3.6.0 or 3.6.1
13 # due to a bug in % formatting in bytestrings.
13 # due to a bug in % formatting in bytestrings.
14 #
14 #
15 # TODO: when we actually work on Python 3, use this string as the
15 # TODO: when we actually work on Python 3, use this string as the
16 # actual supportedpy string.
16 # actual supportedpy string.
17 supportedpy = ','.join([
17 supportedpy = ','.join([
18 '>=2.7',
18 '>=2.7',
19 '!=3.0.*',
19 '!=3.0.*',
20 '!=3.1.*',
20 '!=3.1.*',
21 '!=3.2.*',
21 '!=3.2.*',
22 '!=3.3.*',
22 '!=3.3.*',
23 '!=3.4.*',
23 '!=3.4.*',
24 '!=3.6.0',
24 '!=3.6.0',
25 '!=3.6.1',
25 '!=3.6.1',
26 ])
26 ])
27
27
28 import sys, platform
28 import sys, platform
29 if sys.version_info[0] >= 3:
29 if sys.version_info[0] >= 3:
30 printf = eval('print')
30 printf = eval('print')
31 libdir_escape = 'unicode_escape'
31 libdir_escape = 'unicode_escape'
32 else:
32 else:
33 libdir_escape = 'string_escape'
33 libdir_escape = 'string_escape'
34 def printf(*args, **kwargs):
34 def printf(*args, **kwargs):
35 f = kwargs.get('file', sys.stdout)
35 f = kwargs.get('file', sys.stdout)
36 end = kwargs.get('end', '\n')
36 end = kwargs.get('end', '\n')
37 f.write(b' '.join(args) + end)
37 f.write(b' '.join(args) + end)
38
38
39 # Attempt to guide users to a modern pip - this means that 2.6 users
39 # Attempt to guide users to a modern pip - this means that 2.6 users
40 # should have a chance of getting a 4.2 release, and when we ratchet
40 # should have a chance of getting a 4.2 release, and when we ratchet
41 # the version requirement forward again hopefully everyone will get
41 # the version requirement forward again hopefully everyone will get
42 # something that works for them.
42 # something that works for them.
43 if sys.version_info < (2, 7, 0, 'final'):
43 if sys.version_info < (2, 7, 0, 'final'):
44 pip_message = ('This may be due to an out of date pip. '
44 pip_message = ('This may be due to an out of date pip. '
45 'Make sure you have pip >= 9.0.1.')
45 'Make sure you have pip >= 9.0.1.')
46 try:
46 try:
47 import pip
47 import pip
48 pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
48 pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
49 if pip_version < (9, 0, 1) :
49 if pip_version < (9, 0, 1) :
50 pip_message = (
50 pip_message = (
51 'Your pip version is out of date, please install '
51 'Your pip version is out of date, please install '
52 'pip >= 9.0.1. pip {} detected.'.format(pip.__version__))
52 'pip >= 9.0.1. pip {} detected.'.format(pip.__version__))
53 else:
53 else:
54 # pip is new enough - it must be something else
54 # pip is new enough - it must be something else
55 pip_message = ''
55 pip_message = ''
56 except Exception:
56 except Exception:
57 pass
57 pass
58 error = """
58 error = """
59 Mercurial does not support Python older than 2.7.
59 Mercurial does not support Python older than 2.7.
60 Python {py} detected.
60 Python {py} detected.
61 {pip}
61 {pip}
62 """.format(py=sys.version_info, pip=pip_message)
62 """.format(py=sys.version_info, pip=pip_message)
63 printf(error, file=sys.stderr)
63 printf(error, file=sys.stderr)
64 sys.exit(1)
64 sys.exit(1)
65
65
66 # Solaris Python packaging brain damage
66 # Solaris Python packaging brain damage
67 try:
67 try:
68 import hashlib
68 import hashlib
69 sha = hashlib.sha1()
69 sha = hashlib.sha1()
70 except ImportError:
70 except ImportError:
71 try:
71 try:
72 import sha
72 import sha
73 sha.sha # silence unused import warning
73 sha.sha # silence unused import warning
74 except ImportError:
74 except ImportError:
75 raise SystemExit(
75 raise SystemExit(
76 "Couldn't import standard hashlib (incomplete Python install).")
76 "Couldn't import standard hashlib (incomplete Python install).")
77
77
78 try:
78 try:
79 import zlib
79 import zlib
80 zlib.compressobj # silence unused import warning
80 zlib.compressobj # silence unused import warning
81 except ImportError:
81 except ImportError:
82 raise SystemExit(
82 raise SystemExit(
83 "Couldn't import standard zlib (incomplete Python install).")
83 "Couldn't import standard zlib (incomplete Python install).")
84
84
85 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
85 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
86 isironpython = False
86 isironpython = False
87 try:
87 try:
88 isironpython = (platform.python_implementation()
88 isironpython = (platform.python_implementation()
89 .lower().find("ironpython") != -1)
89 .lower().find("ironpython") != -1)
90 except AttributeError:
90 except AttributeError:
91 pass
91 pass
92
92
93 if isironpython:
93 if isironpython:
94 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
94 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
95 else:
95 else:
96 try:
96 try:
97 import bz2
97 import bz2
98 bz2.BZ2Compressor # silence unused import warning
98 bz2.BZ2Compressor # silence unused import warning
99 except ImportError:
99 except ImportError:
100 raise SystemExit(
100 raise SystemExit(
101 "Couldn't import standard bz2 (incomplete Python install).")
101 "Couldn't import standard bz2 (incomplete Python install).")
102
102
103 ispypy = "PyPy" in sys.version
103 ispypy = "PyPy" in sys.version
104
104
105 import ctypes
105 import ctypes
106 import stat, subprocess, time
106 import stat, subprocess, time
107 import re
107 import re
108 import shutil
108 import shutil
109 import tempfile
109 import tempfile
110 from distutils import log
110 from distutils import log
111 # We have issues with setuptools on some platforms and builders. Until
111 # We have issues with setuptools on some platforms and builders. Until
112 # those are resolved, setuptools is opt-in except for platforms where
112 # those are resolved, setuptools is opt-in except for platforms where
113 # we don't have issues.
113 # we don't have issues.
114 issetuptools = (os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ)
114 issetuptools = (os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ)
115 if issetuptools:
115 if issetuptools:
116 from setuptools import setup
116 from setuptools import setup
117 else:
117 else:
118 from distutils.core import setup
118 from distutils.core import setup
119 from distutils.ccompiler import new_compiler
119 from distutils.ccompiler import new_compiler
120 from distutils.core import Command, Extension
120 from distutils.core import Command, Extension
121 from distutils.dist import Distribution
121 from distutils.dist import Distribution
122 from distutils.command.build import build
122 from distutils.command.build import build
123 from distutils.command.build_ext import build_ext
123 from distutils.command.build_ext import build_ext
124 from distutils.command.build_py import build_py
124 from distutils.command.build_py import build_py
125 from distutils.command.build_scripts import build_scripts
125 from distutils.command.build_scripts import build_scripts
126 from distutils.command.install import install
126 from distutils.command.install import install
127 from distutils.command.install_lib import install_lib
127 from distutils.command.install_lib import install_lib
128 from distutils.command.install_scripts import install_scripts
128 from distutils.command.install_scripts import install_scripts
129 from distutils.spawn import spawn, find_executable
129 from distutils.spawn import spawn, find_executable
130 from distutils import file_util
130 from distutils import file_util
131 from distutils.errors import (
131 from distutils.errors import (
132 CCompilerError,
132 CCompilerError,
133 DistutilsError,
133 DistutilsError,
134 DistutilsExecError,
134 DistutilsExecError,
135 )
135 )
136 from distutils.sysconfig import get_python_inc, get_config_var
136 from distutils.sysconfig import get_python_inc, get_config_var
137 from distutils.version import StrictVersion
137 from distutils.version import StrictVersion
138
138
139 scripts = ['hg']
139 scripts = ['hg']
140 if os.name == 'nt':
140 if os.name == 'nt':
141 # We remove hg.bat if we are able to build hg.exe.
141 # We remove hg.bat if we are able to build hg.exe.
142 scripts.append('contrib/win32/hg.bat')
142 scripts.append('contrib/win32/hg.bat')
143
143
144 def cancompile(cc, code):
144 def cancompile(cc, code):
145 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
145 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
146 devnull = oldstderr = None
146 devnull = oldstderr = None
147 try:
147 try:
148 fname = os.path.join(tmpdir, 'testcomp.c')
148 fname = os.path.join(tmpdir, 'testcomp.c')
149 f = open(fname, 'w')
149 f = open(fname, 'w')
150 f.write(code)
150 f.write(code)
151 f.close()
151 f.close()
152 # Redirect stderr to /dev/null to hide any error messages
152 # Redirect stderr to /dev/null to hide any error messages
153 # from the compiler.
153 # from the compiler.
154 # This will have to be changed if we ever have to check
154 # This will have to be changed if we ever have to check
155 # for a function on Windows.
155 # for a function on Windows.
156 devnull = open('/dev/null', 'w')
156 devnull = open('/dev/null', 'w')
157 oldstderr = os.dup(sys.stderr.fileno())
157 oldstderr = os.dup(sys.stderr.fileno())
158 os.dup2(devnull.fileno(), sys.stderr.fileno())
158 os.dup2(devnull.fileno(), sys.stderr.fileno())
159 objects = cc.compile([fname], output_dir=tmpdir)
159 objects = cc.compile([fname], output_dir=tmpdir)
160 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
160 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
161 return True
161 return True
162 except Exception:
162 except Exception:
163 return False
163 return False
164 finally:
164 finally:
165 if oldstderr is not None:
165 if oldstderr is not None:
166 os.dup2(oldstderr, sys.stderr.fileno())
166 os.dup2(oldstderr, sys.stderr.fileno())
167 if devnull is not None:
167 if devnull is not None:
168 devnull.close()
168 devnull.close()
169 shutil.rmtree(tmpdir)
169 shutil.rmtree(tmpdir)
170
170
171 # simplified version of distutils.ccompiler.CCompiler.has_function
171 # simplified version of distutils.ccompiler.CCompiler.has_function
172 # that actually removes its temporary files.
172 # that actually removes its temporary files.
173 def hasfunction(cc, funcname):
173 def hasfunction(cc, funcname):
174 code = 'int main(void) { %s(); }\n' % funcname
174 code = 'int main(void) { %s(); }\n' % funcname
175 return cancompile(cc, code)
175 return cancompile(cc, code)
176
176
177 def hasheader(cc, headername):
177 def hasheader(cc, headername):
178 code = '#include <%s>\nint main(void) { return 0; }\n' % headername
178 code = '#include <%s>\nint main(void) { return 0; }\n' % headername
179 return cancompile(cc, code)
179 return cancompile(cc, code)
180
180
181 # py2exe needs to be installed to work
181 # py2exe needs to be installed to work
182 try:
182 try:
183 import py2exe
183 import py2exe
184 py2exe.Distribution # silence unused import warning
184 py2exe.Distribution # silence unused import warning
185 py2exeloaded = True
185 py2exeloaded = True
186 # import py2exe's patched Distribution class
186 # import py2exe's patched Distribution class
187 from distutils.core import Distribution
187 from distutils.core import Distribution
188 except ImportError:
188 except ImportError:
189 py2exeloaded = False
189 py2exeloaded = False
190
190
191 def runcmd(cmd, env):
191 def runcmd(cmd, env):
192 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
192 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
193 stderr=subprocess.PIPE, env=env)
193 stderr=subprocess.PIPE, env=env)
194 out, err = p.communicate()
194 out, err = p.communicate()
195 return p.returncode, out, err
195 return p.returncode, out, err
196
196
197 class hgcommand(object):
197 class hgcommand(object):
198 def __init__(self, cmd, env):
198 def __init__(self, cmd, env):
199 self.cmd = cmd
199 self.cmd = cmd
200 self.env = env
200 self.env = env
201
201
202 def run(self, args):
202 def run(self, args):
203 cmd = self.cmd + args
203 cmd = self.cmd + args
204 returncode, out, err = runcmd(cmd, self.env)
204 returncode, out, err = runcmd(cmd, self.env)
205 err = filterhgerr(err)
205 err = filterhgerr(err)
206 if err or returncode != 0:
206 if err or returncode != 0:
207 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
207 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
208 printf(err, file=sys.stderr)
208 printf(err, file=sys.stderr)
209 return ''
209 return ''
210 return out
210 return out
211
211
212 def filterhgerr(err):
212 def filterhgerr(err):
213 # If root is executing setup.py, but the repository is owned by
213 # If root is executing setup.py, but the repository is owned by
214 # another user (as in "sudo python setup.py install") we will get
214 # another user (as in "sudo python setup.py install") we will get
215 # trust warnings since the .hg/hgrc file is untrusted. That is
215 # trust warnings since the .hg/hgrc file is untrusted. That is
216 # fine, we don't want to load it anyway. Python may warn about
216 # fine, we don't want to load it anyway. Python may warn about
217 # a missing __init__.py in mercurial/locale, we also ignore that.
217 # a missing __init__.py in mercurial/locale, we also ignore that.
218 err = [e for e in err.splitlines()
218 err = [e for e in err.splitlines()
219 if (not e.startswith(b'not trusting file')
219 if (not e.startswith(b'not trusting file')
220 and not e.startswith(b'warning: Not importing')
220 and not e.startswith(b'warning: Not importing')
221 and not e.startswith(b'obsolete feature not enabled'))]
221 and not e.startswith(b'obsolete feature not enabled'))]
222 return b'\n'.join(b' ' + e for e in err)
222 return b'\n'.join(b' ' + e for e in err)
223
223
224 def findhg():
224 def findhg():
225 """Try to figure out how we should invoke hg for examining the local
225 """Try to figure out how we should invoke hg for examining the local
226 repository contents.
226 repository contents.
227
227
228 Returns an hgcommand object."""
228 Returns an hgcommand object."""
229 # By default, prefer the "hg" command in the user's path. This was
229 # By default, prefer the "hg" command in the user's path. This was
230 # presumably the hg command that the user used to create this repository.
230 # presumably the hg command that the user used to create this repository.
231 #
231 #
232 # This repository may require extensions or other settings that would not
232 # This repository may require extensions or other settings that would not
233 # be enabled by running the hg script directly from this local repository.
233 # be enabled by running the hg script directly from this local repository.
234 hgenv = os.environ.copy()
234 hgenv = os.environ.copy()
235 # Use HGPLAIN to disable hgrc settings that would change output formatting,
235 # Use HGPLAIN to disable hgrc settings that would change output formatting,
236 # and disable localization for the same reasons.
236 # and disable localization for the same reasons.
237 hgenv['HGPLAIN'] = '1'
237 hgenv['HGPLAIN'] = '1'
238 hgenv['LANGUAGE'] = 'C'
238 hgenv['LANGUAGE'] = 'C'
239 hgcmd = ['hg']
239 hgcmd = ['hg']
240 # Run a simple "hg log" command just to see if using hg from the user's
240 # Run a simple "hg log" command just to see if using hg from the user's
241 # path works and can successfully interact with this repository.
241 # path works and can successfully interact with this repository.
242 check_cmd = ['log', '-r.', '-Ttest']
242 check_cmd = ['log', '-r.', '-Ttest']
243 try:
243 try:
244 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
244 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
245 except EnvironmentError:
245 except EnvironmentError:
246 retcode = -1
246 retcode = -1
247 if retcode == 0 and not filterhgerr(err):
247 if retcode == 0 and not filterhgerr(err):
248 return hgcommand(hgcmd, hgenv)
248 return hgcommand(hgcmd, hgenv)
249
249
250 # Fall back to trying the local hg installation.
250 # Fall back to trying the local hg installation.
251 hgenv = localhgenv()
251 hgenv = localhgenv()
252 # Don't source any system hgrc files when using the local hg.
252 # Don't source any system hgrc files when using the local hg.
253 hgenv['HGRCPATH'] = ''
253 hgenv['HGRCPATH'] = ''
254 hgcmd = [sys.executable, 'hg']
254 hgcmd = [sys.executable, 'hg']
255 try:
255 try:
256 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
256 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
257 except EnvironmentError:
257 except EnvironmentError:
258 retcode = -1
258 retcode = -1
259 if retcode == 0 and not filterhgerr(err):
259 if retcode == 0 and not filterhgerr(err):
260 return hgcommand(hgcmd, hgenv)
260 return hgcommand(hgcmd, hgenv)
261
261
262 raise SystemExit('Unable to find a working hg binary to extract the '
262 raise SystemExit('Unable to find a working hg binary to extract the '
263 'version from the repository tags')
263 'version from the repository tags')
264
264
265 def localhgenv():
265 def localhgenv():
266 """Get an environment dictionary to use for invoking or importing
266 """Get an environment dictionary to use for invoking or importing
267 mercurial from the local repository."""
267 mercurial from the local repository."""
268 # Execute hg out of this directory with a custom environment which takes
268 # Execute hg out of this directory with a custom environment which takes
269 # care to not use any hgrc files and do no localization.
269 # care to not use any hgrc files and do no localization.
270 env = {'HGMODULEPOLICY': 'py',
270 env = {'HGMODULEPOLICY': 'py',
271 'HGRCPATH': '',
271 'HGRCPATH': '',
272 'LANGUAGE': 'C',
272 'LANGUAGE': 'C',
273 'PATH': ''} # make pypi modules that use os.environ['PATH'] happy
273 'PATH': ''} # make pypi modules that use os.environ['PATH'] happy
274 if 'LD_LIBRARY_PATH' in os.environ:
274 if 'LD_LIBRARY_PATH' in os.environ:
275 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
275 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
276 if 'SystemRoot' in os.environ:
276 if 'SystemRoot' in os.environ:
277 # SystemRoot is required by Windows to load various DLLs. See:
277 # SystemRoot is required by Windows to load various DLLs. See:
278 # https://bugs.python.org/issue13524#msg148850
278 # https://bugs.python.org/issue13524#msg148850
279 env['SystemRoot'] = os.environ['SystemRoot']
279 env['SystemRoot'] = os.environ['SystemRoot']
280 return env
280 return env
281
281
282 version = ''
282 version = ''
283
283
284 if os.path.isdir('.hg'):
284 if os.path.isdir('.hg'):
285 hg = findhg()
285 hg = findhg()
286 cmd = ['log', '-r', '.', '--template', '{tags}\n']
286 cmd = ['log', '-r', '.', '--template', '{tags}\n']
287 numerictags = [t for t in hg.run(cmd).split() if t[0:1].isdigit()]
287 numerictags = [t for t in hg.run(cmd).split() if t[0:1].isdigit()]
288 hgid = hg.run(['id', '-i']).strip()
288 hgid = hg.run(['id', '-i']).strip()
289 if not hgid:
289 if not hgid:
290 # Bail out if hg is having problems interacting with this repository,
290 # Bail out if hg is having problems interacting with this repository,
291 # rather than falling through and producing a bogus version number.
291 # rather than falling through and producing a bogus version number.
292 # Continuing with an invalid version number will break extensions
292 # Continuing with an invalid version number will break extensions
293 # that define minimumhgversion.
293 # that define minimumhgversion.
294 raise SystemExit('Unable to determine hg version from local repository')
294 raise SystemExit('Unable to determine hg version from local repository')
295 if numerictags: # tag(s) found
295 if numerictags: # tag(s) found
296 version = numerictags[-1]
296 version = numerictags[-1]
297 if hgid.endswith('+'): # propagate the dirty status to the tag
297 if hgid.endswith('+'): # propagate the dirty status to the tag
298 version += '+'
298 version += '+'
299 else: # no tag found
299 else: # no tag found
300 ltagcmd = ['parents', '--template', '{latesttag}']
300 ltagcmd = ['parents', '--template', '{latesttag}']
301 ltag = hg.run(ltagcmd)
301 ltag = hg.run(ltagcmd)
302 changessincecmd = ['log', '-T', 'x\n', '-r', "only(.,'%s')" % ltag]
302 changessincecmd = ['log', '-T', 'x\n', '-r', "only(.,'%s')" % ltag]
303 changessince = len(hg.run(changessincecmd).splitlines())
303 changessince = len(hg.run(changessincecmd).splitlines())
304 version = '%s+%s-%s' % (ltag, changessince, hgid)
304 version = '%s+%s-%s' % (ltag, changessince, hgid)
305 if version.endswith('+'):
305 if version.endswith('+'):
306 version += time.strftime('%Y%m%d')
306 version += time.strftime('%Y%m%d')
307 elif os.path.exists('.hg_archival.txt'):
307 elif os.path.exists('.hg_archival.txt'):
308 kw = dict([[t.strip() for t in l.split(':', 1)]
308 kw = dict([[t.strip() for t in l.split(':', 1)]
309 for l in open('.hg_archival.txt')])
309 for l in open('.hg_archival.txt')])
310 if 'tag' in kw:
310 if 'tag' in kw:
311 version = kw['tag']
311 version = kw['tag']
312 elif 'latesttag' in kw:
312 elif 'latesttag' in kw:
313 if 'changessincelatesttag' in kw:
313 if 'changessincelatesttag' in kw:
314 version = '%(latesttag)s+%(changessincelatesttag)s-%(node).12s' % kw
314 version = '%(latesttag)s+%(changessincelatesttag)s-%(node).12s' % kw
315 else:
315 else:
316 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
316 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
317 else:
317 else:
318 version = kw.get('node', '')[:12]
318 version = kw.get('node', '')[:12]
319
319
320 if version:
320 if version:
321 with open("mercurial/__version__.py", "w") as f:
321 with open("mercurial/__version__.py", "w") as f:
322 f.write('# this file is autogenerated by setup.py\n')
322 f.write('# this file is autogenerated by setup.py\n')
323 f.write('version = "%s"\n' % version)
323 f.write('version = "%s"\n' % version)
324
324
325 try:
325 try:
326 oldpolicy = os.environ.get('HGMODULEPOLICY', None)
326 oldpolicy = os.environ.get('HGMODULEPOLICY', None)
327 os.environ['HGMODULEPOLICY'] = 'py'
327 os.environ['HGMODULEPOLICY'] = 'py'
328 from mercurial import __version__
328 from mercurial import __version__
329 version = __version__.version
329 version = __version__.version
330 except ImportError:
330 except ImportError:
331 version = 'unknown'
331 version = 'unknown'
332 finally:
332 finally:
333 if oldpolicy is None:
333 if oldpolicy is None:
334 del os.environ['HGMODULEPOLICY']
334 del os.environ['HGMODULEPOLICY']
335 else:
335 else:
336 os.environ['HGMODULEPOLICY'] = oldpolicy
336 os.environ['HGMODULEPOLICY'] = oldpolicy
337
337
338 class hgbuild(build):
338 class hgbuild(build):
339 # Insert hgbuildmo first so that files in mercurial/locale/ are found
339 # Insert hgbuildmo first so that files in mercurial/locale/ are found
340 # when build_py is run next.
340 # when build_py is run next.
341 sub_commands = [('build_mo', None)] + build.sub_commands
341 sub_commands = [('build_mo', None)] + build.sub_commands
342
342
343 class hgbuildmo(build):
343 class hgbuildmo(build):
344
344
345 description = "build translations (.mo files)"
345 description = "build translations (.mo files)"
346
346
347 def run(self):
347 def run(self):
348 if not find_executable('msgfmt'):
348 if not find_executable('msgfmt'):
349 self.warn("could not find msgfmt executable, no translations "
349 self.warn("could not find msgfmt executable, no translations "
350 "will be built")
350 "will be built")
351 return
351 return
352
352
353 podir = 'i18n'
353 podir = 'i18n'
354 if not os.path.isdir(podir):
354 if not os.path.isdir(podir):
355 self.warn("could not find %s/ directory" % podir)
355 self.warn("could not find %s/ directory" % podir)
356 return
356 return
357
357
358 join = os.path.join
358 join = os.path.join
359 for po in os.listdir(podir):
359 for po in os.listdir(podir):
360 if not po.endswith('.po'):
360 if not po.endswith('.po'):
361 continue
361 continue
362 pofile = join(podir, po)
362 pofile = join(podir, po)
363 modir = join('locale', po[:-3], 'LC_MESSAGES')
363 modir = join('locale', po[:-3], 'LC_MESSAGES')
364 mofile = join(modir, 'hg.mo')
364 mofile = join(modir, 'hg.mo')
365 mobuildfile = join('mercurial', mofile)
365 mobuildfile = join('mercurial', mofile)
366 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
366 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
367 if sys.platform != 'sunos5':
367 if sys.platform != 'sunos5':
368 # msgfmt on Solaris does not know about -c
368 # msgfmt on Solaris does not know about -c
369 cmd.append('-c')
369 cmd.append('-c')
370 self.mkpath(join('mercurial', modir))
370 self.mkpath(join('mercurial', modir))
371 self.make_file([pofile], mobuildfile, spawn, (cmd,))
371 self.make_file([pofile], mobuildfile, spawn, (cmd,))
372
372
373
373
374 class hgdist(Distribution):
374 class hgdist(Distribution):
375 pure = False
375 pure = False
376 cffi = ispypy
376 cffi = ispypy
377
377
378 global_options = Distribution.global_options + \
378 global_options = Distribution.global_options + \
379 [('pure', None, "use pure (slow) Python "
379 [('pure', None, "use pure (slow) Python "
380 "code instead of C extensions"),
380 "code instead of C extensions"),
381 ]
381 ]
382
382
383 def has_ext_modules(self):
383 def has_ext_modules(self):
384 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
384 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
385 # too late for some cases
385 # too late for some cases
386 return not self.pure and Distribution.has_ext_modules(self)
386 return not self.pure and Distribution.has_ext_modules(self)
387
387
388 # This is ugly as a one-liner. So use a variable.
388 # This is ugly as a one-liner. So use a variable.
389 buildextnegops = dict(getattr(build_ext, 'negative_options', {}))
389 buildextnegops = dict(getattr(build_ext, 'negative_options', {}))
390 buildextnegops['no-zstd'] = 'zstd'
390 buildextnegops['no-zstd'] = 'zstd'
391
391
392 class hgbuildext(build_ext):
392 class hgbuildext(build_ext):
393 user_options = build_ext.user_options + [
393 user_options = build_ext.user_options + [
394 ('zstd', None, 'compile zstd bindings [default]'),
394 ('zstd', None, 'compile zstd bindings [default]'),
395 ('no-zstd', None, 'do not compile zstd bindings'),
395 ('no-zstd', None, 'do not compile zstd bindings'),
396 ]
396 ]
397
397
398 boolean_options = build_ext.boolean_options + ['zstd']
398 boolean_options = build_ext.boolean_options + ['zstd']
399 negative_opt = buildextnegops
399 negative_opt = buildextnegops
400
400
401 def initialize_options(self):
401 def initialize_options(self):
402 self.zstd = True
402 self.zstd = True
403 return build_ext.initialize_options(self)
403 return build_ext.initialize_options(self)
404
404
405 def build_extensions(self):
405 def build_extensions(self):
406 # Filter out zstd if disabled via argument.
406 # Filter out zstd if disabled via argument.
407 if not self.zstd:
407 if not self.zstd:
408 self.extensions = [e for e in self.extensions
408 self.extensions = [e for e in self.extensions
409 if e.name != 'mercurial.zstd']
409 if e.name != 'mercurial.zstd']
410
410
411 return build_ext.build_extensions(self)
411 return build_ext.build_extensions(self)
412
412
413 def build_extension(self, ext):
413 def build_extension(self, ext):
414 try:
414 try:
415 build_ext.build_extension(self, ext)
415 build_ext.build_extension(self, ext)
416 except CCompilerError:
416 except CCompilerError:
417 if not getattr(ext, 'optional', False):
417 if not getattr(ext, 'optional', False):
418 raise
418 raise
419 log.warn("Failed to build optional extension '%s' (skipping)",
419 log.warn("Failed to build optional extension '%s' (skipping)",
420 ext.name)
420 ext.name)
421
421
422 class hgbuildscripts(build_scripts):
422 class hgbuildscripts(build_scripts):
423 def run(self):
423 def run(self):
424 if os.name != 'nt' or self.distribution.pure:
424 if os.name != 'nt' or self.distribution.pure:
425 return build_scripts.run(self)
425 return build_scripts.run(self)
426
426
427 exebuilt = False
427 exebuilt = False
428 try:
428 try:
429 self.run_command('build_hgexe')
429 self.run_command('build_hgexe')
430 exebuilt = True
430 exebuilt = True
431 except (DistutilsError, CCompilerError):
431 except (DistutilsError, CCompilerError):
432 log.warn('failed to build optional hg.exe')
432 log.warn('failed to build optional hg.exe')
433
433
434 if exebuilt:
434 if exebuilt:
435 # Copying hg.exe to the scripts build directory ensures it is
435 # Copying hg.exe to the scripts build directory ensures it is
436 # installed by the install_scripts command.
436 # installed by the install_scripts command.
437 hgexecommand = self.get_finalized_command('build_hgexe')
437 hgexecommand = self.get_finalized_command('build_hgexe')
438 dest = os.path.join(self.build_dir, 'hg.exe')
438 dest = os.path.join(self.build_dir, 'hg.exe')
439 self.mkpath(self.build_dir)
439 self.mkpath(self.build_dir)
440 self.copy_file(hgexecommand.hgexepath, dest)
440 self.copy_file(hgexecommand.hgexepath, dest)
441
441
442 # Remove hg.bat because it is redundant with hg.exe.
442 # Remove hg.bat because it is redundant with hg.exe.
443 self.scripts.remove('contrib/win32/hg.bat')
443 self.scripts.remove('contrib/win32/hg.bat')
444
444
445 return build_scripts.run(self)
445 return build_scripts.run(self)
446
446
447 class hgbuildpy(build_py):
447 class hgbuildpy(build_py):
448 def finalize_options(self):
448 def finalize_options(self):
449 build_py.finalize_options(self)
449 build_py.finalize_options(self)
450
450
451 if self.distribution.pure:
451 if self.distribution.pure:
452 self.distribution.ext_modules = []
452 self.distribution.ext_modules = []
453 elif self.distribution.cffi:
453 elif self.distribution.cffi:
454 from mercurial.cffi import (
454 from mercurial.cffi import (
455 bdiffbuild,
455 bdiffbuild,
456 mpatchbuild,
456 mpatchbuild,
457 )
457 )
458 exts = [mpatchbuild.ffi.distutils_extension(),
458 exts = [mpatchbuild.ffi.distutils_extension(),
459 bdiffbuild.ffi.distutils_extension()]
459 bdiffbuild.ffi.distutils_extension()]
460 # cffi modules go here
460 # cffi modules go here
461 if sys.platform == 'darwin':
461 if sys.platform == 'darwin':
462 from mercurial.cffi import osutilbuild
462 from mercurial.cffi import osutilbuild
463 exts.append(osutilbuild.ffi.distutils_extension())
463 exts.append(osutilbuild.ffi.distutils_extension())
464 self.distribution.ext_modules = exts
464 self.distribution.ext_modules = exts
465 else:
465 else:
466 h = os.path.join(get_python_inc(), 'Python.h')
466 h = os.path.join(get_python_inc(), 'Python.h')
467 if not os.path.exists(h):
467 if not os.path.exists(h):
468 raise SystemExit('Python headers are required to build '
468 raise SystemExit('Python headers are required to build '
469 'Mercurial but weren\'t found in %s' % h)
469 'Mercurial but weren\'t found in %s' % h)
470
470
471 def run(self):
471 def run(self):
472 basepath = os.path.join(self.build_lib, 'mercurial')
472 basepath = os.path.join(self.build_lib, 'mercurial')
473 self.mkpath(basepath)
473 self.mkpath(basepath)
474
474
475 if self.distribution.pure:
475 if self.distribution.pure:
476 modulepolicy = 'py'
476 modulepolicy = 'py'
477 elif self.build_lib == '.':
477 elif self.build_lib == '.':
478 # in-place build should run without rebuilding C extensions
478 # in-place build should run without rebuilding C extensions
479 modulepolicy = 'allow'
479 modulepolicy = 'allow'
480 else:
480 else:
481 modulepolicy = 'c'
481 modulepolicy = 'c'
482 with open(os.path.join(basepath, '__modulepolicy__.py'), "w") as f:
482 with open(os.path.join(basepath, '__modulepolicy__.py'), "w") as f:
483 f.write('# this file is autogenerated by setup.py\n')
483 f.write('# this file is autogenerated by setup.py\n')
484 f.write('modulepolicy = b"%s"\n' % modulepolicy)
484 f.write('modulepolicy = b"%s"\n' % modulepolicy)
485
485
486 build_py.run(self)
486 build_py.run(self)
487
487
488 class buildhgextindex(Command):
488 class buildhgextindex(Command):
489 description = 'generate prebuilt index of hgext (for frozen package)'
489 description = 'generate prebuilt index of hgext (for frozen package)'
490 user_options = []
490 user_options = []
491 _indexfilename = 'hgext/__index__.py'
491 _indexfilename = 'hgext/__index__.py'
492
492
493 def initialize_options(self):
493 def initialize_options(self):
494 pass
494 pass
495
495
496 def finalize_options(self):
496 def finalize_options(self):
497 pass
497 pass
498
498
499 def run(self):
499 def run(self):
500 if os.path.exists(self._indexfilename):
500 if os.path.exists(self._indexfilename):
501 with open(self._indexfilename, 'w') as f:
501 with open(self._indexfilename, 'w') as f:
502 f.write('# empty\n')
502 f.write('# empty\n')
503
503
504 # here no extension enabled, disabled() lists up everything
504 # here no extension enabled, disabled() lists up everything
505 code = ('import pprint; from mercurial import extensions; '
505 code = ('import pprint; from mercurial import extensions; '
506 'pprint.pprint(extensions.disabled())')
506 'pprint.pprint(extensions.disabled())')
507 returncode, out, err = runcmd([sys.executable, '-c', code],
507 returncode, out, err = runcmd([sys.executable, '-c', code],
508 localhgenv())
508 localhgenv())
509 if err or returncode != 0:
509 if err or returncode != 0:
510 raise DistutilsExecError(err)
510 raise DistutilsExecError(err)
511
511
512 with open(self._indexfilename, 'w') as f:
512 with open(self._indexfilename, 'w') as f:
513 f.write('# this file is autogenerated by setup.py\n')
513 f.write('# this file is autogenerated by setup.py\n')
514 f.write('docs = ')
514 f.write('docs = ')
515 f.write(out)
515 f.write(out)
516
516
517 class buildhgexe(build_ext):
517 class buildhgexe(build_ext):
518 description = 'compile hg.exe from mercurial/exewrapper.c'
518 description = 'compile hg.exe from mercurial/exewrapper.c'
519
519
520 def build_extensions(self):
520 def build_extensions(self):
521 if os.name != 'nt':
521 if os.name != 'nt':
522 return
522 return
523 if isinstance(self.compiler, HackedMingw32CCompiler):
523 if isinstance(self.compiler, HackedMingw32CCompiler):
524 self.compiler.compiler_so = self.compiler.compiler # no -mdll
524 self.compiler.compiler_so = self.compiler.compiler # no -mdll
525 self.compiler.dll_libraries = [] # no -lmsrvc90
525 self.compiler.dll_libraries = [] # no -lmsrvc90
526
526
527 # Different Python installs can have different Python library
527 # Different Python installs can have different Python library
528 # names. e.g. the official CPython distribution uses pythonXY.dll
528 # names. e.g. the official CPython distribution uses pythonXY.dll
529 # and MinGW uses libpythonX.Y.dll.
529 # and MinGW uses libpythonX.Y.dll.
530 _kernel32 = ctypes.windll.kernel32
530 _kernel32 = ctypes.windll.kernel32
531 _kernel32.GetModuleFileNameA.argtypes = [ctypes.c_void_p,
531 _kernel32.GetModuleFileNameA.argtypes = [ctypes.c_void_p,
532 ctypes.c_void_p,
532 ctypes.c_void_p,
533 ctypes.c_ulong]
533 ctypes.c_ulong]
534 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
534 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
535 size = 1000
535 size = 1000
536 buf = ctypes.create_string_buffer(size + 1)
536 buf = ctypes.create_string_buffer(size + 1)
537 filelen = _kernel32.GetModuleFileNameA(sys.dllhandle, ctypes.byref(buf),
537 filelen = _kernel32.GetModuleFileNameA(sys.dllhandle, ctypes.byref(buf),
538 size)
538 size)
539
539
540 if filelen > 0 and filelen != size:
540 if filelen > 0 and filelen != size:
541 dllbasename = os.path.basename(buf.value)
541 dllbasename = os.path.basename(buf.value)
542 if not dllbasename.lower().endswith('.dll'):
542 if not dllbasename.lower().endswith('.dll'):
543 raise SystemExit('Python DLL does not end with .dll: %s' %
543 raise SystemExit('Python DLL does not end with .dll: %s' %
544 dllbasename)
544 dllbasename)
545 pythonlib = dllbasename[:-4]
545 pythonlib = dllbasename[:-4]
546 else:
546 else:
547 log.warn('could not determine Python DLL filename; '
547 log.warn('could not determine Python DLL filename; '
548 'assuming pythonXY')
548 'assuming pythonXY')
549
549
550 hv = sys.hexversion
550 hv = sys.hexversion
551 pythonlib = 'python%d%d' % (hv >> 24, (hv >> 16) & 0xff)
551 pythonlib = 'python%d%d' % (hv >> 24, (hv >> 16) & 0xff)
552
552
553 log.info('using %s as Python library name' % pythonlib)
553 log.info('using %s as Python library name' % pythonlib)
554 with open('mercurial/hgpythonlib.h', 'wb') as f:
554 with open('mercurial/hgpythonlib.h', 'wb') as f:
555 f.write('/* this file is autogenerated by setup.py */\n')
555 f.write('/* this file is autogenerated by setup.py */\n')
556 f.write('#define HGPYTHONLIB "%s"\n' % pythonlib)
556 f.write('#define HGPYTHONLIB "%s"\n' % pythonlib)
557 objects = self.compiler.compile(['mercurial/exewrapper.c'],
557 objects = self.compiler.compile(['mercurial/exewrapper.c'],
558 output_dir=self.build_temp)
558 output_dir=self.build_temp)
559 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
559 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
560 target = os.path.join(dir, 'hg')
560 target = os.path.join(dir, 'hg')
561 self.compiler.link_executable(objects, target,
561 self.compiler.link_executable(objects, target,
562 libraries=[],
562 libraries=[],
563 output_dir=self.build_temp)
563 output_dir=self.build_temp)
564
564
565 @property
565 @property
566 def hgexepath(self):
566 def hgexepath(self):
567 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
567 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
568 return os.path.join(self.build_temp, dir, 'hg.exe')
568 return os.path.join(self.build_temp, dir, 'hg.exe')
569
569
570 class hginstall(install):
570 class hginstall(install):
571
571
572 user_options = install.user_options + [
572 user_options = install.user_options + [
573 ('old-and-unmanageable', None,
573 ('old-and-unmanageable', None,
574 'noop, present for eggless setuptools compat'),
574 'noop, present for eggless setuptools compat'),
575 ('single-version-externally-managed', None,
575 ('single-version-externally-managed', None,
576 'noop, present for eggless setuptools compat'),
576 'noop, present for eggless setuptools compat'),
577 ]
577 ]
578
578
579 # Also helps setuptools not be sad while we refuse to create eggs.
579 # Also helps setuptools not be sad while we refuse to create eggs.
580 single_version_externally_managed = True
580 single_version_externally_managed = True
581
581
582 def get_sub_commands(self):
582 def get_sub_commands(self):
583 # Screen out egg related commands to prevent egg generation. But allow
583 # Screen out egg related commands to prevent egg generation. But allow
584 # mercurial.egg-info generation, since that is part of modern
584 # mercurial.egg-info generation, since that is part of modern
585 # packaging.
585 # packaging.
586 excl = set(['bdist_egg'])
586 excl = set(['bdist_egg'])
587 return filter(lambda x: x not in excl, install.get_sub_commands(self))
587 return filter(lambda x: x not in excl, install.get_sub_commands(self))
588
588
589 class hginstalllib(install_lib):
589 class hginstalllib(install_lib):
590 '''
590 '''
591 This is a specialization of install_lib that replaces the copy_file used
591 This is a specialization of install_lib that replaces the copy_file used
592 there so that it supports setting the mode of files after copying them,
592 there so that it supports setting the mode of files after copying them,
593 instead of just preserving the mode that the files originally had. If your
593 instead of just preserving the mode that the files originally had. If your
594 system has a umask of something like 027, preserving the permissions when
594 system has a umask of something like 027, preserving the permissions when
595 copying will lead to a broken install.
595 copying will lead to a broken install.
596
596
597 Note that just passing keep_permissions=False to copy_file would be
597 Note that just passing keep_permissions=False to copy_file would be
598 insufficient, as it might still be applying a umask.
598 insufficient, as it might still be applying a umask.
599 '''
599 '''
600
600
601 def run(self):
601 def run(self):
602 realcopyfile = file_util.copy_file
602 realcopyfile = file_util.copy_file
603 def copyfileandsetmode(*args, **kwargs):
603 def copyfileandsetmode(*args, **kwargs):
604 src, dst = args[0], args[1]
604 src, dst = args[0], args[1]
605 dst, copied = realcopyfile(*args, **kwargs)
605 dst, copied = realcopyfile(*args, **kwargs)
606 if copied:
606 if copied:
607 st = os.stat(src)
607 st = os.stat(src)
608 # Persist executable bit (apply it to group and other if user
608 # Persist executable bit (apply it to group and other if user
609 # has it)
609 # has it)
610 if st[stat.ST_MODE] & stat.S_IXUSR:
610 if st[stat.ST_MODE] & stat.S_IXUSR:
611 setmode = int('0755', 8)
611 setmode = int('0755', 8)
612 else:
612 else:
613 setmode = int('0644', 8)
613 setmode = int('0644', 8)
614 m = stat.S_IMODE(st[stat.ST_MODE])
614 m = stat.S_IMODE(st[stat.ST_MODE])
615 m = (m & ~int('0777', 8)) | setmode
615 m = (m & ~int('0777', 8)) | setmode
616 os.chmod(dst, m)
616 os.chmod(dst, m)
617 file_util.copy_file = copyfileandsetmode
617 file_util.copy_file = copyfileandsetmode
618 try:
618 try:
619 install_lib.run(self)
619 install_lib.run(self)
620 finally:
620 finally:
621 file_util.copy_file = realcopyfile
621 file_util.copy_file = realcopyfile
622
622
623 class hginstallscripts(install_scripts):
623 class hginstallscripts(install_scripts):
624 '''
624 '''
625 This is a specialization of install_scripts that replaces the @LIBDIR@ with
625 This is a specialization of install_scripts that replaces the @LIBDIR@ with
626 the configured directory for modules. If possible, the path is made relative
626 the configured directory for modules. If possible, the path is made relative
627 to the directory for scripts.
627 to the directory for scripts.
628 '''
628 '''
629
629
630 def initialize_options(self):
630 def initialize_options(self):
631 install_scripts.initialize_options(self)
631 install_scripts.initialize_options(self)
632
632
633 self.install_lib = None
633 self.install_lib = None
634
634
635 def finalize_options(self):
635 def finalize_options(self):
636 install_scripts.finalize_options(self)
636 install_scripts.finalize_options(self)
637 self.set_undefined_options('install',
637 self.set_undefined_options('install',
638 ('install_lib', 'install_lib'))
638 ('install_lib', 'install_lib'))
639
639
640 def run(self):
640 def run(self):
641 install_scripts.run(self)
641 install_scripts.run(self)
642
642
643 # It only makes sense to replace @LIBDIR@ with the install path if
643 # It only makes sense to replace @LIBDIR@ with the install path if
644 # the install path is known. For wheels, the logic below calculates
644 # the install path is known. For wheels, the logic below calculates
645 # the libdir to be "../..". This is because the internal layout of a
645 # the libdir to be "../..". This is because the internal layout of a
646 # wheel archive looks like:
646 # wheel archive looks like:
647 #
647 #
648 # mercurial-3.6.1.data/scripts/hg
648 # mercurial-3.6.1.data/scripts/hg
649 # mercurial/__init__.py
649 # mercurial/__init__.py
650 #
650 #
651 # When installing wheels, the subdirectories of the "<pkg>.data"
651 # When installing wheels, the subdirectories of the "<pkg>.data"
652 # directory are translated to system local paths and files therein
652 # directory are translated to system local paths and files therein
653 # are copied in place. The mercurial/* files are installed into the
653 # are copied in place. The mercurial/* files are installed into the
654 # site-packages directory. However, the site-packages directory
654 # site-packages directory. However, the site-packages directory
655 # isn't known until wheel install time. This means we have no clue
655 # isn't known until wheel install time. This means we have no clue
656 # at wheel generation time what the installed site-packages directory
656 # at wheel generation time what the installed site-packages directory
657 # will be. And, wheels don't appear to provide the ability to register
657 # will be. And, wheels don't appear to provide the ability to register
658 # custom code to run during wheel installation. This all means that
658 # custom code to run during wheel installation. This all means that
659 # we can't reliably set the libdir in wheels: the default behavior
659 # we can't reliably set the libdir in wheels: the default behavior
660 # of looking in sys.path must do.
660 # of looking in sys.path must do.
661
661
662 if (os.path.splitdrive(self.install_dir)[0] !=
662 if (os.path.splitdrive(self.install_dir)[0] !=
663 os.path.splitdrive(self.install_lib)[0]):
663 os.path.splitdrive(self.install_lib)[0]):
664 # can't make relative paths from one drive to another, so use an
664 # can't make relative paths from one drive to another, so use an
665 # absolute path instead
665 # absolute path instead
666 libdir = self.install_lib
666 libdir = self.install_lib
667 else:
667 else:
668 common = os.path.commonprefix((self.install_dir, self.install_lib))
668 common = os.path.commonprefix((self.install_dir, self.install_lib))
669 rest = self.install_dir[len(common):]
669 rest = self.install_dir[len(common):]
670 uplevel = len([n for n in os.path.split(rest) if n])
670 uplevel = len([n for n in os.path.split(rest) if n])
671
671
672 libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common):]
672 libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common):]
673
673
674 for outfile in self.outfiles:
674 for outfile in self.outfiles:
675 with open(outfile, 'rb') as fp:
675 with open(outfile, 'rb') as fp:
676 data = fp.read()
676 data = fp.read()
677
677
678 # skip binary files
678 # skip binary files
679 if b'\0' in data:
679 if b'\0' in data:
680 continue
680 continue
681
681
682 # During local installs, the shebang will be rewritten to the final
682 # During local installs, the shebang will be rewritten to the final
683 # install path. During wheel packaging, the shebang has a special
683 # install path. During wheel packaging, the shebang has a special
684 # value.
684 # value.
685 if data.startswith(b'#!python'):
685 if data.startswith(b'#!python'):
686 log.info('not rewriting @LIBDIR@ in %s because install path '
686 log.info('not rewriting @LIBDIR@ in %s because install path '
687 'not known' % outfile)
687 'not known' % outfile)
688 continue
688 continue
689
689
690 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
690 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
691 with open(outfile, 'wb') as fp:
691 with open(outfile, 'wb') as fp:
692 fp.write(data)
692 fp.write(data)
693
693
694 cmdclass = {'build': hgbuild,
694 cmdclass = {'build': hgbuild,
695 'build_mo': hgbuildmo,
695 'build_mo': hgbuildmo,
696 'build_ext': hgbuildext,
696 'build_ext': hgbuildext,
697 'build_py': hgbuildpy,
697 'build_py': hgbuildpy,
698 'build_scripts': hgbuildscripts,
698 'build_scripts': hgbuildscripts,
699 'build_hgextindex': buildhgextindex,
699 'build_hgextindex': buildhgextindex,
700 'install': hginstall,
700 'install': hginstall,
701 'install_lib': hginstalllib,
701 'install_lib': hginstalllib,
702 'install_scripts': hginstallscripts,
702 'install_scripts': hginstallscripts,
703 'build_hgexe': buildhgexe,
703 'build_hgexe': buildhgexe,
704 }
704 }
705
705
706 packages = ['mercurial',
706 packages = ['mercurial',
707 'mercurial.cext',
707 'mercurial.cext',
708 'mercurial.cffi',
708 'mercurial.cffi',
709 'mercurial.hgweb',
709 'mercurial.hgweb',
710 'mercurial.httpclient',
710 'mercurial.httpclient',
711 'mercurial.pure',
711 'mercurial.pure',
712 'hgext', 'hgext.convert', 'hgext.fsmonitor',
712 'hgext', 'hgext.convert', 'hgext.fsmonitor',
713 'hgext.fsmonitor.pywatchman', 'hgext.highlight',
713 'hgext.fsmonitor.pywatchman', 'hgext.highlight',
714 'hgext.largefiles', 'hgext.zeroconf', 'hgext3rd',
714 'hgext.largefiles', 'hgext.zeroconf', 'hgext3rd',
715 'hgdemandimport']
715 'hgdemandimport']
716
716
717 common_depends = ['mercurial/bitmanipulation.h',
717 common_depends = ['mercurial/bitmanipulation.h',
718 'mercurial/compat.h',
718 'mercurial/compat.h',
719 'mercurial/cext/util.h']
719 'mercurial/cext/util.h']
720 common_include_dirs = ['mercurial']
720 common_include_dirs = ['mercurial']
721
721
722 osutil_cflags = []
722 osutil_cflags = []
723 osutil_ldflags = []
723 osutil_ldflags = []
724
724
725 # platform specific macros
725 # platform specific macros
726 for plat, func in [('bsd', 'setproctitle')]:
726 for plat, func in [('bsd', 'setproctitle')]:
727 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
727 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
728 osutil_cflags.append('-DHAVE_%s' % func.upper())
728 osutil_cflags.append('-DHAVE_%s' % func.upper())
729
729
730 for plat, macro, code in [
730 for plat, macro, code in [
731 ('bsd|darwin', 'BSD_STATFS', '''
731 ('bsd|darwin', 'BSD_STATFS', '''
732 #include <sys/param.h>
732 #include <sys/param.h>
733 #include <sys/mount.h>
733 #include <sys/mount.h>
734 int main() { struct statfs s; return sizeof(s.f_fstypename); }
734 int main() { struct statfs s; return sizeof(s.f_fstypename); }
735 '''),
735 '''),
736 ('linux', 'LINUX_STATFS', '''
736 ('linux', 'LINUX_STATFS', '''
737 #include <linux/magic.h>
737 #include <linux/magic.h>
738 #include <sys/vfs.h>
738 #include <sys/vfs.h>
739 int main() { struct statfs s; return sizeof(s.f_type); }
739 int main() { struct statfs s; return sizeof(s.f_type); }
740 '''),
740 '''),
741 ]:
741 ]:
742 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
742 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
743 osutil_cflags.append('-DHAVE_%s' % macro)
743 osutil_cflags.append('-DHAVE_%s' % macro)
744
744
745 if sys.platform == 'darwin':
745 if sys.platform == 'darwin':
746 osutil_ldflags += ['-framework', 'ApplicationServices']
746 osutil_ldflags += ['-framework', 'ApplicationServices']
747
747
748 extmodules = [
748 extmodules = [
749 Extension('mercurial.cext.base85', ['mercurial/cext/base85.c'],
749 Extension('mercurial.cext.base85', ['mercurial/cext/base85.c'],
750 include_dirs=common_include_dirs,
750 include_dirs=common_include_dirs,
751 depends=common_depends),
751 depends=common_depends),
752 Extension('mercurial.cext.bdiff', ['mercurial/bdiff.c',
752 Extension('mercurial.cext.bdiff', ['mercurial/bdiff.c',
753 'mercurial/cext/bdiff.c'],
753 'mercurial/cext/bdiff.c'],
754 include_dirs=common_include_dirs,
754 include_dirs=common_include_dirs,
755 depends=common_depends + ['mercurial/bdiff.h']),
755 depends=common_depends + ['mercurial/bdiff.h']),
756 Extension('mercurial.cext.diffhelpers', ['mercurial/cext/diffhelpers.c'],
756 Extension('mercurial.cext.diffhelpers', ['mercurial/cext/diffhelpers.c'],
757 include_dirs=common_include_dirs,
757 include_dirs=common_include_dirs,
758 depends=common_depends),
758 depends=common_depends),
759 Extension('mercurial.cext.mpatch', ['mercurial/mpatch.c',
759 Extension('mercurial.cext.mpatch', ['mercurial/mpatch.c',
760 'mercurial/cext/mpatch.c'],
760 'mercurial/cext/mpatch.c'],
761 include_dirs=common_include_dirs,
761 include_dirs=common_include_dirs,
762 depends=common_depends),
762 depends=common_depends),
763 Extension('mercurial.cext.parsers', ['mercurial/cext/charencode.c',
763 Extension('mercurial.cext.parsers', ['mercurial/cext/charencode.c',
764 'mercurial/cext/dirs.c',
764 'mercurial/cext/dirs.c',
765 'mercurial/cext/manifest.c',
765 'mercurial/cext/manifest.c',
766 'mercurial/cext/parsers.c',
766 'mercurial/cext/parsers.c',
767 'mercurial/cext/pathencode.c',
767 'mercurial/cext/pathencode.c',
768 'mercurial/cext/revlog.c'],
768 'mercurial/cext/revlog.c'],
769 include_dirs=common_include_dirs,
769 include_dirs=common_include_dirs,
770 depends=common_depends + ['mercurial/cext/charencode.h']),
770 depends=common_depends + ['mercurial/cext/charencode.h']),
771 Extension('mercurial.cext.osutil', ['mercurial/cext/osutil.c'],
771 Extension('mercurial.cext.osutil', ['mercurial/cext/osutil.c'],
772 include_dirs=common_include_dirs,
772 include_dirs=common_include_dirs,
773 extra_compile_args=osutil_cflags,
773 extra_compile_args=osutil_cflags,
774 extra_link_args=osutil_ldflags,
774 extra_link_args=osutil_ldflags,
775 depends=common_depends),
775 depends=common_depends),
776 Extension('hgext.fsmonitor.pywatchman.bser',
776 Extension('hgext.fsmonitor.pywatchman.bser',
777 ['hgext/fsmonitor/pywatchman/bser.c']),
777 ['hgext/fsmonitor/pywatchman/bser.c']),
778 ]
778 ]
779
779
780 sys.path.insert(0, 'contrib/python-zstandard')
780 sys.path.insert(0, 'contrib/python-zstandard')
781 import setup_zstd
781 import setup_zstd
782 extmodules.append(setup_zstd.get_c_extension(name='mercurial.zstd'))
782 extmodules.append(setup_zstd.get_c_extension(name='mercurial.zstd'))
783
783
784 try:
784 try:
785 from distutils import cygwinccompiler
785 from distutils import cygwinccompiler
786
786
787 # the -mno-cygwin option has been deprecated for years
787 # the -mno-cygwin option has been deprecated for years
788 compiler = cygwinccompiler.Mingw32CCompiler
788 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
789
789
790 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
790 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
791 def __init__(self, *args, **kwargs):
791 def __init__(self, *args, **kwargs):
792 compiler.__init__(self, *args, **kwargs)
792 mingw32compilerclass.__init__(self, *args, **kwargs)
793 for i in 'compiler compiler_so linker_exe linker_so'.split():
793 for i in 'compiler compiler_so linker_exe linker_so'.split():
794 try:
794 try:
795 getattr(self, i).remove('-mno-cygwin')
795 getattr(self, i).remove('-mno-cygwin')
796 except ValueError:
796 except ValueError:
797 pass
797 pass
798
798
799 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
799 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
800 except ImportError:
800 except ImportError:
801 # the cygwinccompiler package is not available on some Python
801 # the cygwinccompiler package is not available on some Python
802 # distributions like the ones from the optware project for Synology
802 # distributions like the ones from the optware project for Synology
803 # DiskStation boxes
803 # DiskStation boxes
804 class HackedMingw32CCompiler(object):
804 class HackedMingw32CCompiler(object):
805 pass
805 pass
806
806
807 if os.name == 'nt':
807 if os.name == 'nt':
808 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
808 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
809 # extra_link_args to distutils.extensions.Extension() doesn't have any
809 # extra_link_args to distutils.extensions.Extension() doesn't have any
810 # effect.
810 # effect.
811 from distutils import msvccompiler
811 from distutils import msvccompiler
812
812
813 compiler = msvccompiler.MSVCCompiler
813 msvccompilerclass = msvccompiler.MSVCCompiler
814
814
815 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
815 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
816 def initialize(self):
816 def initialize(self):
817 compiler.initialize(self)
817 msvccompilerclass.initialize(self)
818 # "warning LNK4197: export 'func' specified multiple times"
818 # "warning LNK4197: export 'func' specified multiple times"
819 self.ldflags_shared.append('/ignore:4197')
819 self.ldflags_shared.append('/ignore:4197')
820 self.ldflags_shared_debug.append('/ignore:4197')
820 self.ldflags_shared_debug.append('/ignore:4197')
821
821
822 msvccompiler.MSVCCompiler = HackedMSVCCompiler
822 msvccompiler.MSVCCompiler = HackedMSVCCompiler
823
823
824 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
824 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
825 'help/*.txt',
825 'help/*.txt',
826 'help/internals/*.txt',
826 'help/internals/*.txt',
827 'default.d/*.rc',
827 'default.d/*.rc',
828 'dummycert.pem']}
828 'dummycert.pem']}
829
829
830 def ordinarypath(p):
830 def ordinarypath(p):
831 return p and p[0] != '.' and p[-1] != '~'
831 return p and p[0] != '.' and p[-1] != '~'
832
832
833 for root in ('templates',):
833 for root in ('templates',):
834 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
834 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
835 curdir = curdir.split(os.sep, 1)[1]
835 curdir = curdir.split(os.sep, 1)[1]
836 dirs[:] = filter(ordinarypath, dirs)
836 dirs[:] = filter(ordinarypath, dirs)
837 for f in filter(ordinarypath, files):
837 for f in filter(ordinarypath, files):
838 f = os.path.join(curdir, f)
838 f = os.path.join(curdir, f)
839 packagedata['mercurial'].append(f)
839 packagedata['mercurial'].append(f)
840
840
841 datafiles = []
841 datafiles = []
842
842
843 # distutils expects version to be str/unicode. Converting it to
843 # distutils expects version to be str/unicode. Converting it to
844 # unicode on Python 2 still works because it won't contain any
844 # unicode on Python 2 still works because it won't contain any
845 # non-ascii bytes and will be implicitly converted back to bytes
845 # non-ascii bytes and will be implicitly converted back to bytes
846 # when operated on.
846 # when operated on.
847 assert isinstance(version, bytes)
847 assert isinstance(version, bytes)
848 setupversion = version.decode('ascii')
848 setupversion = version.decode('ascii')
849
849
850 extra = {}
850 extra = {}
851
851
852 if issetuptools:
852 if issetuptools:
853 extra['python_requires'] = supportedpy
853 extra['python_requires'] = supportedpy
854 if py2exeloaded:
854 if py2exeloaded:
855 extra['console'] = [
855 extra['console'] = [
856 {'script':'hg',
856 {'script':'hg',
857 'copyright':'Copyright (C) 2005-2017 Matt Mackall and others',
857 'copyright':'Copyright (C) 2005-2017 Matt Mackall and others',
858 'product_version':version}]
858 'product_version':version}]
859 # sub command of 'build' because 'py2exe' does not handle sub_commands
859 # sub command of 'build' because 'py2exe' does not handle sub_commands
860 build.sub_commands.insert(0, ('build_hgextindex', None))
860 build.sub_commands.insert(0, ('build_hgextindex', None))
861 # put dlls in sub directory so that they won't pollute PATH
861 # put dlls in sub directory so that they won't pollute PATH
862 extra['zipfile'] = 'lib/library.zip'
862 extra['zipfile'] = 'lib/library.zip'
863
863
864 if os.name == 'nt':
864 if os.name == 'nt':
865 # Windows binary file versions for exe/dll files must have the
865 # Windows binary file versions for exe/dll files must have the
866 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
866 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
867 setupversion = version.split('+', 1)[0]
867 setupversion = version.split('+', 1)[0]
868
868
869 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
869 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
870 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
870 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
871 if version:
871 if version:
872 version = version[0]
872 version = version[0]
873 if sys.version_info[0] == 3:
873 if sys.version_info[0] == 3:
874 version = version.decode('utf-8')
874 version = version.decode('utf-8')
875 xcode4 = (version.startswith('Xcode') and
875 xcode4 = (version.startswith('Xcode') and
876 StrictVersion(version.split()[1]) >= StrictVersion('4.0'))
876 StrictVersion(version.split()[1]) >= StrictVersion('4.0'))
877 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
877 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
878 else:
878 else:
879 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
879 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
880 # installed, but instead with only command-line tools. Assume
880 # installed, but instead with only command-line tools. Assume
881 # that only happens on >= Lion, thus no PPC support.
881 # that only happens on >= Lion, thus no PPC support.
882 xcode4 = True
882 xcode4 = True
883 xcode51 = False
883 xcode51 = False
884
884
885 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
885 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
886 # distutils.sysconfig
886 # distutils.sysconfig
887 if xcode4:
887 if xcode4:
888 os.environ['ARCHFLAGS'] = ''
888 os.environ['ARCHFLAGS'] = ''
889
889
890 # XCode 5.1 changes clang such that it now fails to compile if the
890 # XCode 5.1 changes clang such that it now fails to compile if the
891 # -mno-fused-madd flag is passed, but the version of Python shipped with
891 # -mno-fused-madd flag is passed, but the version of Python shipped with
892 # OS X 10.9 Mavericks includes this flag. This causes problems in all
892 # OS X 10.9 Mavericks includes this flag. This causes problems in all
893 # C extension modules, and a bug has been filed upstream at
893 # C extension modules, and a bug has been filed upstream at
894 # http://bugs.python.org/issue21244. We also need to patch this here
894 # http://bugs.python.org/issue21244. We also need to patch this here
895 # so Mercurial can continue to compile in the meantime.
895 # so Mercurial can continue to compile in the meantime.
896 if xcode51:
896 if xcode51:
897 cflags = get_config_var('CFLAGS')
897 cflags = get_config_var('CFLAGS')
898 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
898 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
899 os.environ['CFLAGS'] = (
899 os.environ['CFLAGS'] = (
900 os.environ.get('CFLAGS', '') + ' -Qunused-arguments')
900 os.environ.get('CFLAGS', '') + ' -Qunused-arguments')
901
901
902 setup(name='mercurial',
902 setup(name='mercurial',
903 version=setupversion,
903 version=setupversion,
904 author='Matt Mackall and many others',
904 author='Matt Mackall and many others',
905 author_email='mercurial@mercurial-scm.org',
905 author_email='mercurial@mercurial-scm.org',
906 url='https://mercurial-scm.org/',
906 url='https://mercurial-scm.org/',
907 download_url='https://mercurial-scm.org/release/',
907 download_url='https://mercurial-scm.org/release/',
908 description=('Fast scalable distributed SCM (revision control, version '
908 description=('Fast scalable distributed SCM (revision control, version '
909 'control) system'),
909 'control) system'),
910 long_description=('Mercurial is a distributed SCM tool written in Python.'
910 long_description=('Mercurial is a distributed SCM tool written in Python.'
911 ' It is used by a number of large projects that require'
911 ' It is used by a number of large projects that require'
912 ' fast, reliable distributed revision control, such as '
912 ' fast, reliable distributed revision control, such as '
913 'Mozilla.'),
913 'Mozilla.'),
914 license='GNU GPLv2 or any later version',
914 license='GNU GPLv2 or any later version',
915 classifiers=[
915 classifiers=[
916 'Development Status :: 6 - Mature',
916 'Development Status :: 6 - Mature',
917 'Environment :: Console',
917 'Environment :: Console',
918 'Intended Audience :: Developers',
918 'Intended Audience :: Developers',
919 'Intended Audience :: System Administrators',
919 'Intended Audience :: System Administrators',
920 'License :: OSI Approved :: GNU General Public License (GPL)',
920 'License :: OSI Approved :: GNU General Public License (GPL)',
921 'Natural Language :: Danish',
921 'Natural Language :: Danish',
922 'Natural Language :: English',
922 'Natural Language :: English',
923 'Natural Language :: German',
923 'Natural Language :: German',
924 'Natural Language :: Italian',
924 'Natural Language :: Italian',
925 'Natural Language :: Japanese',
925 'Natural Language :: Japanese',
926 'Natural Language :: Portuguese (Brazilian)',
926 'Natural Language :: Portuguese (Brazilian)',
927 'Operating System :: Microsoft :: Windows',
927 'Operating System :: Microsoft :: Windows',
928 'Operating System :: OS Independent',
928 'Operating System :: OS Independent',
929 'Operating System :: POSIX',
929 'Operating System :: POSIX',
930 'Programming Language :: C',
930 'Programming Language :: C',
931 'Programming Language :: Python',
931 'Programming Language :: Python',
932 'Topic :: Software Development :: Version Control',
932 'Topic :: Software Development :: Version Control',
933 ],
933 ],
934 scripts=scripts,
934 scripts=scripts,
935 packages=packages,
935 packages=packages,
936 ext_modules=extmodules,
936 ext_modules=extmodules,
937 data_files=datafiles,
937 data_files=datafiles,
938 package_data=packagedata,
938 package_data=packagedata,
939 cmdclass=cmdclass,
939 cmdclass=cmdclass,
940 distclass=hgdist,
940 distclass=hgdist,
941 options={'py2exe': {'packages': ['hgdemandimport', 'hgext', 'email',
941 options={'py2exe': {'packages': ['hgdemandimport', 'hgext', 'email',
942 # implicitly imported per module policy
942 # implicitly imported per module policy
943 # (cffi wouldn't be used as a frozen exe)
943 # (cffi wouldn't be used as a frozen exe)
944 'mercurial.cext',
944 'mercurial.cext',
945 #'mercurial.cffi',
945 #'mercurial.cffi',
946 'mercurial.pure']},
946 'mercurial.pure']},
947 'bdist_mpkg': {'zipdist': False,
947 'bdist_mpkg': {'zipdist': False,
948 'license': 'COPYING',
948 'license': 'COPYING',
949 'readme': 'contrib/macosx/Readme.html',
949 'readme': 'contrib/macosx/Readme.html',
950 'welcome': 'contrib/macosx/Welcome.html',
950 'welcome': 'contrib/macosx/Welcome.html',
951 },
951 },
952 },
952 },
953 **extra)
953 **extra)
@@ -1,2131 +1,2131 b''
1 > do_push()
1 > do_push()
2 > {
2 > {
3 > user=$1
3 > user=$1
4 > shift
4 > shift
5 > echo "Pushing as user $user"
5 > echo "Pushing as user $user"
6 > echo 'hgrc = """'
6 > echo 'hgrc = """'
7 > sed -n '/\[[ha]/,$p' b/.hg/hgrc | grep -v fakegroups.py
7 > sed -n '/\[[ha]/,$p' b/.hg/hgrc | grep -v fakegroups.py
8 > echo '"""'
8 > echo '"""'
9 > if test -f acl.config; then
9 > if test -f acl.config; then
10 > echo 'acl.config = """'
10 > echo 'acl.config = """'
11 > cat acl.config
11 > cat acl.config
12 > echo '"""'
12 > echo '"""'
13 > fi
13 > fi
14 > # On AIX /etc/profile sets LOGNAME read-only. So
14 > # On AIX /etc/profile sets LOGNAME read-only. So
15 > # LOGNAME=$user hg --cws a --debug push ../b
15 > # LOGNAME=$user hg --cws a --debug push ../b
16 > # fails with "This variable is read only."
16 > # fails with "This variable is read only."
17 > # Use env to work around this.
17 > # Use env to work around this.
18 > env LOGNAME=$user hg --cwd a --debug push ../b
18 > env LOGNAME=$user hg --cwd a --debug push ../b
19 > hg --cwd b rollback
19 > hg --cwd b rollback
20 > hg --cwd b --quiet tip
20 > hg --cwd b --quiet tip
21 > echo
21 > echo
22 > }
22 > }
23
23
24 > init_config()
24 > init_config()
25 > {
25 > {
26 > cat > fakegroups.py <<EOF
26 > cat > fakegroups.py <<EOF
27 > from hgext import acl
27 > from hgext import acl
28 > def fakegetusers(ui, group):
28 > def fakegetusers(ui, group):
29 > try:
29 > try:
30 > return acl._getusersorig(ui, group)
30 > return acl._getusersorig(ui, group)
31 > except:
31 > except:
32 > return ["fred", "betty"]
32 > return ["fred", "betty"]
33 > acl._getusersorig = acl._getusers
33 > acl._getusersorig = acl._getusers
34 > acl._getusers = fakegetusers
34 > acl._getusers = fakegetusers
35 > EOF
35 > EOF
36 > rm -f acl.config
36 > rm -f acl.config
37 > cat > $config <<EOF
37 > cat > $config <<EOF
38 > [hooks]
38 > [hooks]
39 > pretxnchangegroup.acl = python:hgext.acl.hook
39 > pretxnchangegroup.acl = python:hgext.acl.hook
40 > [acl]
40 > [acl]
41 > sources = push
41 > sources = push
42 > [extensions]
42 > [extensions]
43 > f=`pwd`/fakegroups.py
43 > f=`pwd`/fakegroups.py
44 > EOF
44 > EOF
45 > }
45 > }
46
46
47 $ hg init a
47 $ hg init a
48 $ cd a
48 $ cd a
49 $ mkdir foo foo/Bar quux
49 $ mkdir foo foo/Bar quux
50 $ echo 'in foo' > foo/file.txt
50 $ echo 'in foo' > foo/file.txt
51 $ echo 'in foo/Bar' > foo/Bar/file.txt
51 $ echo 'in foo/Bar' > foo/Bar/file.txt
52 $ echo 'in quux' > quux/file.py
52 $ echo 'in quux' > quux/file.py
53 $ hg add -q
53 $ hg add -q
54 $ hg ci -m 'add files' -d '1000000 0'
54 $ hg ci -m 'add files' -d '1000000 0'
55 $ echo >> foo/file.txt
55 $ echo >> foo/file.txt
56 $ hg ci -m 'change foo/file' -d '1000001 0'
56 $ hg ci -m 'change foo/file' -d '1000001 0'
57 $ echo >> foo/Bar/file.txt
57 $ echo >> foo/Bar/file.txt
58 $ hg ci -m 'change foo/Bar/file' -d '1000002 0'
58 $ hg ci -m 'change foo/Bar/file' -d '1000002 0'
59 $ echo >> quux/file.py
59 $ echo >> quux/file.py
60 $ hg ci -m 'change quux/file' -d '1000003 0'
60 $ hg ci -m 'change quux/file' -d '1000003 0'
61 $ hg tip --quiet
61 $ hg tip --quiet
62 3:911600dab2ae
62 3:911600dab2ae
63
63
64 $ cd ..
64 $ cd ..
65 $ hg clone -r 0 a b
65 $ hg clone -r 0 a b
66 adding changesets
66 adding changesets
67 adding manifests
67 adding manifests
68 adding file changes
68 adding file changes
69 added 1 changesets with 3 changes to 3 files
69 added 1 changesets with 3 changes to 3 files
70 updating to branch default
70 updating to branch default
71 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
71 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
72
72
73 $ config=b/.hg/hgrc
73 $ config=b/.hg/hgrc
74
74
75 Extension disabled for lack of a hook
75 Extension disabled for lack of a hook
76
76
77 $ do_push fred
77 $ do_push fred
78 Pushing as user fred
78 Pushing as user fred
79 hgrc = """
79 hgrc = """
80 """
80 """
81 pushing to ../b
81 pushing to ../b
82 query 1; heads
82 query 1; heads
83 searching for changes
83 searching for changes
84 all remote heads known locally
84 all remote heads known locally
85 listing keys for "phases"
85 listing keys for "phases"
86 checking for updated bookmarks
86 checking for updated bookmarks
87 listing keys for "bookmarks"
87 listing keys for "bookmarks"
88 listing keys for "bookmarks"
88 listing keys for "bookmarks"
89 3 changesets found
89 3 changesets found
90 list of changesets:
90 list of changesets:
91 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
91 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
92 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
92 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
93 911600dab2ae7a9baff75958b84fe606851ce955
93 911600dab2ae7a9baff75958b84fe606851ce955
94 bundle2-output-bundle: "HG20", 4 parts total
94 bundle2-output-bundle: "HG20", 4 parts total
95 bundle2-output-part: "replycaps" 155 bytes payload
95 bundle2-output-part: "replycaps" 155 bytes payload
96 bundle2-output-part: "check:heads" streamed payload
96 bundle2-output-part: "check:heads" streamed payload
97 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
97 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
98 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
98 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
99 bundle2-input-bundle: with-transaction
99 bundle2-input-bundle: with-transaction
100 bundle2-input-part: "replycaps" supported
100 bundle2-input-part: "replycaps" supported
101 bundle2-input-part: total payload size 155
101 bundle2-input-part: total payload size 155
102 bundle2-input-part: "check:heads" supported
102 bundle2-input-part: "check:heads" supported
103 bundle2-input-part: total payload size 20
103 bundle2-input-part: total payload size 20
104 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
104 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
105 adding changesets
105 adding changesets
106 add changeset ef1ea85a6374
106 add changeset ef1ea85a6374
107 add changeset f9cafe1212c8
107 add changeset f9cafe1212c8
108 add changeset 911600dab2ae
108 add changeset 911600dab2ae
109 adding manifests
109 adding manifests
110 adding file changes
110 adding file changes
111 adding foo/Bar/file.txt revisions
111 adding foo/Bar/file.txt revisions
112 adding foo/file.txt revisions
112 adding foo/file.txt revisions
113 adding quux/file.py revisions
113 adding quux/file.py revisions
114 added 3 changesets with 3 changes to 3 files
114 added 3 changesets with 3 changes to 3 files
115 bundle2-input-part: total payload size 1553
115 bundle2-input-part: total payload size 1553
116 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
116 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
117 pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
117 pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
118 bundle2-input-bundle: 3 parts total
118 bundle2-input-bundle: 3 parts total
119 updating the branch cache
119 updating the branch cache
120 bundle2-output-bundle: "HG20", 2 parts total
120 bundle2-output-bundle: "HG20", 2 parts total
121 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
121 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
122 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
122 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
123 bundle2-input-bundle: no-transaction
123 bundle2-input-bundle: no-transaction
124 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
124 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
125 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
125 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
126 bundle2-input-bundle: 1 parts total
126 bundle2-input-bundle: 1 parts total
127 listing keys for "phases"
127 listing keys for "phases"
128 repository tip rolled back to revision 0 (undo push)
128 repository tip rolled back to revision 0 (undo push)
129 0:6675d58eff77
129 0:6675d58eff77
130
130
131
131
132 $ echo '[hooks]' >> $config
132 $ echo '[hooks]' >> $config
133 $ echo 'pretxnchangegroup.acl = python:hgext.acl.hook' >> $config
133 $ echo 'pretxnchangegroup.acl = python:hgext.acl.hook' >> $config
134
134
135 Extension disabled for lack of acl.sources
135 Extension disabled for lack of acl.sources
136
136
137 $ do_push fred
137 $ do_push fred
138 Pushing as user fred
138 Pushing as user fred
139 hgrc = """
139 hgrc = """
140 [hooks]
140 [hooks]
141 pretxnchangegroup.acl = python:hgext.acl.hook
141 pretxnchangegroup.acl = python:hgext.acl.hook
142 """
142 """
143 pushing to ../b
143 pushing to ../b
144 query 1; heads
144 query 1; heads
145 searching for changes
145 searching for changes
146 all remote heads known locally
146 all remote heads known locally
147 listing keys for "phases"
147 listing keys for "phases"
148 checking for updated bookmarks
148 checking for updated bookmarks
149 listing keys for "bookmarks"
149 listing keys for "bookmarks"
150 listing keys for "bookmarks"
150 listing keys for "bookmarks"
151 3 changesets found
151 3 changesets found
152 list of changesets:
152 list of changesets:
153 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
153 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
154 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
154 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
155 911600dab2ae7a9baff75958b84fe606851ce955
155 911600dab2ae7a9baff75958b84fe606851ce955
156 bundle2-output-bundle: "HG20", 4 parts total
156 bundle2-output-bundle: "HG20", 4 parts total
157 bundle2-output-part: "replycaps" 155 bytes payload
157 bundle2-output-part: "replycaps" 155 bytes payload
158 bundle2-output-part: "check:heads" streamed payload
158 bundle2-output-part: "check:heads" streamed payload
159 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
159 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
160 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
160 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
161 bundle2-input-bundle: with-transaction
161 bundle2-input-bundle: with-transaction
162 bundle2-input-part: "replycaps" supported
162 bundle2-input-part: "replycaps" supported
163 bundle2-input-part: total payload size 155
163 bundle2-input-part: total payload size 155
164 bundle2-input-part: "check:heads" supported
164 bundle2-input-part: "check:heads" supported
165 bundle2-input-part: total payload size 20
165 bundle2-input-part: total payload size 20
166 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
166 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
167 adding changesets
167 adding changesets
168 add changeset ef1ea85a6374
168 add changeset ef1ea85a6374
169 add changeset f9cafe1212c8
169 add changeset f9cafe1212c8
170 add changeset 911600dab2ae
170 add changeset 911600dab2ae
171 adding manifests
171 adding manifests
172 adding file changes
172 adding file changes
173 adding foo/Bar/file.txt revisions
173 adding foo/Bar/file.txt revisions
174 adding foo/file.txt revisions
174 adding foo/file.txt revisions
175 adding quux/file.py revisions
175 adding quux/file.py revisions
176 added 3 changesets with 3 changes to 3 files
176 added 3 changesets with 3 changes to 3 files
177 calling hook pretxnchangegroup.acl: hgext.acl.hook
177 calling hook pretxnchangegroup.acl: hgext.acl.hook
178 acl: changes have source "push" - skipping
178 acl: changes have source "push" - skipping
179 bundle2-input-part: total payload size 1553
179 bundle2-input-part: total payload size 1553
180 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
180 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
181 pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
181 pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
182 bundle2-input-bundle: 3 parts total
182 bundle2-input-bundle: 3 parts total
183 updating the branch cache
183 updating the branch cache
184 bundle2-output-bundle: "HG20", 2 parts total
184 bundle2-output-bundle: "HG20", 2 parts total
185 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
185 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
186 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
186 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
187 bundle2-input-bundle: no-transaction
187 bundle2-input-bundle: no-transaction
188 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
188 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
189 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
189 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
190 bundle2-input-bundle: 1 parts total
190 bundle2-input-bundle: 1 parts total
191 listing keys for "phases"
191 listing keys for "phases"
192 repository tip rolled back to revision 0 (undo push)
192 repository tip rolled back to revision 0 (undo push)
193 0:6675d58eff77
193 0:6675d58eff77
194
194
195
195
196 No [acl.allow]/[acl.deny]
196 No [acl.allow]/[acl.deny]
197
197
198 $ echo '[acl]' >> $config
198 $ echo '[acl]' >> $config
199 $ echo 'sources = push' >> $config
199 $ echo 'sources = push' >> $config
200 $ do_push fred
200 $ do_push fred
201 Pushing as user fred
201 Pushing as user fred
202 hgrc = """
202 hgrc = """
203 [hooks]
203 [hooks]
204 pretxnchangegroup.acl = python:hgext.acl.hook
204 pretxnchangegroup.acl = python:hgext.acl.hook
205 [acl]
205 [acl]
206 sources = push
206 sources = push
207 """
207 """
208 pushing to ../b
208 pushing to ../b
209 query 1; heads
209 query 1; heads
210 searching for changes
210 searching for changes
211 all remote heads known locally
211 all remote heads known locally
212 listing keys for "phases"
212 listing keys for "phases"
213 checking for updated bookmarks
213 checking for updated bookmarks
214 listing keys for "bookmarks"
214 listing keys for "bookmarks"
215 listing keys for "bookmarks"
215 listing keys for "bookmarks"
216 3 changesets found
216 3 changesets found
217 list of changesets:
217 list of changesets:
218 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
218 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
219 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
219 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
220 911600dab2ae7a9baff75958b84fe606851ce955
220 911600dab2ae7a9baff75958b84fe606851ce955
221 bundle2-output-bundle: "HG20", 4 parts total
221 bundle2-output-bundle: "HG20", 4 parts total
222 bundle2-output-part: "replycaps" 155 bytes payload
222 bundle2-output-part: "replycaps" 155 bytes payload
223 bundle2-output-part: "check:heads" streamed payload
223 bundle2-output-part: "check:heads" streamed payload
224 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
224 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
225 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
225 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
226 bundle2-input-bundle: with-transaction
226 bundle2-input-bundle: with-transaction
227 bundle2-input-part: "replycaps" supported
227 bundle2-input-part: "replycaps" supported
228 bundle2-input-part: total payload size 155
228 bundle2-input-part: total payload size 155
229 bundle2-input-part: "check:heads" supported
229 bundle2-input-part: "check:heads" supported
230 bundle2-input-part: total payload size 20
230 bundle2-input-part: total payload size 20
231 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
231 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
232 adding changesets
232 adding changesets
233 add changeset ef1ea85a6374
233 add changeset ef1ea85a6374
234 add changeset f9cafe1212c8
234 add changeset f9cafe1212c8
235 add changeset 911600dab2ae
235 add changeset 911600dab2ae
236 adding manifests
236 adding manifests
237 adding file changes
237 adding file changes
238 adding foo/Bar/file.txt revisions
238 adding foo/Bar/file.txt revisions
239 adding foo/file.txt revisions
239 adding foo/file.txt revisions
240 adding quux/file.py revisions
240 adding quux/file.py revisions
241 added 3 changesets with 3 changes to 3 files
241 added 3 changesets with 3 changes to 3 files
242 calling hook pretxnchangegroup.acl: hgext.acl.hook
242 calling hook pretxnchangegroup.acl: hgext.acl.hook
243 acl: checking access for user "fred"
243 acl: checking access for user "fred"
244 acl: acl.allow.branches not enabled
244 acl: acl.allow.branches not enabled
245 acl: acl.deny.branches not enabled
245 acl: acl.deny.branches not enabled
246 acl: acl.allow not enabled
246 acl: acl.allow not enabled
247 acl: acl.deny not enabled
247 acl: acl.deny not enabled
248 acl: branch access granted: "ef1ea85a6374" on branch "default"
248 acl: branch access granted: "ef1ea85a6374" on branch "default"
249 acl: path access granted: "ef1ea85a6374"
249 acl: path access granted: "ef1ea85a6374"
250 acl: branch access granted: "f9cafe1212c8" on branch "default"
250 acl: branch access granted: "f9cafe1212c8" on branch "default"
251 acl: path access granted: "f9cafe1212c8"
251 acl: path access granted: "f9cafe1212c8"
252 acl: branch access granted: "911600dab2ae" on branch "default"
252 acl: branch access granted: "911600dab2ae" on branch "default"
253 acl: path access granted: "911600dab2ae"
253 acl: path access granted: "911600dab2ae"
254 bundle2-input-part: total payload size 1553
254 bundle2-input-part: total payload size 1553
255 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
255 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
256 pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
256 pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
257 bundle2-input-bundle: 3 parts total
257 bundle2-input-bundle: 3 parts total
258 updating the branch cache
258 updating the branch cache
259 bundle2-output-bundle: "HG20", 2 parts total
259 bundle2-output-bundle: "HG20", 2 parts total
260 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
260 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
261 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
261 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
262 bundle2-input-bundle: no-transaction
262 bundle2-input-bundle: no-transaction
263 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
263 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
264 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
264 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
265 bundle2-input-bundle: 1 parts total
265 bundle2-input-bundle: 1 parts total
266 listing keys for "phases"
266 listing keys for "phases"
267 repository tip rolled back to revision 0 (undo push)
267 repository tip rolled back to revision 0 (undo push)
268 0:6675d58eff77
268 0:6675d58eff77
269
269
270
270
271 Empty [acl.allow]
271 Empty [acl.allow]
272
272
273 $ echo '[acl.allow]' >> $config
273 $ echo '[acl.allow]' >> $config
274 $ do_push fred
274 $ do_push fred
275 Pushing as user fred
275 Pushing as user fred
276 hgrc = """
276 hgrc = """
277 [hooks]
277 [hooks]
278 pretxnchangegroup.acl = python:hgext.acl.hook
278 pretxnchangegroup.acl = python:hgext.acl.hook
279 [acl]
279 [acl]
280 sources = push
280 sources = push
281 [acl.allow]
281 [acl.allow]
282 """
282 """
283 pushing to ../b
283 pushing to ../b
284 query 1; heads
284 query 1; heads
285 searching for changes
285 searching for changes
286 all remote heads known locally
286 all remote heads known locally
287 listing keys for "phases"
287 listing keys for "phases"
288 checking for updated bookmarks
288 checking for updated bookmarks
289 listing keys for "bookmarks"
289 listing keys for "bookmarks"
290 listing keys for "bookmarks"
290 listing keys for "bookmarks"
291 3 changesets found
291 3 changesets found
292 list of changesets:
292 list of changesets:
293 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
293 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
294 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
294 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
295 911600dab2ae7a9baff75958b84fe606851ce955
295 911600dab2ae7a9baff75958b84fe606851ce955
296 bundle2-output-bundle: "HG20", 4 parts total
296 bundle2-output-bundle: "HG20", 4 parts total
297 bundle2-output-part: "replycaps" 155 bytes payload
297 bundle2-output-part: "replycaps" 155 bytes payload
298 bundle2-output-part: "check:heads" streamed payload
298 bundle2-output-part: "check:heads" streamed payload
299 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
299 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
300 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
300 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
301 bundle2-input-bundle: with-transaction
301 bundle2-input-bundle: with-transaction
302 bundle2-input-part: "replycaps" supported
302 bundle2-input-part: "replycaps" supported
303 bundle2-input-part: total payload size 155
303 bundle2-input-part: total payload size 155
304 bundle2-input-part: "check:heads" supported
304 bundle2-input-part: "check:heads" supported
305 bundle2-input-part: total payload size 20
305 bundle2-input-part: total payload size 20
306 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
306 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
307 adding changesets
307 adding changesets
308 add changeset ef1ea85a6374
308 add changeset ef1ea85a6374
309 add changeset f9cafe1212c8
309 add changeset f9cafe1212c8
310 add changeset 911600dab2ae
310 add changeset 911600dab2ae
311 adding manifests
311 adding manifests
312 adding file changes
312 adding file changes
313 adding foo/Bar/file.txt revisions
313 adding foo/Bar/file.txt revisions
314 adding foo/file.txt revisions
314 adding foo/file.txt revisions
315 adding quux/file.py revisions
315 adding quux/file.py revisions
316 added 3 changesets with 3 changes to 3 files
316 added 3 changesets with 3 changes to 3 files
317 calling hook pretxnchangegroup.acl: hgext.acl.hook
317 calling hook pretxnchangegroup.acl: hgext.acl.hook
318 acl: checking access for user "fred"
318 acl: checking access for user "fred"
319 acl: acl.allow.branches not enabled
319 acl: acl.allow.branches not enabled
320 acl: acl.deny.branches not enabled
320 acl: acl.deny.branches not enabled
321 acl: acl.allow enabled, 0 entries for user fred
321 acl: acl.allow enabled, 0 entries for user fred
322 acl: acl.deny not enabled
322 acl: acl.deny not enabled
323 acl: branch access granted: "ef1ea85a6374" on branch "default"
323 acl: branch access granted: "ef1ea85a6374" on branch "default"
324 error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
324 error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
325 bundle2-input-part: total payload size 1553
325 bundle2-input-part: total payload size 1553
326 bundle2-input-bundle: 3 parts total
326 bundle2-input-bundle: 3 parts total
327 transaction abort!
327 transaction abort!
328 rollback completed
328 rollback completed
329 abort: acl: user "fred" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
329 abort: acl: user "fred" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
330 no rollback information available
330 no rollback information available
331 0:6675d58eff77
331 0:6675d58eff77
332
332
333
333
334 fred is allowed inside foo/
334 fred is allowed inside foo/
335
335
336 $ echo 'foo/** = fred' >> $config
336 $ echo 'foo/** = fred' >> $config
337 $ do_push fred
337 $ do_push fred
338 Pushing as user fred
338 Pushing as user fred
339 hgrc = """
339 hgrc = """
340 [hooks]
340 [hooks]
341 pretxnchangegroup.acl = python:hgext.acl.hook
341 pretxnchangegroup.acl = python:hgext.acl.hook
342 [acl]
342 [acl]
343 sources = push
343 sources = push
344 [acl.allow]
344 [acl.allow]
345 foo/** = fred
345 foo/** = fred
346 """
346 """
347 pushing to ../b
347 pushing to ../b
348 query 1; heads
348 query 1; heads
349 searching for changes
349 searching for changes
350 all remote heads known locally
350 all remote heads known locally
351 listing keys for "phases"
351 listing keys for "phases"
352 checking for updated bookmarks
352 checking for updated bookmarks
353 listing keys for "bookmarks"
353 listing keys for "bookmarks"
354 listing keys for "bookmarks"
354 listing keys for "bookmarks"
355 3 changesets found
355 3 changesets found
356 list of changesets:
356 list of changesets:
357 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
357 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
358 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
358 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
359 911600dab2ae7a9baff75958b84fe606851ce955
359 911600dab2ae7a9baff75958b84fe606851ce955
360 bundle2-output-bundle: "HG20", 4 parts total
360 bundle2-output-bundle: "HG20", 4 parts total
361 bundle2-output-part: "replycaps" 155 bytes payload
361 bundle2-output-part: "replycaps" 155 bytes payload
362 bundle2-output-part: "check:heads" streamed payload
362 bundle2-output-part: "check:heads" streamed payload
363 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
363 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
364 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
364 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
365 bundle2-input-bundle: with-transaction
365 bundle2-input-bundle: with-transaction
366 bundle2-input-part: "replycaps" supported
366 bundle2-input-part: "replycaps" supported
367 bundle2-input-part: total payload size 155
367 bundle2-input-part: total payload size 155
368 bundle2-input-part: "check:heads" supported
368 bundle2-input-part: "check:heads" supported
369 bundle2-input-part: total payload size 20
369 bundle2-input-part: total payload size 20
370 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
370 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
371 adding changesets
371 adding changesets
372 add changeset ef1ea85a6374
372 add changeset ef1ea85a6374
373 add changeset f9cafe1212c8
373 add changeset f9cafe1212c8
374 add changeset 911600dab2ae
374 add changeset 911600dab2ae
375 adding manifests
375 adding manifests
376 adding file changes
376 adding file changes
377 adding foo/Bar/file.txt revisions
377 adding foo/Bar/file.txt revisions
378 adding foo/file.txt revisions
378 adding foo/file.txt revisions
379 adding quux/file.py revisions
379 adding quux/file.py revisions
380 added 3 changesets with 3 changes to 3 files
380 added 3 changesets with 3 changes to 3 files
381 calling hook pretxnchangegroup.acl: hgext.acl.hook
381 calling hook pretxnchangegroup.acl: hgext.acl.hook
382 acl: checking access for user "fred"
382 acl: checking access for user "fred"
383 acl: acl.allow.branches not enabled
383 acl: acl.allow.branches not enabled
384 acl: acl.deny.branches not enabled
384 acl: acl.deny.branches not enabled
385 acl: acl.allow enabled, 1 entries for user fred
385 acl: acl.allow enabled, 1 entries for user fred
386 acl: acl.deny not enabled
386 acl: acl.deny not enabled
387 acl: branch access granted: "ef1ea85a6374" on branch "default"
387 acl: branch access granted: "ef1ea85a6374" on branch "default"
388 acl: path access granted: "ef1ea85a6374"
388 acl: path access granted: "ef1ea85a6374"
389 acl: branch access granted: "f9cafe1212c8" on branch "default"
389 acl: branch access granted: "f9cafe1212c8" on branch "default"
390 acl: path access granted: "f9cafe1212c8"
390 acl: path access granted: "f9cafe1212c8"
391 acl: branch access granted: "911600dab2ae" on branch "default"
391 acl: branch access granted: "911600dab2ae" on branch "default"
392 error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
392 error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
393 bundle2-input-part: total payload size 1553
393 bundle2-input-part: total payload size 1553
394 bundle2-input-bundle: 3 parts total
394 bundle2-input-bundle: 3 parts total
395 transaction abort!
395 transaction abort!
396 rollback completed
396 rollback completed
397 abort: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
397 abort: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
398 no rollback information available
398 no rollback information available
399 0:6675d58eff77
399 0:6675d58eff77
400
400
401
401
402 Empty [acl.deny]
402 Empty [acl.deny]
403
403
404 $ echo '[acl.deny]' >> $config
404 $ echo '[acl.deny]' >> $config
405 $ do_push barney
405 $ do_push barney
406 Pushing as user barney
406 Pushing as user barney
407 hgrc = """
407 hgrc = """
408 [hooks]
408 [hooks]
409 pretxnchangegroup.acl = python:hgext.acl.hook
409 pretxnchangegroup.acl = python:hgext.acl.hook
410 [acl]
410 [acl]
411 sources = push
411 sources = push
412 [acl.allow]
412 [acl.allow]
413 foo/** = fred
413 foo/** = fred
414 [acl.deny]
414 [acl.deny]
415 """
415 """
416 pushing to ../b
416 pushing to ../b
417 query 1; heads
417 query 1; heads
418 searching for changes
418 searching for changes
419 all remote heads known locally
419 all remote heads known locally
420 listing keys for "phases"
420 listing keys for "phases"
421 checking for updated bookmarks
421 checking for updated bookmarks
422 listing keys for "bookmarks"
422 listing keys for "bookmarks"
423 listing keys for "bookmarks"
423 listing keys for "bookmarks"
424 3 changesets found
424 3 changesets found
425 list of changesets:
425 list of changesets:
426 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
426 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
427 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
427 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
428 911600dab2ae7a9baff75958b84fe606851ce955
428 911600dab2ae7a9baff75958b84fe606851ce955
429 bundle2-output-bundle: "HG20", 4 parts total
429 bundle2-output-bundle: "HG20", 4 parts total
430 bundle2-output-part: "replycaps" 155 bytes payload
430 bundle2-output-part: "replycaps" 155 bytes payload
431 bundle2-output-part: "check:heads" streamed payload
431 bundle2-output-part: "check:heads" streamed payload
432 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
432 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
433 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
433 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
434 bundle2-input-bundle: with-transaction
434 bundle2-input-bundle: with-transaction
435 bundle2-input-part: "replycaps" supported
435 bundle2-input-part: "replycaps" supported
436 bundle2-input-part: total payload size 155
436 bundle2-input-part: total payload size 155
437 bundle2-input-part: "check:heads" supported
437 bundle2-input-part: "check:heads" supported
438 bundle2-input-part: total payload size 20
438 bundle2-input-part: total payload size 20
439 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
439 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
440 adding changesets
440 adding changesets
441 add changeset ef1ea85a6374
441 add changeset ef1ea85a6374
442 add changeset f9cafe1212c8
442 add changeset f9cafe1212c8
443 add changeset 911600dab2ae
443 add changeset 911600dab2ae
444 adding manifests
444 adding manifests
445 adding file changes
445 adding file changes
446 adding foo/Bar/file.txt revisions
446 adding foo/Bar/file.txt revisions
447 adding foo/file.txt revisions
447 adding foo/file.txt revisions
448 adding quux/file.py revisions
448 adding quux/file.py revisions
449 added 3 changesets with 3 changes to 3 files
449 added 3 changesets with 3 changes to 3 files
450 calling hook pretxnchangegroup.acl: hgext.acl.hook
450 calling hook pretxnchangegroup.acl: hgext.acl.hook
451 acl: checking access for user "barney"
451 acl: checking access for user "barney"
452 acl: acl.allow.branches not enabled
452 acl: acl.allow.branches not enabled
453 acl: acl.deny.branches not enabled
453 acl: acl.deny.branches not enabled
454 acl: acl.allow enabled, 0 entries for user barney
454 acl: acl.allow enabled, 0 entries for user barney
455 acl: acl.deny enabled, 0 entries for user barney
455 acl: acl.deny enabled, 0 entries for user barney
456 acl: branch access granted: "ef1ea85a6374" on branch "default"
456 acl: branch access granted: "ef1ea85a6374" on branch "default"
457 error: pretxnchangegroup.acl hook failed: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
457 error: pretxnchangegroup.acl hook failed: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
458 bundle2-input-part: total payload size 1553
458 bundle2-input-part: total payload size 1553
459 bundle2-input-bundle: 3 parts total
459 bundle2-input-bundle: 3 parts total
460 transaction abort!
460 transaction abort!
461 rollback completed
461 rollback completed
462 abort: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
462 abort: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
463 no rollback information available
463 no rollback information available
464 0:6675d58eff77
464 0:6675d58eff77
465
465
466
466
467 fred is allowed inside foo/, but not foo/bar/ (case matters)
467 fred is allowed inside foo/, but not foo/bar/ (case matters)
468
468
469 $ echo 'foo/bar/** = fred' >> $config
469 $ echo 'foo/bar/** = fred' >> $config
470 $ do_push fred
470 $ do_push fred
471 Pushing as user fred
471 Pushing as user fred
472 hgrc = """
472 hgrc = """
473 [hooks]
473 [hooks]
474 pretxnchangegroup.acl = python:hgext.acl.hook
474 pretxnchangegroup.acl = python:hgext.acl.hook
475 [acl]
475 [acl]
476 sources = push
476 sources = push
477 [acl.allow]
477 [acl.allow]
478 foo/** = fred
478 foo/** = fred
479 [acl.deny]
479 [acl.deny]
480 foo/bar/** = fred
480 foo/bar/** = fred
481 """
481 """
482 pushing to ../b
482 pushing to ../b
483 query 1; heads
483 query 1; heads
484 searching for changes
484 searching for changes
485 all remote heads known locally
485 all remote heads known locally
486 listing keys for "phases"
486 listing keys for "phases"
487 checking for updated bookmarks
487 checking for updated bookmarks
488 listing keys for "bookmarks"
488 listing keys for "bookmarks"
489 listing keys for "bookmarks"
489 listing keys for "bookmarks"
490 3 changesets found
490 3 changesets found
491 list of changesets:
491 list of changesets:
492 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
492 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
493 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
493 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
494 911600dab2ae7a9baff75958b84fe606851ce955
494 911600dab2ae7a9baff75958b84fe606851ce955
495 bundle2-output-bundle: "HG20", 4 parts total
495 bundle2-output-bundle: "HG20", 4 parts total
496 bundle2-output-part: "replycaps" 155 bytes payload
496 bundle2-output-part: "replycaps" 155 bytes payload
497 bundle2-output-part: "check:heads" streamed payload
497 bundle2-output-part: "check:heads" streamed payload
498 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
498 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
499 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
499 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
500 bundle2-input-bundle: with-transaction
500 bundle2-input-bundle: with-transaction
501 bundle2-input-part: "replycaps" supported
501 bundle2-input-part: "replycaps" supported
502 bundle2-input-part: total payload size 155
502 bundle2-input-part: total payload size 155
503 bundle2-input-part: "check:heads" supported
503 bundle2-input-part: "check:heads" supported
504 bundle2-input-part: total payload size 20
504 bundle2-input-part: total payload size 20
505 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
505 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
506 adding changesets
506 adding changesets
507 add changeset ef1ea85a6374
507 add changeset ef1ea85a6374
508 add changeset f9cafe1212c8
508 add changeset f9cafe1212c8
509 add changeset 911600dab2ae
509 add changeset 911600dab2ae
510 adding manifests
510 adding manifests
511 adding file changes
511 adding file changes
512 adding foo/Bar/file.txt revisions
512 adding foo/Bar/file.txt revisions
513 adding foo/file.txt revisions
513 adding foo/file.txt revisions
514 adding quux/file.py revisions
514 adding quux/file.py revisions
515 added 3 changesets with 3 changes to 3 files
515 added 3 changesets with 3 changes to 3 files
516 calling hook pretxnchangegroup.acl: hgext.acl.hook
516 calling hook pretxnchangegroup.acl: hgext.acl.hook
517 acl: checking access for user "fred"
517 acl: checking access for user "fred"
518 acl: acl.allow.branches not enabled
518 acl: acl.allow.branches not enabled
519 acl: acl.deny.branches not enabled
519 acl: acl.deny.branches not enabled
520 acl: acl.allow enabled, 1 entries for user fred
520 acl: acl.allow enabled, 1 entries for user fred
521 acl: acl.deny enabled, 1 entries for user fred
521 acl: acl.deny enabled, 1 entries for user fred
522 acl: branch access granted: "ef1ea85a6374" on branch "default"
522 acl: branch access granted: "ef1ea85a6374" on branch "default"
523 acl: path access granted: "ef1ea85a6374"
523 acl: path access granted: "ef1ea85a6374"
524 acl: branch access granted: "f9cafe1212c8" on branch "default"
524 acl: branch access granted: "f9cafe1212c8" on branch "default"
525 acl: path access granted: "f9cafe1212c8"
525 acl: path access granted: "f9cafe1212c8"
526 acl: branch access granted: "911600dab2ae" on branch "default"
526 acl: branch access granted: "911600dab2ae" on branch "default"
527 error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
527 error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
528 bundle2-input-part: total payload size 1553
528 bundle2-input-part: total payload size 1553
529 bundle2-input-bundle: 3 parts total
529 bundle2-input-bundle: 3 parts total
530 transaction abort!
530 transaction abort!
531 rollback completed
531 rollback completed
532 abort: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
532 abort: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
533 no rollback information available
533 no rollback information available
534 0:6675d58eff77
534 0:6675d58eff77
535
535
536
536
537 fred is allowed inside foo/, but not foo/Bar/
537 fred is allowed inside foo/, but not foo/Bar/
538
538
539 $ echo 'foo/Bar/** = fred' >> $config
539 $ echo 'foo/Bar/** = fred' >> $config
540 $ do_push fred
540 $ do_push fred
541 Pushing as user fred
541 Pushing as user fred
542 hgrc = """
542 hgrc = """
543 [hooks]
543 [hooks]
544 pretxnchangegroup.acl = python:hgext.acl.hook
544 pretxnchangegroup.acl = python:hgext.acl.hook
545 [acl]
545 [acl]
546 sources = push
546 sources = push
547 [acl.allow]
547 [acl.allow]
548 foo/** = fred
548 foo/** = fred
549 [acl.deny]
549 [acl.deny]
550 foo/bar/** = fred
550 foo/bar/** = fred
551 foo/Bar/** = fred
551 foo/Bar/** = fred
552 """
552 """
553 pushing to ../b
553 pushing to ../b
554 query 1; heads
554 query 1; heads
555 searching for changes
555 searching for changes
556 all remote heads known locally
556 all remote heads known locally
557 listing keys for "phases"
557 listing keys for "phases"
558 checking for updated bookmarks
558 checking for updated bookmarks
559 listing keys for "bookmarks"
559 listing keys for "bookmarks"
560 listing keys for "bookmarks"
560 listing keys for "bookmarks"
561 3 changesets found
561 3 changesets found
562 list of changesets:
562 list of changesets:
563 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
563 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
564 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
564 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
565 911600dab2ae7a9baff75958b84fe606851ce955
565 911600dab2ae7a9baff75958b84fe606851ce955
566 bundle2-output-bundle: "HG20", 4 parts total
566 bundle2-output-bundle: "HG20", 4 parts total
567 bundle2-output-part: "replycaps" 155 bytes payload
567 bundle2-output-part: "replycaps" 155 bytes payload
568 bundle2-output-part: "check:heads" streamed payload
568 bundle2-output-part: "check:heads" streamed payload
569 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
569 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
570 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
570 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
571 bundle2-input-bundle: with-transaction
571 bundle2-input-bundle: with-transaction
572 bundle2-input-part: "replycaps" supported
572 bundle2-input-part: "replycaps" supported
573 bundle2-input-part: total payload size 155
573 bundle2-input-part: total payload size 155
574 bundle2-input-part: "check:heads" supported
574 bundle2-input-part: "check:heads" supported
575 bundle2-input-part: total payload size 20
575 bundle2-input-part: total payload size 20
576 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
576 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
577 adding changesets
577 adding changesets
578 add changeset ef1ea85a6374
578 add changeset ef1ea85a6374
579 add changeset f9cafe1212c8
579 add changeset f9cafe1212c8
580 add changeset 911600dab2ae
580 add changeset 911600dab2ae
581 adding manifests
581 adding manifests
582 adding file changes
582 adding file changes
583 adding foo/Bar/file.txt revisions
583 adding foo/Bar/file.txt revisions
584 adding foo/file.txt revisions
584 adding foo/file.txt revisions
585 adding quux/file.py revisions
585 adding quux/file.py revisions
586 added 3 changesets with 3 changes to 3 files
586 added 3 changesets with 3 changes to 3 files
587 calling hook pretxnchangegroup.acl: hgext.acl.hook
587 calling hook pretxnchangegroup.acl: hgext.acl.hook
588 acl: checking access for user "fred"
588 acl: checking access for user "fred"
589 acl: acl.allow.branches not enabled
589 acl: acl.allow.branches not enabled
590 acl: acl.deny.branches not enabled
590 acl: acl.deny.branches not enabled
591 acl: acl.allow enabled, 1 entries for user fred
591 acl: acl.allow enabled, 1 entries for user fred
592 acl: acl.deny enabled, 2 entries for user fred
592 acl: acl.deny enabled, 2 entries for user fred
593 acl: branch access granted: "ef1ea85a6374" on branch "default"
593 acl: branch access granted: "ef1ea85a6374" on branch "default"
594 acl: path access granted: "ef1ea85a6374"
594 acl: path access granted: "ef1ea85a6374"
595 acl: branch access granted: "f9cafe1212c8" on branch "default"
595 acl: branch access granted: "f9cafe1212c8" on branch "default"
596 error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
596 error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
597 bundle2-input-part: total payload size 1553
597 bundle2-input-part: total payload size 1553
598 bundle2-input-bundle: 3 parts total
598 bundle2-input-bundle: 3 parts total
599 transaction abort!
599 transaction abort!
600 rollback completed
600 rollback completed
601 abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
601 abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
602 no rollback information available
602 no rollback information available
603 0:6675d58eff77
603 0:6675d58eff77
604
604
605
605
606 $ echo 'barney is not mentioned => not allowed anywhere'
606 $ echo 'barney is not mentioned => not allowed anywhere'
607 barney is not mentioned => not allowed anywhere
607 barney is not mentioned => not allowed anywhere
608 $ do_push barney
608 $ do_push barney
609 Pushing as user barney
609 Pushing as user barney
610 hgrc = """
610 hgrc = """
611 [hooks]
611 [hooks]
612 pretxnchangegroup.acl = python:hgext.acl.hook
612 pretxnchangegroup.acl = python:hgext.acl.hook
613 [acl]
613 [acl]
614 sources = push
614 sources = push
615 [acl.allow]
615 [acl.allow]
616 foo/** = fred
616 foo/** = fred
617 [acl.deny]
617 [acl.deny]
618 foo/bar/** = fred
618 foo/bar/** = fred
619 foo/Bar/** = fred
619 foo/Bar/** = fred
620 """
620 """
621 pushing to ../b
621 pushing to ../b
622 query 1; heads
622 query 1; heads
623 searching for changes
623 searching for changes
624 all remote heads known locally
624 all remote heads known locally
625 listing keys for "phases"
625 listing keys for "phases"
626 checking for updated bookmarks
626 checking for updated bookmarks
627 listing keys for "bookmarks"
627 listing keys for "bookmarks"
628 listing keys for "bookmarks"
628 listing keys for "bookmarks"
629 3 changesets found
629 3 changesets found
630 list of changesets:
630 list of changesets:
631 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
631 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
632 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
632 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
633 911600dab2ae7a9baff75958b84fe606851ce955
633 911600dab2ae7a9baff75958b84fe606851ce955
634 bundle2-output-bundle: "HG20", 4 parts total
634 bundle2-output-bundle: "HG20", 4 parts total
635 bundle2-output-part: "replycaps" 155 bytes payload
635 bundle2-output-part: "replycaps" 155 bytes payload
636 bundle2-output-part: "check:heads" streamed payload
636 bundle2-output-part: "check:heads" streamed payload
637 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
637 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
638 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
638 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
639 bundle2-input-bundle: with-transaction
639 bundle2-input-bundle: with-transaction
640 bundle2-input-part: "replycaps" supported
640 bundle2-input-part: "replycaps" supported
641 bundle2-input-part: total payload size 155
641 bundle2-input-part: total payload size 155
642 bundle2-input-part: "check:heads" supported
642 bundle2-input-part: "check:heads" supported
643 bundle2-input-part: total payload size 20
643 bundle2-input-part: total payload size 20
644 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
644 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
645 adding changesets
645 adding changesets
646 add changeset ef1ea85a6374
646 add changeset ef1ea85a6374
647 add changeset f9cafe1212c8
647 add changeset f9cafe1212c8
648 add changeset 911600dab2ae
648 add changeset 911600dab2ae
649 adding manifests
649 adding manifests
650 adding file changes
650 adding file changes
651 adding foo/Bar/file.txt revisions
651 adding foo/Bar/file.txt revisions
652 adding foo/file.txt revisions
652 adding foo/file.txt revisions
653 adding quux/file.py revisions
653 adding quux/file.py revisions
654 added 3 changesets with 3 changes to 3 files
654 added 3 changesets with 3 changes to 3 files
655 calling hook pretxnchangegroup.acl: hgext.acl.hook
655 calling hook pretxnchangegroup.acl: hgext.acl.hook
656 acl: checking access for user "barney"
656 acl: checking access for user "barney"
657 acl: acl.allow.branches not enabled
657 acl: acl.allow.branches not enabled
658 acl: acl.deny.branches not enabled
658 acl: acl.deny.branches not enabled
659 acl: acl.allow enabled, 0 entries for user barney
659 acl: acl.allow enabled, 0 entries for user barney
660 acl: acl.deny enabled, 0 entries for user barney
660 acl: acl.deny enabled, 0 entries for user barney
661 acl: branch access granted: "ef1ea85a6374" on branch "default"
661 acl: branch access granted: "ef1ea85a6374" on branch "default"
662 error: pretxnchangegroup.acl hook failed: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
662 error: pretxnchangegroup.acl hook failed: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
663 bundle2-input-part: total payload size 1553
663 bundle2-input-part: total payload size 1553
664 bundle2-input-bundle: 3 parts total
664 bundle2-input-bundle: 3 parts total
665 transaction abort!
665 transaction abort!
666 rollback completed
666 rollback completed
667 abort: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
667 abort: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
668 no rollback information available
668 no rollback information available
669 0:6675d58eff77
669 0:6675d58eff77
670
670
671
671
672 barney is allowed everywhere
672 barney is allowed everywhere
673
673
674 $ echo '[acl.allow]' >> $config
674 $ echo '[acl.allow]' >> $config
675 $ echo '** = barney' >> $config
675 $ echo '** = barney' >> $config
676 $ do_push barney
676 $ do_push barney
677 Pushing as user barney
677 Pushing as user barney
678 hgrc = """
678 hgrc = """
679 [hooks]
679 [hooks]
680 pretxnchangegroup.acl = python:hgext.acl.hook
680 pretxnchangegroup.acl = python:hgext.acl.hook
681 [acl]
681 [acl]
682 sources = push
682 sources = push
683 [acl.allow]
683 [acl.allow]
684 foo/** = fred
684 foo/** = fred
685 [acl.deny]
685 [acl.deny]
686 foo/bar/** = fred
686 foo/bar/** = fred
687 foo/Bar/** = fred
687 foo/Bar/** = fred
688 [acl.allow]
688 [acl.allow]
689 ** = barney
689 ** = barney
690 """
690 """
691 pushing to ../b
691 pushing to ../b
692 query 1; heads
692 query 1; heads
693 searching for changes
693 searching for changes
694 all remote heads known locally
694 all remote heads known locally
695 listing keys for "phases"
695 listing keys for "phases"
696 checking for updated bookmarks
696 checking for updated bookmarks
697 listing keys for "bookmarks"
697 listing keys for "bookmarks"
698 listing keys for "bookmarks"
698 listing keys for "bookmarks"
699 3 changesets found
699 3 changesets found
700 list of changesets:
700 list of changesets:
701 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
701 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
702 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
702 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
703 911600dab2ae7a9baff75958b84fe606851ce955
703 911600dab2ae7a9baff75958b84fe606851ce955
704 bundle2-output-bundle: "HG20", 4 parts total
704 bundle2-output-bundle: "HG20", 4 parts total
705 bundle2-output-part: "replycaps" 155 bytes payload
705 bundle2-output-part: "replycaps" 155 bytes payload
706 bundle2-output-part: "check:heads" streamed payload
706 bundle2-output-part: "check:heads" streamed payload
707 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
707 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
708 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
708 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
709 bundle2-input-bundle: with-transaction
709 bundle2-input-bundle: with-transaction
710 bundle2-input-part: "replycaps" supported
710 bundle2-input-part: "replycaps" supported
711 bundle2-input-part: total payload size 155
711 bundle2-input-part: total payload size 155
712 bundle2-input-part: "check:heads" supported
712 bundle2-input-part: "check:heads" supported
713 bundle2-input-part: total payload size 20
713 bundle2-input-part: total payload size 20
714 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
714 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
715 adding changesets
715 adding changesets
716 add changeset ef1ea85a6374
716 add changeset ef1ea85a6374
717 add changeset f9cafe1212c8
717 add changeset f9cafe1212c8
718 add changeset 911600dab2ae
718 add changeset 911600dab2ae
719 adding manifests
719 adding manifests
720 adding file changes
720 adding file changes
721 adding foo/Bar/file.txt revisions
721 adding foo/Bar/file.txt revisions
722 adding foo/file.txt revisions
722 adding foo/file.txt revisions
723 adding quux/file.py revisions
723 adding quux/file.py revisions
724 added 3 changesets with 3 changes to 3 files
724 added 3 changesets with 3 changes to 3 files
725 calling hook pretxnchangegroup.acl: hgext.acl.hook
725 calling hook pretxnchangegroup.acl: hgext.acl.hook
726 acl: checking access for user "barney"
726 acl: checking access for user "barney"
727 acl: acl.allow.branches not enabled
727 acl: acl.allow.branches not enabled
728 acl: acl.deny.branches not enabled
728 acl: acl.deny.branches not enabled
729 acl: acl.allow enabled, 1 entries for user barney
729 acl: acl.allow enabled, 1 entries for user barney
730 acl: acl.deny enabled, 0 entries for user barney
730 acl: acl.deny enabled, 0 entries for user barney
731 acl: branch access granted: "ef1ea85a6374" on branch "default"
731 acl: branch access granted: "ef1ea85a6374" on branch "default"
732 acl: path access granted: "ef1ea85a6374"
732 acl: path access granted: "ef1ea85a6374"
733 acl: branch access granted: "f9cafe1212c8" on branch "default"
733 acl: branch access granted: "f9cafe1212c8" on branch "default"
734 acl: path access granted: "f9cafe1212c8"
734 acl: path access granted: "f9cafe1212c8"
735 acl: branch access granted: "911600dab2ae" on branch "default"
735 acl: branch access granted: "911600dab2ae" on branch "default"
736 acl: path access granted: "911600dab2ae"
736 acl: path access granted: "911600dab2ae"
737 bundle2-input-part: total payload size 1553
737 bundle2-input-part: total payload size 1553
738 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
738 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
739 pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
739 pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
740 bundle2-input-bundle: 3 parts total
740 bundle2-input-bundle: 3 parts total
741 updating the branch cache
741 updating the branch cache
742 bundle2-output-bundle: "HG20", 2 parts total
742 bundle2-output-bundle: "HG20", 2 parts total
743 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
743 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
744 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
744 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
745 bundle2-input-bundle: no-transaction
745 bundle2-input-bundle: no-transaction
746 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
746 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
747 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
747 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
748 bundle2-input-bundle: 1 parts total
748 bundle2-input-bundle: 1 parts total
749 listing keys for "phases"
749 listing keys for "phases"
750 repository tip rolled back to revision 0 (undo push)
750 repository tip rolled back to revision 0 (undo push)
751 0:6675d58eff77
751 0:6675d58eff77
752
752
753
753
754 wilma can change files with a .txt extension
754 wilma can change files with a .txt extension
755
755
756 $ echo '**/*.txt = wilma' >> $config
756 $ echo '**/*.txt = wilma' >> $config
757 $ do_push wilma
757 $ do_push wilma
758 Pushing as user wilma
758 Pushing as user wilma
759 hgrc = """
759 hgrc = """
760 [hooks]
760 [hooks]
761 pretxnchangegroup.acl = python:hgext.acl.hook
761 pretxnchangegroup.acl = python:hgext.acl.hook
762 [acl]
762 [acl]
763 sources = push
763 sources = push
764 [acl.allow]
764 [acl.allow]
765 foo/** = fred
765 foo/** = fred
766 [acl.deny]
766 [acl.deny]
767 foo/bar/** = fred
767 foo/bar/** = fred
768 foo/Bar/** = fred
768 foo/Bar/** = fred
769 [acl.allow]
769 [acl.allow]
770 ** = barney
770 ** = barney
771 **/*.txt = wilma
771 **/*.txt = wilma
772 """
772 """
773 pushing to ../b
773 pushing to ../b
774 query 1; heads
774 query 1; heads
775 searching for changes
775 searching for changes
776 all remote heads known locally
776 all remote heads known locally
777 listing keys for "phases"
777 listing keys for "phases"
778 checking for updated bookmarks
778 checking for updated bookmarks
779 listing keys for "bookmarks"
779 listing keys for "bookmarks"
780 listing keys for "bookmarks"
780 listing keys for "bookmarks"
781 3 changesets found
781 3 changesets found
782 list of changesets:
782 list of changesets:
783 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
783 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
784 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
784 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
785 911600dab2ae7a9baff75958b84fe606851ce955
785 911600dab2ae7a9baff75958b84fe606851ce955
786 bundle2-output-bundle: "HG20", 4 parts total
786 bundle2-output-bundle: "HG20", 4 parts total
787 bundle2-output-part: "replycaps" 155 bytes payload
787 bundle2-output-part: "replycaps" 155 bytes payload
788 bundle2-output-part: "check:heads" streamed payload
788 bundle2-output-part: "check:heads" streamed payload
789 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
789 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
790 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
790 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
791 bundle2-input-bundle: with-transaction
791 bundle2-input-bundle: with-transaction
792 bundle2-input-part: "replycaps" supported
792 bundle2-input-part: "replycaps" supported
793 bundle2-input-part: total payload size 155
793 bundle2-input-part: total payload size 155
794 bundle2-input-part: "check:heads" supported
794 bundle2-input-part: "check:heads" supported
795 bundle2-input-part: total payload size 20
795 bundle2-input-part: total payload size 20
796 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
796 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
797 adding changesets
797 adding changesets
798 add changeset ef1ea85a6374
798 add changeset ef1ea85a6374
799 add changeset f9cafe1212c8
799 add changeset f9cafe1212c8
800 add changeset 911600dab2ae
800 add changeset 911600dab2ae
801 adding manifests
801 adding manifests
802 adding file changes
802 adding file changes
803 adding foo/Bar/file.txt revisions
803 adding foo/Bar/file.txt revisions
804 adding foo/file.txt revisions
804 adding foo/file.txt revisions
805 adding quux/file.py revisions
805 adding quux/file.py revisions
806 added 3 changesets with 3 changes to 3 files
806 added 3 changesets with 3 changes to 3 files
807 calling hook pretxnchangegroup.acl: hgext.acl.hook
807 calling hook pretxnchangegroup.acl: hgext.acl.hook
808 acl: checking access for user "wilma"
808 acl: checking access for user "wilma"
809 acl: acl.allow.branches not enabled
809 acl: acl.allow.branches not enabled
810 acl: acl.deny.branches not enabled
810 acl: acl.deny.branches not enabled
811 acl: acl.allow enabled, 1 entries for user wilma
811 acl: acl.allow enabled, 1 entries for user wilma
812 acl: acl.deny enabled, 0 entries for user wilma
812 acl: acl.deny enabled, 0 entries for user wilma
813 acl: branch access granted: "ef1ea85a6374" on branch "default"
813 acl: branch access granted: "ef1ea85a6374" on branch "default"
814 acl: path access granted: "ef1ea85a6374"
814 acl: path access granted: "ef1ea85a6374"
815 acl: branch access granted: "f9cafe1212c8" on branch "default"
815 acl: branch access granted: "f9cafe1212c8" on branch "default"
816 acl: path access granted: "f9cafe1212c8"
816 acl: path access granted: "f9cafe1212c8"
817 acl: branch access granted: "911600dab2ae" on branch "default"
817 acl: branch access granted: "911600dab2ae" on branch "default"
818 error: pretxnchangegroup.acl hook failed: acl: user "wilma" not allowed on "quux/file.py" (changeset "911600dab2ae")
818 error: pretxnchangegroup.acl hook failed: acl: user "wilma" not allowed on "quux/file.py" (changeset "911600dab2ae")
819 bundle2-input-part: total payload size 1553
819 bundle2-input-part: total payload size 1553
820 bundle2-input-bundle: 3 parts total
820 bundle2-input-bundle: 3 parts total
821 transaction abort!
821 transaction abort!
822 rollback completed
822 rollback completed
823 abort: acl: user "wilma" not allowed on "quux/file.py" (changeset "911600dab2ae")
823 abort: acl: user "wilma" not allowed on "quux/file.py" (changeset "911600dab2ae")
824 no rollback information available
824 no rollback information available
825 0:6675d58eff77
825 0:6675d58eff77
826
826
827
827
828 file specified by acl.config does not exist
828 file specified by acl.config does not exist
829
829
830 $ echo '[acl]' >> $config
830 $ echo '[acl]' >> $config
831 $ echo 'config = ../acl.config' >> $config
831 $ echo 'config = ../acl.config' >> $config
832 $ do_push barney
832 $ do_push barney
833 Pushing as user barney
833 Pushing as user barney
834 hgrc = """
834 hgrc = """
835 [hooks]
835 [hooks]
836 pretxnchangegroup.acl = python:hgext.acl.hook
836 pretxnchangegroup.acl = python:hgext.acl.hook
837 [acl]
837 [acl]
838 sources = push
838 sources = push
839 [acl.allow]
839 [acl.allow]
840 foo/** = fred
840 foo/** = fred
841 [acl.deny]
841 [acl.deny]
842 foo/bar/** = fred
842 foo/bar/** = fred
843 foo/Bar/** = fred
843 foo/Bar/** = fred
844 [acl.allow]
844 [acl.allow]
845 ** = barney
845 ** = barney
846 **/*.txt = wilma
846 **/*.txt = wilma
847 [acl]
847 [acl]
848 config = ../acl.config
848 config = ../acl.config
849 """
849 """
850 pushing to ../b
850 pushing to ../b
851 query 1; heads
851 query 1; heads
852 searching for changes
852 searching for changes
853 all remote heads known locally
853 all remote heads known locally
854 listing keys for "phases"
854 listing keys for "phases"
855 checking for updated bookmarks
855 checking for updated bookmarks
856 listing keys for "bookmarks"
856 listing keys for "bookmarks"
857 listing keys for "bookmarks"
857 listing keys for "bookmarks"
858 3 changesets found
858 3 changesets found
859 list of changesets:
859 list of changesets:
860 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
860 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
861 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
861 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
862 911600dab2ae7a9baff75958b84fe606851ce955
862 911600dab2ae7a9baff75958b84fe606851ce955
863 bundle2-output-bundle: "HG20", 4 parts total
863 bundle2-output-bundle: "HG20", 4 parts total
864 bundle2-output-part: "replycaps" 155 bytes payload
864 bundle2-output-part: "replycaps" 155 bytes payload
865 bundle2-output-part: "check:heads" streamed payload
865 bundle2-output-part: "check:heads" streamed payload
866 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
866 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
867 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
867 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
868 bundle2-input-bundle: with-transaction
868 bundle2-input-bundle: with-transaction
869 bundle2-input-part: "replycaps" supported
869 bundle2-input-part: "replycaps" supported
870 bundle2-input-part: total payload size 155
870 bundle2-input-part: total payload size 155
871 bundle2-input-part: "check:heads" supported
871 bundle2-input-part: "check:heads" supported
872 bundle2-input-part: total payload size 20
872 bundle2-input-part: total payload size 20
873 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
873 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
874 adding changesets
874 adding changesets
875 add changeset ef1ea85a6374
875 add changeset ef1ea85a6374
876 add changeset f9cafe1212c8
876 add changeset f9cafe1212c8
877 add changeset 911600dab2ae
877 add changeset 911600dab2ae
878 adding manifests
878 adding manifests
879 adding file changes
879 adding file changes
880 adding foo/Bar/file.txt revisions
880 adding foo/Bar/file.txt revisions
881 adding foo/file.txt revisions
881 adding foo/file.txt revisions
882 adding quux/file.py revisions
882 adding quux/file.py revisions
883 added 3 changesets with 3 changes to 3 files
883 added 3 changesets with 3 changes to 3 files
884 calling hook pretxnchangegroup.acl: hgext.acl.hook
884 calling hook pretxnchangegroup.acl: hgext.acl.hook
885 acl: checking access for user "barney"
885 acl: checking access for user "barney"
886 error: pretxnchangegroup.acl hook raised an exception: [Errno 2] No such file or directory: '../acl.config'
886 error: pretxnchangegroup.acl hook raised an exception: [Errno *] * (glob)
887 bundle2-input-part: total payload size 1553
887 bundle2-input-part: total payload size 1553
888 bundle2-input-bundle: 3 parts total
888 bundle2-input-bundle: 3 parts total
889 transaction abort!
889 transaction abort!
890 rollback completed
890 rollback completed
891 abort: No such file or directory: ../acl.config
891 abort: No such file or directory: ../acl.config
892 no rollback information available
892 no rollback information available
893 0:6675d58eff77
893 0:6675d58eff77
894
894
895
895
896 betty is allowed inside foo/ by a acl.config file
896 betty is allowed inside foo/ by a acl.config file
897
897
898 $ echo '[acl.allow]' >> acl.config
898 $ echo '[acl.allow]' >> acl.config
899 $ echo 'foo/** = betty' >> acl.config
899 $ echo 'foo/** = betty' >> acl.config
900 $ do_push betty
900 $ do_push betty
901 Pushing as user betty
901 Pushing as user betty
902 hgrc = """
902 hgrc = """
903 [hooks]
903 [hooks]
904 pretxnchangegroup.acl = python:hgext.acl.hook
904 pretxnchangegroup.acl = python:hgext.acl.hook
905 [acl]
905 [acl]
906 sources = push
906 sources = push
907 [acl.allow]
907 [acl.allow]
908 foo/** = fred
908 foo/** = fred
909 [acl.deny]
909 [acl.deny]
910 foo/bar/** = fred
910 foo/bar/** = fred
911 foo/Bar/** = fred
911 foo/Bar/** = fred
912 [acl.allow]
912 [acl.allow]
913 ** = barney
913 ** = barney
914 **/*.txt = wilma
914 **/*.txt = wilma
915 [acl]
915 [acl]
916 config = ../acl.config
916 config = ../acl.config
917 """
917 """
918 acl.config = """
918 acl.config = """
919 [acl.allow]
919 [acl.allow]
920 foo/** = betty
920 foo/** = betty
921 """
921 """
922 pushing to ../b
922 pushing to ../b
923 query 1; heads
923 query 1; heads
924 searching for changes
924 searching for changes
925 all remote heads known locally
925 all remote heads known locally
926 listing keys for "phases"
926 listing keys for "phases"
927 checking for updated bookmarks
927 checking for updated bookmarks
928 listing keys for "bookmarks"
928 listing keys for "bookmarks"
929 listing keys for "bookmarks"
929 listing keys for "bookmarks"
930 3 changesets found
930 3 changesets found
931 list of changesets:
931 list of changesets:
932 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
932 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
933 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
933 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
934 911600dab2ae7a9baff75958b84fe606851ce955
934 911600dab2ae7a9baff75958b84fe606851ce955
935 bundle2-output-bundle: "HG20", 4 parts total
935 bundle2-output-bundle: "HG20", 4 parts total
936 bundle2-output-part: "replycaps" 155 bytes payload
936 bundle2-output-part: "replycaps" 155 bytes payload
937 bundle2-output-part: "check:heads" streamed payload
937 bundle2-output-part: "check:heads" streamed payload
938 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
938 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
939 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
939 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
940 bundle2-input-bundle: with-transaction
940 bundle2-input-bundle: with-transaction
941 bundle2-input-part: "replycaps" supported
941 bundle2-input-part: "replycaps" supported
942 bundle2-input-part: total payload size 155
942 bundle2-input-part: total payload size 155
943 bundle2-input-part: "check:heads" supported
943 bundle2-input-part: "check:heads" supported
944 bundle2-input-part: total payload size 20
944 bundle2-input-part: total payload size 20
945 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
945 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
946 adding changesets
946 adding changesets
947 add changeset ef1ea85a6374
947 add changeset ef1ea85a6374
948 add changeset f9cafe1212c8
948 add changeset f9cafe1212c8
949 add changeset 911600dab2ae
949 add changeset 911600dab2ae
950 adding manifests
950 adding manifests
951 adding file changes
951 adding file changes
952 adding foo/Bar/file.txt revisions
952 adding foo/Bar/file.txt revisions
953 adding foo/file.txt revisions
953 adding foo/file.txt revisions
954 adding quux/file.py revisions
954 adding quux/file.py revisions
955 added 3 changesets with 3 changes to 3 files
955 added 3 changesets with 3 changes to 3 files
956 calling hook pretxnchangegroup.acl: hgext.acl.hook
956 calling hook pretxnchangegroup.acl: hgext.acl.hook
957 acl: checking access for user "betty"
957 acl: checking access for user "betty"
958 acl: acl.allow.branches not enabled
958 acl: acl.allow.branches not enabled
959 acl: acl.deny.branches not enabled
959 acl: acl.deny.branches not enabled
960 acl: acl.allow enabled, 1 entries for user betty
960 acl: acl.allow enabled, 1 entries for user betty
961 acl: acl.deny enabled, 0 entries for user betty
961 acl: acl.deny enabled, 0 entries for user betty
962 acl: branch access granted: "ef1ea85a6374" on branch "default"
962 acl: branch access granted: "ef1ea85a6374" on branch "default"
963 acl: path access granted: "ef1ea85a6374"
963 acl: path access granted: "ef1ea85a6374"
964 acl: branch access granted: "f9cafe1212c8" on branch "default"
964 acl: branch access granted: "f9cafe1212c8" on branch "default"
965 acl: path access granted: "f9cafe1212c8"
965 acl: path access granted: "f9cafe1212c8"
966 acl: branch access granted: "911600dab2ae" on branch "default"
966 acl: branch access granted: "911600dab2ae" on branch "default"
967 error: pretxnchangegroup.acl hook failed: acl: user "betty" not allowed on "quux/file.py" (changeset "911600dab2ae")
967 error: pretxnchangegroup.acl hook failed: acl: user "betty" not allowed on "quux/file.py" (changeset "911600dab2ae")
968 bundle2-input-part: total payload size 1553
968 bundle2-input-part: total payload size 1553
969 bundle2-input-bundle: 3 parts total
969 bundle2-input-bundle: 3 parts total
970 transaction abort!
970 transaction abort!
971 rollback completed
971 rollback completed
972 abort: acl: user "betty" not allowed on "quux/file.py" (changeset "911600dab2ae")
972 abort: acl: user "betty" not allowed on "quux/file.py" (changeset "911600dab2ae")
973 no rollback information available
973 no rollback information available
974 0:6675d58eff77
974 0:6675d58eff77
975
975
976
976
977 acl.config can set only [acl.allow]/[acl.deny]
977 acl.config can set only [acl.allow]/[acl.deny]
978
978
979 $ echo '[hooks]' >> acl.config
979 $ echo '[hooks]' >> acl.config
980 $ echo 'changegroup.acl = false' >> acl.config
980 $ echo 'changegroup.acl = false' >> acl.config
981 $ do_push barney
981 $ do_push barney
982 Pushing as user barney
982 Pushing as user barney
983 hgrc = """
983 hgrc = """
984 [hooks]
984 [hooks]
985 pretxnchangegroup.acl = python:hgext.acl.hook
985 pretxnchangegroup.acl = python:hgext.acl.hook
986 [acl]
986 [acl]
987 sources = push
987 sources = push
988 [acl.allow]
988 [acl.allow]
989 foo/** = fred
989 foo/** = fred
990 [acl.deny]
990 [acl.deny]
991 foo/bar/** = fred
991 foo/bar/** = fred
992 foo/Bar/** = fred
992 foo/Bar/** = fred
993 [acl.allow]
993 [acl.allow]
994 ** = barney
994 ** = barney
995 **/*.txt = wilma
995 **/*.txt = wilma
996 [acl]
996 [acl]
997 config = ../acl.config
997 config = ../acl.config
998 """
998 """
999 acl.config = """
999 acl.config = """
1000 [acl.allow]
1000 [acl.allow]
1001 foo/** = betty
1001 foo/** = betty
1002 [hooks]
1002 [hooks]
1003 changegroup.acl = false
1003 changegroup.acl = false
1004 """
1004 """
1005 pushing to ../b
1005 pushing to ../b
1006 query 1; heads
1006 query 1; heads
1007 searching for changes
1007 searching for changes
1008 all remote heads known locally
1008 all remote heads known locally
1009 listing keys for "phases"
1009 listing keys for "phases"
1010 checking for updated bookmarks
1010 checking for updated bookmarks
1011 listing keys for "bookmarks"
1011 listing keys for "bookmarks"
1012 listing keys for "bookmarks"
1012 listing keys for "bookmarks"
1013 3 changesets found
1013 3 changesets found
1014 list of changesets:
1014 list of changesets:
1015 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1015 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1016 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1016 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1017 911600dab2ae7a9baff75958b84fe606851ce955
1017 911600dab2ae7a9baff75958b84fe606851ce955
1018 bundle2-output-bundle: "HG20", 4 parts total
1018 bundle2-output-bundle: "HG20", 4 parts total
1019 bundle2-output-part: "replycaps" 155 bytes payload
1019 bundle2-output-part: "replycaps" 155 bytes payload
1020 bundle2-output-part: "check:heads" streamed payload
1020 bundle2-output-part: "check:heads" streamed payload
1021 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1021 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1022 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1022 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1023 bundle2-input-bundle: with-transaction
1023 bundle2-input-bundle: with-transaction
1024 bundle2-input-part: "replycaps" supported
1024 bundle2-input-part: "replycaps" supported
1025 bundle2-input-part: total payload size 155
1025 bundle2-input-part: total payload size 155
1026 bundle2-input-part: "check:heads" supported
1026 bundle2-input-part: "check:heads" supported
1027 bundle2-input-part: total payload size 20
1027 bundle2-input-part: total payload size 20
1028 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1028 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1029 adding changesets
1029 adding changesets
1030 add changeset ef1ea85a6374
1030 add changeset ef1ea85a6374
1031 add changeset f9cafe1212c8
1031 add changeset f9cafe1212c8
1032 add changeset 911600dab2ae
1032 add changeset 911600dab2ae
1033 adding manifests
1033 adding manifests
1034 adding file changes
1034 adding file changes
1035 adding foo/Bar/file.txt revisions
1035 adding foo/Bar/file.txt revisions
1036 adding foo/file.txt revisions
1036 adding foo/file.txt revisions
1037 adding quux/file.py revisions
1037 adding quux/file.py revisions
1038 added 3 changesets with 3 changes to 3 files
1038 added 3 changesets with 3 changes to 3 files
1039 calling hook pretxnchangegroup.acl: hgext.acl.hook
1039 calling hook pretxnchangegroup.acl: hgext.acl.hook
1040 acl: checking access for user "barney"
1040 acl: checking access for user "barney"
1041 acl: acl.allow.branches not enabled
1041 acl: acl.allow.branches not enabled
1042 acl: acl.deny.branches not enabled
1042 acl: acl.deny.branches not enabled
1043 acl: acl.allow enabled, 1 entries for user barney
1043 acl: acl.allow enabled, 1 entries for user barney
1044 acl: acl.deny enabled, 0 entries for user barney
1044 acl: acl.deny enabled, 0 entries for user barney
1045 acl: branch access granted: "ef1ea85a6374" on branch "default"
1045 acl: branch access granted: "ef1ea85a6374" on branch "default"
1046 acl: path access granted: "ef1ea85a6374"
1046 acl: path access granted: "ef1ea85a6374"
1047 acl: branch access granted: "f9cafe1212c8" on branch "default"
1047 acl: branch access granted: "f9cafe1212c8" on branch "default"
1048 acl: path access granted: "f9cafe1212c8"
1048 acl: path access granted: "f9cafe1212c8"
1049 acl: branch access granted: "911600dab2ae" on branch "default"
1049 acl: branch access granted: "911600dab2ae" on branch "default"
1050 acl: path access granted: "911600dab2ae"
1050 acl: path access granted: "911600dab2ae"
1051 bundle2-input-part: total payload size 1553
1051 bundle2-input-part: total payload size 1553
1052 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
1052 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
1053 pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
1053 pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
1054 bundle2-input-bundle: 3 parts total
1054 bundle2-input-bundle: 3 parts total
1055 updating the branch cache
1055 updating the branch cache
1056 bundle2-output-bundle: "HG20", 2 parts total
1056 bundle2-output-bundle: "HG20", 2 parts total
1057 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
1057 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
1058 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
1058 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
1059 bundle2-input-bundle: no-transaction
1059 bundle2-input-bundle: no-transaction
1060 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
1060 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
1061 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
1061 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
1062 bundle2-input-bundle: 1 parts total
1062 bundle2-input-bundle: 1 parts total
1063 listing keys for "phases"
1063 listing keys for "phases"
1064 repository tip rolled back to revision 0 (undo push)
1064 repository tip rolled back to revision 0 (undo push)
1065 0:6675d58eff77
1065 0:6675d58eff77
1066
1066
1067
1067
1068 asterisk
1068 asterisk
1069
1069
1070 $ init_config
1070 $ init_config
1071
1071
1072 asterisk test
1072 asterisk test
1073
1073
1074 $ echo '[acl.allow]' >> $config
1074 $ echo '[acl.allow]' >> $config
1075 $ echo "** = fred" >> $config
1075 $ echo "** = fred" >> $config
1076
1076
1077 fred is always allowed
1077 fred is always allowed
1078
1078
1079 $ do_push fred
1079 $ do_push fred
1080 Pushing as user fred
1080 Pushing as user fred
1081 hgrc = """
1081 hgrc = """
1082 [hooks]
1082 [hooks]
1083 pretxnchangegroup.acl = python:hgext.acl.hook
1083 pretxnchangegroup.acl = python:hgext.acl.hook
1084 [acl]
1084 [acl]
1085 sources = push
1085 sources = push
1086 [extensions]
1086 [extensions]
1087 [acl.allow]
1087 [acl.allow]
1088 ** = fred
1088 ** = fred
1089 """
1089 """
1090 pushing to ../b
1090 pushing to ../b
1091 query 1; heads
1091 query 1; heads
1092 searching for changes
1092 searching for changes
1093 all remote heads known locally
1093 all remote heads known locally
1094 listing keys for "phases"
1094 listing keys for "phases"
1095 checking for updated bookmarks
1095 checking for updated bookmarks
1096 listing keys for "bookmarks"
1096 listing keys for "bookmarks"
1097 listing keys for "bookmarks"
1097 listing keys for "bookmarks"
1098 3 changesets found
1098 3 changesets found
1099 list of changesets:
1099 list of changesets:
1100 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1100 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1101 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1101 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1102 911600dab2ae7a9baff75958b84fe606851ce955
1102 911600dab2ae7a9baff75958b84fe606851ce955
1103 bundle2-output-bundle: "HG20", 4 parts total
1103 bundle2-output-bundle: "HG20", 4 parts total
1104 bundle2-output-part: "replycaps" 155 bytes payload
1104 bundle2-output-part: "replycaps" 155 bytes payload
1105 bundle2-output-part: "check:heads" streamed payload
1105 bundle2-output-part: "check:heads" streamed payload
1106 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1106 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1107 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1107 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1108 bundle2-input-bundle: with-transaction
1108 bundle2-input-bundle: with-transaction
1109 bundle2-input-part: "replycaps" supported
1109 bundle2-input-part: "replycaps" supported
1110 bundle2-input-part: total payload size 155
1110 bundle2-input-part: total payload size 155
1111 bundle2-input-part: "check:heads" supported
1111 bundle2-input-part: "check:heads" supported
1112 bundle2-input-part: total payload size 20
1112 bundle2-input-part: total payload size 20
1113 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1113 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1114 adding changesets
1114 adding changesets
1115 add changeset ef1ea85a6374
1115 add changeset ef1ea85a6374
1116 add changeset f9cafe1212c8
1116 add changeset f9cafe1212c8
1117 add changeset 911600dab2ae
1117 add changeset 911600dab2ae
1118 adding manifests
1118 adding manifests
1119 adding file changes
1119 adding file changes
1120 adding foo/Bar/file.txt revisions
1120 adding foo/Bar/file.txt revisions
1121 adding foo/file.txt revisions
1121 adding foo/file.txt revisions
1122 adding quux/file.py revisions
1122 adding quux/file.py revisions
1123 added 3 changesets with 3 changes to 3 files
1123 added 3 changesets with 3 changes to 3 files
1124 calling hook pretxnchangegroup.acl: hgext.acl.hook
1124 calling hook pretxnchangegroup.acl: hgext.acl.hook
1125 acl: checking access for user "fred"
1125 acl: checking access for user "fred"
1126 acl: acl.allow.branches not enabled
1126 acl: acl.allow.branches not enabled
1127 acl: acl.deny.branches not enabled
1127 acl: acl.deny.branches not enabled
1128 acl: acl.allow enabled, 1 entries for user fred
1128 acl: acl.allow enabled, 1 entries for user fred
1129 acl: acl.deny not enabled
1129 acl: acl.deny not enabled
1130 acl: branch access granted: "ef1ea85a6374" on branch "default"
1130 acl: branch access granted: "ef1ea85a6374" on branch "default"
1131 acl: path access granted: "ef1ea85a6374"
1131 acl: path access granted: "ef1ea85a6374"
1132 acl: branch access granted: "f9cafe1212c8" on branch "default"
1132 acl: branch access granted: "f9cafe1212c8" on branch "default"
1133 acl: path access granted: "f9cafe1212c8"
1133 acl: path access granted: "f9cafe1212c8"
1134 acl: branch access granted: "911600dab2ae" on branch "default"
1134 acl: branch access granted: "911600dab2ae" on branch "default"
1135 acl: path access granted: "911600dab2ae"
1135 acl: path access granted: "911600dab2ae"
1136 bundle2-input-part: total payload size 1553
1136 bundle2-input-part: total payload size 1553
1137 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
1137 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
1138 pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
1138 pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
1139 bundle2-input-bundle: 3 parts total
1139 bundle2-input-bundle: 3 parts total
1140 updating the branch cache
1140 updating the branch cache
1141 bundle2-output-bundle: "HG20", 2 parts total
1141 bundle2-output-bundle: "HG20", 2 parts total
1142 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
1142 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
1143 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
1143 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
1144 bundle2-input-bundle: no-transaction
1144 bundle2-input-bundle: no-transaction
1145 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
1145 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
1146 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
1146 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
1147 bundle2-input-bundle: 1 parts total
1147 bundle2-input-bundle: 1 parts total
1148 listing keys for "phases"
1148 listing keys for "phases"
1149 repository tip rolled back to revision 0 (undo push)
1149 repository tip rolled back to revision 0 (undo push)
1150 0:6675d58eff77
1150 0:6675d58eff77
1151
1151
1152
1152
1153 $ echo '[acl.deny]' >> $config
1153 $ echo '[acl.deny]' >> $config
1154 $ echo "foo/Bar/** = *" >> $config
1154 $ echo "foo/Bar/** = *" >> $config
1155
1155
1156 no one is allowed inside foo/Bar/
1156 no one is allowed inside foo/Bar/
1157
1157
1158 $ do_push fred
1158 $ do_push fred
1159 Pushing as user fred
1159 Pushing as user fred
1160 hgrc = """
1160 hgrc = """
1161 [hooks]
1161 [hooks]
1162 pretxnchangegroup.acl = python:hgext.acl.hook
1162 pretxnchangegroup.acl = python:hgext.acl.hook
1163 [acl]
1163 [acl]
1164 sources = push
1164 sources = push
1165 [extensions]
1165 [extensions]
1166 [acl.allow]
1166 [acl.allow]
1167 ** = fred
1167 ** = fred
1168 [acl.deny]
1168 [acl.deny]
1169 foo/Bar/** = *
1169 foo/Bar/** = *
1170 """
1170 """
1171 pushing to ../b
1171 pushing to ../b
1172 query 1; heads
1172 query 1; heads
1173 searching for changes
1173 searching for changes
1174 all remote heads known locally
1174 all remote heads known locally
1175 listing keys for "phases"
1175 listing keys for "phases"
1176 checking for updated bookmarks
1176 checking for updated bookmarks
1177 listing keys for "bookmarks"
1177 listing keys for "bookmarks"
1178 listing keys for "bookmarks"
1178 listing keys for "bookmarks"
1179 3 changesets found
1179 3 changesets found
1180 list of changesets:
1180 list of changesets:
1181 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1181 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1182 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1182 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1183 911600dab2ae7a9baff75958b84fe606851ce955
1183 911600dab2ae7a9baff75958b84fe606851ce955
1184 bundle2-output-bundle: "HG20", 4 parts total
1184 bundle2-output-bundle: "HG20", 4 parts total
1185 bundle2-output-part: "replycaps" 155 bytes payload
1185 bundle2-output-part: "replycaps" 155 bytes payload
1186 bundle2-output-part: "check:heads" streamed payload
1186 bundle2-output-part: "check:heads" streamed payload
1187 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1187 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1188 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1188 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1189 bundle2-input-bundle: with-transaction
1189 bundle2-input-bundle: with-transaction
1190 bundle2-input-part: "replycaps" supported
1190 bundle2-input-part: "replycaps" supported
1191 bundle2-input-part: total payload size 155
1191 bundle2-input-part: total payload size 155
1192 bundle2-input-part: "check:heads" supported
1192 bundle2-input-part: "check:heads" supported
1193 bundle2-input-part: total payload size 20
1193 bundle2-input-part: total payload size 20
1194 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1194 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1195 adding changesets
1195 adding changesets
1196 add changeset ef1ea85a6374
1196 add changeset ef1ea85a6374
1197 add changeset f9cafe1212c8
1197 add changeset f9cafe1212c8
1198 add changeset 911600dab2ae
1198 add changeset 911600dab2ae
1199 adding manifests
1199 adding manifests
1200 adding file changes
1200 adding file changes
1201 adding foo/Bar/file.txt revisions
1201 adding foo/Bar/file.txt revisions
1202 adding foo/file.txt revisions
1202 adding foo/file.txt revisions
1203 adding quux/file.py revisions
1203 adding quux/file.py revisions
1204 added 3 changesets with 3 changes to 3 files
1204 added 3 changesets with 3 changes to 3 files
1205 calling hook pretxnchangegroup.acl: hgext.acl.hook
1205 calling hook pretxnchangegroup.acl: hgext.acl.hook
1206 acl: checking access for user "fred"
1206 acl: checking access for user "fred"
1207 acl: acl.allow.branches not enabled
1207 acl: acl.allow.branches not enabled
1208 acl: acl.deny.branches not enabled
1208 acl: acl.deny.branches not enabled
1209 acl: acl.allow enabled, 1 entries for user fred
1209 acl: acl.allow enabled, 1 entries for user fred
1210 acl: acl.deny enabled, 1 entries for user fred
1210 acl: acl.deny enabled, 1 entries for user fred
1211 acl: branch access granted: "ef1ea85a6374" on branch "default"
1211 acl: branch access granted: "ef1ea85a6374" on branch "default"
1212 acl: path access granted: "ef1ea85a6374"
1212 acl: path access granted: "ef1ea85a6374"
1213 acl: branch access granted: "f9cafe1212c8" on branch "default"
1213 acl: branch access granted: "f9cafe1212c8" on branch "default"
1214 error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
1214 error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
1215 bundle2-input-part: total payload size 1553
1215 bundle2-input-part: total payload size 1553
1216 bundle2-input-bundle: 3 parts total
1216 bundle2-input-bundle: 3 parts total
1217 transaction abort!
1217 transaction abort!
1218 rollback completed
1218 rollback completed
1219 abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
1219 abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
1220 no rollback information available
1220 no rollback information available
1221 0:6675d58eff77
1221 0:6675d58eff77
1222
1222
1223
1223
1224 Groups
1224 Groups
1225
1225
1226 $ init_config
1226 $ init_config
1227
1227
1228 OS-level groups
1228 OS-level groups
1229
1229
1230 $ echo '[acl.allow]' >> $config
1230 $ echo '[acl.allow]' >> $config
1231 $ echo "** = @group1" >> $config
1231 $ echo "** = @group1" >> $config
1232
1232
1233 @group1 is always allowed
1233 @group1 is always allowed
1234
1234
1235 $ do_push fred
1235 $ do_push fred
1236 Pushing as user fred
1236 Pushing as user fred
1237 hgrc = """
1237 hgrc = """
1238 [hooks]
1238 [hooks]
1239 pretxnchangegroup.acl = python:hgext.acl.hook
1239 pretxnchangegroup.acl = python:hgext.acl.hook
1240 [acl]
1240 [acl]
1241 sources = push
1241 sources = push
1242 [extensions]
1242 [extensions]
1243 [acl.allow]
1243 [acl.allow]
1244 ** = @group1
1244 ** = @group1
1245 """
1245 """
1246 pushing to ../b
1246 pushing to ../b
1247 query 1; heads
1247 query 1; heads
1248 searching for changes
1248 searching for changes
1249 all remote heads known locally
1249 all remote heads known locally
1250 listing keys for "phases"
1250 listing keys for "phases"
1251 checking for updated bookmarks
1251 checking for updated bookmarks
1252 listing keys for "bookmarks"
1252 listing keys for "bookmarks"
1253 listing keys for "bookmarks"
1253 listing keys for "bookmarks"
1254 3 changesets found
1254 3 changesets found
1255 list of changesets:
1255 list of changesets:
1256 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1256 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1257 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1257 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1258 911600dab2ae7a9baff75958b84fe606851ce955
1258 911600dab2ae7a9baff75958b84fe606851ce955
1259 bundle2-output-bundle: "HG20", 4 parts total
1259 bundle2-output-bundle: "HG20", 4 parts total
1260 bundle2-output-part: "replycaps" 155 bytes payload
1260 bundle2-output-part: "replycaps" 155 bytes payload
1261 bundle2-output-part: "check:heads" streamed payload
1261 bundle2-output-part: "check:heads" streamed payload
1262 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1262 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1263 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1263 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1264 bundle2-input-bundle: with-transaction
1264 bundle2-input-bundle: with-transaction
1265 bundle2-input-part: "replycaps" supported
1265 bundle2-input-part: "replycaps" supported
1266 bundle2-input-part: total payload size 155
1266 bundle2-input-part: total payload size 155
1267 bundle2-input-part: "check:heads" supported
1267 bundle2-input-part: "check:heads" supported
1268 bundle2-input-part: total payload size 20
1268 bundle2-input-part: total payload size 20
1269 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1269 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1270 adding changesets
1270 adding changesets
1271 add changeset ef1ea85a6374
1271 add changeset ef1ea85a6374
1272 add changeset f9cafe1212c8
1272 add changeset f9cafe1212c8
1273 add changeset 911600dab2ae
1273 add changeset 911600dab2ae
1274 adding manifests
1274 adding manifests
1275 adding file changes
1275 adding file changes
1276 adding foo/Bar/file.txt revisions
1276 adding foo/Bar/file.txt revisions
1277 adding foo/file.txt revisions
1277 adding foo/file.txt revisions
1278 adding quux/file.py revisions
1278 adding quux/file.py revisions
1279 added 3 changesets with 3 changes to 3 files
1279 added 3 changesets with 3 changes to 3 files
1280 calling hook pretxnchangegroup.acl: hgext.acl.hook
1280 calling hook pretxnchangegroup.acl: hgext.acl.hook
1281 acl: checking access for user "fred"
1281 acl: checking access for user "fred"
1282 acl: acl.allow.branches not enabled
1282 acl: acl.allow.branches not enabled
1283 acl: acl.deny.branches not enabled
1283 acl: acl.deny.branches not enabled
1284 acl: "group1" not defined in [acl.groups]
1284 acl: "group1" not defined in [acl.groups]
1285 acl: acl.allow enabled, 1 entries for user fred
1285 acl: acl.allow enabled, 1 entries for user fred
1286 acl: acl.deny not enabled
1286 acl: acl.deny not enabled
1287 acl: branch access granted: "ef1ea85a6374" on branch "default"
1287 acl: branch access granted: "ef1ea85a6374" on branch "default"
1288 acl: path access granted: "ef1ea85a6374"
1288 acl: path access granted: "ef1ea85a6374"
1289 acl: branch access granted: "f9cafe1212c8" on branch "default"
1289 acl: branch access granted: "f9cafe1212c8" on branch "default"
1290 acl: path access granted: "f9cafe1212c8"
1290 acl: path access granted: "f9cafe1212c8"
1291 acl: branch access granted: "911600dab2ae" on branch "default"
1291 acl: branch access granted: "911600dab2ae" on branch "default"
1292 acl: path access granted: "911600dab2ae"
1292 acl: path access granted: "911600dab2ae"
1293 bundle2-input-part: total payload size 1553
1293 bundle2-input-part: total payload size 1553
1294 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
1294 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
1295 pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
1295 pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
1296 bundle2-input-bundle: 3 parts total
1296 bundle2-input-bundle: 3 parts total
1297 updating the branch cache
1297 updating the branch cache
1298 bundle2-output-bundle: "HG20", 2 parts total
1298 bundle2-output-bundle: "HG20", 2 parts total
1299 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
1299 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
1300 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
1300 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
1301 bundle2-input-bundle: no-transaction
1301 bundle2-input-bundle: no-transaction
1302 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
1302 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
1303 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
1303 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
1304 bundle2-input-bundle: 1 parts total
1304 bundle2-input-bundle: 1 parts total
1305 listing keys for "phases"
1305 listing keys for "phases"
1306 repository tip rolled back to revision 0 (undo push)
1306 repository tip rolled back to revision 0 (undo push)
1307 0:6675d58eff77
1307 0:6675d58eff77
1308
1308
1309
1309
1310 $ echo '[acl.deny]' >> $config
1310 $ echo '[acl.deny]' >> $config
1311 $ echo "foo/Bar/** = @group1" >> $config
1311 $ echo "foo/Bar/** = @group1" >> $config
1312
1312
1313 @group is allowed inside anything but foo/Bar/
1313 @group is allowed inside anything but foo/Bar/
1314
1314
1315 $ do_push fred
1315 $ do_push fred
1316 Pushing as user fred
1316 Pushing as user fred
1317 hgrc = """
1317 hgrc = """
1318 [hooks]
1318 [hooks]
1319 pretxnchangegroup.acl = python:hgext.acl.hook
1319 pretxnchangegroup.acl = python:hgext.acl.hook
1320 [acl]
1320 [acl]
1321 sources = push
1321 sources = push
1322 [extensions]
1322 [extensions]
1323 [acl.allow]
1323 [acl.allow]
1324 ** = @group1
1324 ** = @group1
1325 [acl.deny]
1325 [acl.deny]
1326 foo/Bar/** = @group1
1326 foo/Bar/** = @group1
1327 """
1327 """
1328 pushing to ../b
1328 pushing to ../b
1329 query 1; heads
1329 query 1; heads
1330 searching for changes
1330 searching for changes
1331 all remote heads known locally
1331 all remote heads known locally
1332 listing keys for "phases"
1332 listing keys for "phases"
1333 checking for updated bookmarks
1333 checking for updated bookmarks
1334 listing keys for "bookmarks"
1334 listing keys for "bookmarks"
1335 listing keys for "bookmarks"
1335 listing keys for "bookmarks"
1336 3 changesets found
1336 3 changesets found
1337 list of changesets:
1337 list of changesets:
1338 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1338 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1339 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1339 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1340 911600dab2ae7a9baff75958b84fe606851ce955
1340 911600dab2ae7a9baff75958b84fe606851ce955
1341 bundle2-output-bundle: "HG20", 4 parts total
1341 bundle2-output-bundle: "HG20", 4 parts total
1342 bundle2-output-part: "replycaps" 155 bytes payload
1342 bundle2-output-part: "replycaps" 155 bytes payload
1343 bundle2-output-part: "check:heads" streamed payload
1343 bundle2-output-part: "check:heads" streamed payload
1344 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1344 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1345 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1345 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1346 bundle2-input-bundle: with-transaction
1346 bundle2-input-bundle: with-transaction
1347 bundle2-input-part: "replycaps" supported
1347 bundle2-input-part: "replycaps" supported
1348 bundle2-input-part: total payload size 155
1348 bundle2-input-part: total payload size 155
1349 bundle2-input-part: "check:heads" supported
1349 bundle2-input-part: "check:heads" supported
1350 bundle2-input-part: total payload size 20
1350 bundle2-input-part: total payload size 20
1351 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1351 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1352 adding changesets
1352 adding changesets
1353 add changeset ef1ea85a6374
1353 add changeset ef1ea85a6374
1354 add changeset f9cafe1212c8
1354 add changeset f9cafe1212c8
1355 add changeset 911600dab2ae
1355 add changeset 911600dab2ae
1356 adding manifests
1356 adding manifests
1357 adding file changes
1357 adding file changes
1358 adding foo/Bar/file.txt revisions
1358 adding foo/Bar/file.txt revisions
1359 adding foo/file.txt revisions
1359 adding foo/file.txt revisions
1360 adding quux/file.py revisions
1360 adding quux/file.py revisions
1361 added 3 changesets with 3 changes to 3 files
1361 added 3 changesets with 3 changes to 3 files
1362 calling hook pretxnchangegroup.acl: hgext.acl.hook
1362 calling hook pretxnchangegroup.acl: hgext.acl.hook
1363 acl: checking access for user "fred"
1363 acl: checking access for user "fred"
1364 acl: acl.allow.branches not enabled
1364 acl: acl.allow.branches not enabled
1365 acl: acl.deny.branches not enabled
1365 acl: acl.deny.branches not enabled
1366 acl: "group1" not defined in [acl.groups]
1366 acl: "group1" not defined in [acl.groups]
1367 acl: acl.allow enabled, 1 entries for user fred
1367 acl: acl.allow enabled, 1 entries for user fred
1368 acl: "group1" not defined in [acl.groups]
1368 acl: "group1" not defined in [acl.groups]
1369 acl: acl.deny enabled, 1 entries for user fred
1369 acl: acl.deny enabled, 1 entries for user fred
1370 acl: branch access granted: "ef1ea85a6374" on branch "default"
1370 acl: branch access granted: "ef1ea85a6374" on branch "default"
1371 acl: path access granted: "ef1ea85a6374"
1371 acl: path access granted: "ef1ea85a6374"
1372 acl: branch access granted: "f9cafe1212c8" on branch "default"
1372 acl: branch access granted: "f9cafe1212c8" on branch "default"
1373 error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
1373 error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
1374 bundle2-input-part: total payload size 1553
1374 bundle2-input-part: total payload size 1553
1375 bundle2-input-bundle: 3 parts total
1375 bundle2-input-bundle: 3 parts total
1376 transaction abort!
1376 transaction abort!
1377 rollback completed
1377 rollback completed
1378 abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
1378 abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
1379 no rollback information available
1379 no rollback information available
1380 0:6675d58eff77
1380 0:6675d58eff77
1381
1381
1382
1382
1383 Invalid group
1383 Invalid group
1384
1384
1385 Disable the fakegroups trick to get real failures
1385 Disable the fakegroups trick to get real failures
1386
1386
1387 $ grep -v fakegroups $config > config.tmp
1387 $ grep -v fakegroups $config > config.tmp
1388 $ mv config.tmp $config
1388 $ mv config.tmp $config
1389 $ echo '[acl.allow]' >> $config
1389 $ echo '[acl.allow]' >> $config
1390 $ echo "** = @unlikelytoexist" >> $config
1390 $ echo "** = @unlikelytoexist" >> $config
1391 $ do_push fred 2>&1 | grep unlikelytoexist
1391 $ do_push fred 2>&1 | grep unlikelytoexist
1392 ** = @unlikelytoexist
1392 ** = @unlikelytoexist
1393 acl: "unlikelytoexist" not defined in [acl.groups]
1393 acl: "unlikelytoexist" not defined in [acl.groups]
1394 error: pretxnchangegroup.acl hook failed: group 'unlikelytoexist' is undefined
1394 error: pretxnchangegroup.acl hook failed: group 'unlikelytoexist' is undefined
1395 abort: group 'unlikelytoexist' is undefined
1395 abort: group 'unlikelytoexist' is undefined
1396
1396
1397
1397
1398 Branch acl tests setup
1398 Branch acl tests setup
1399
1399
1400 $ init_config
1400 $ init_config
1401 $ cd b
1401 $ cd b
1402 $ hg up
1402 $ hg up
1403 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1403 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1404 $ hg branch foobar
1404 $ hg branch foobar
1405 marked working directory as branch foobar
1405 marked working directory as branch foobar
1406 (branches are permanent and global, did you want a bookmark?)
1406 (branches are permanent and global, did you want a bookmark?)
1407 $ hg commit -m 'create foobar'
1407 $ hg commit -m 'create foobar'
1408 $ echo 'foo contents' > abc.txt
1408 $ echo 'foo contents' > abc.txt
1409 $ hg add abc.txt
1409 $ hg add abc.txt
1410 $ hg commit -m 'foobar contents'
1410 $ hg commit -m 'foobar contents'
1411 $ cd ..
1411 $ cd ..
1412 $ hg --cwd a pull ../b
1412 $ hg --cwd a pull ../b
1413 pulling from ../b
1413 pulling from ../b
1414 searching for changes
1414 searching for changes
1415 adding changesets
1415 adding changesets
1416 adding manifests
1416 adding manifests
1417 adding file changes
1417 adding file changes
1418 added 2 changesets with 1 changes to 1 files (+1 heads)
1418 added 2 changesets with 1 changes to 1 files (+1 heads)
1419 (run 'hg heads' to see heads)
1419 (run 'hg heads' to see heads)
1420
1420
1421 Create additional changeset on foobar branch
1421 Create additional changeset on foobar branch
1422
1422
1423 $ cd a
1423 $ cd a
1424 $ hg up -C foobar
1424 $ hg up -C foobar
1425 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
1425 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
1426 $ echo 'foo contents2' > abc.txt
1426 $ echo 'foo contents2' > abc.txt
1427 $ hg commit -m 'foobar contents2'
1427 $ hg commit -m 'foobar contents2'
1428 $ cd ..
1428 $ cd ..
1429
1429
1430
1430
1431 No branch acls specified
1431 No branch acls specified
1432
1432
1433 $ do_push astro
1433 $ do_push astro
1434 Pushing as user astro
1434 Pushing as user astro
1435 hgrc = """
1435 hgrc = """
1436 [hooks]
1436 [hooks]
1437 pretxnchangegroup.acl = python:hgext.acl.hook
1437 pretxnchangegroup.acl = python:hgext.acl.hook
1438 [acl]
1438 [acl]
1439 sources = push
1439 sources = push
1440 [extensions]
1440 [extensions]
1441 """
1441 """
1442 pushing to ../b
1442 pushing to ../b
1443 query 1; heads
1443 query 1; heads
1444 searching for changes
1444 searching for changes
1445 all remote heads known locally
1445 all remote heads known locally
1446 listing keys for "phases"
1446 listing keys for "phases"
1447 checking for updated bookmarks
1447 checking for updated bookmarks
1448 listing keys for "bookmarks"
1448 listing keys for "bookmarks"
1449 listing keys for "bookmarks"
1449 listing keys for "bookmarks"
1450 4 changesets found
1450 4 changesets found
1451 list of changesets:
1451 list of changesets:
1452 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1452 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1453 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1453 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1454 911600dab2ae7a9baff75958b84fe606851ce955
1454 911600dab2ae7a9baff75958b84fe606851ce955
1455 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1455 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1456 bundle2-output-bundle: "HG20", 5 parts total
1456 bundle2-output-bundle: "HG20", 5 parts total
1457 bundle2-output-part: "replycaps" 155 bytes payload
1457 bundle2-output-part: "replycaps" 155 bytes payload
1458 bundle2-output-part: "check:heads" streamed payload
1458 bundle2-output-part: "check:heads" streamed payload
1459 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1459 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1460 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1460 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1461 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1461 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1462 bundle2-input-bundle: with-transaction
1462 bundle2-input-bundle: with-transaction
1463 bundle2-input-part: "replycaps" supported
1463 bundle2-input-part: "replycaps" supported
1464 bundle2-input-part: total payload size 155
1464 bundle2-input-part: total payload size 155
1465 bundle2-input-part: "check:heads" supported
1465 bundle2-input-part: "check:heads" supported
1466 bundle2-input-part: total payload size 20
1466 bundle2-input-part: total payload size 20
1467 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1467 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1468 adding changesets
1468 adding changesets
1469 add changeset ef1ea85a6374
1469 add changeset ef1ea85a6374
1470 add changeset f9cafe1212c8
1470 add changeset f9cafe1212c8
1471 add changeset 911600dab2ae
1471 add changeset 911600dab2ae
1472 add changeset e8fc755d4d82
1472 add changeset e8fc755d4d82
1473 adding manifests
1473 adding manifests
1474 adding file changes
1474 adding file changes
1475 adding abc.txt revisions
1475 adding abc.txt revisions
1476 adding foo/Bar/file.txt revisions
1476 adding foo/Bar/file.txt revisions
1477 adding foo/file.txt revisions
1477 adding foo/file.txt revisions
1478 adding quux/file.py revisions
1478 adding quux/file.py revisions
1479 added 4 changesets with 4 changes to 4 files (+1 heads)
1479 added 4 changesets with 4 changes to 4 files (+1 heads)
1480 calling hook pretxnchangegroup.acl: hgext.acl.hook
1480 calling hook pretxnchangegroup.acl: hgext.acl.hook
1481 acl: checking access for user "astro"
1481 acl: checking access for user "astro"
1482 acl: acl.allow.branches not enabled
1482 acl: acl.allow.branches not enabled
1483 acl: acl.deny.branches not enabled
1483 acl: acl.deny.branches not enabled
1484 acl: acl.allow not enabled
1484 acl: acl.allow not enabled
1485 acl: acl.deny not enabled
1485 acl: acl.deny not enabled
1486 acl: branch access granted: "ef1ea85a6374" on branch "default"
1486 acl: branch access granted: "ef1ea85a6374" on branch "default"
1487 acl: path access granted: "ef1ea85a6374"
1487 acl: path access granted: "ef1ea85a6374"
1488 acl: branch access granted: "f9cafe1212c8" on branch "default"
1488 acl: branch access granted: "f9cafe1212c8" on branch "default"
1489 acl: path access granted: "f9cafe1212c8"
1489 acl: path access granted: "f9cafe1212c8"
1490 acl: branch access granted: "911600dab2ae" on branch "default"
1490 acl: branch access granted: "911600dab2ae" on branch "default"
1491 acl: path access granted: "911600dab2ae"
1491 acl: path access granted: "911600dab2ae"
1492 acl: branch access granted: "e8fc755d4d82" on branch "foobar"
1492 acl: branch access granted: "e8fc755d4d82" on branch "foobar"
1493 acl: path access granted: "e8fc755d4d82"
1493 acl: path access granted: "e8fc755d4d82"
1494 bundle2-input-part: total payload size 2068
1494 bundle2-input-part: total payload size 2068
1495 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
1495 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
1496 pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
1496 pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
1497 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
1497 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
1498 pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01"
1498 pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01"
1499 bundle2-input-bundle: 4 parts total
1499 bundle2-input-bundle: 4 parts total
1500 updating the branch cache
1500 updating the branch cache
1501 bundle2-output-bundle: "HG20", 3 parts total
1501 bundle2-output-bundle: "HG20", 3 parts total
1502 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
1502 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
1503 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
1503 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
1504 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
1504 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
1505 bundle2-input-bundle: no-transaction
1505 bundle2-input-bundle: no-transaction
1506 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
1506 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
1507 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
1507 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
1508 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
1508 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
1509 bundle2-input-bundle: 2 parts total
1509 bundle2-input-bundle: 2 parts total
1510 listing keys for "phases"
1510 listing keys for "phases"
1511 repository tip rolled back to revision 2 (undo push)
1511 repository tip rolled back to revision 2 (undo push)
1512 2:fb35475503ef
1512 2:fb35475503ef
1513
1513
1514
1514
1515 Branch acl deny test
1515 Branch acl deny test
1516
1516
1517 $ echo "[acl.deny.branches]" >> $config
1517 $ echo "[acl.deny.branches]" >> $config
1518 $ echo "foobar = *" >> $config
1518 $ echo "foobar = *" >> $config
1519 $ do_push astro
1519 $ do_push astro
1520 Pushing as user astro
1520 Pushing as user astro
1521 hgrc = """
1521 hgrc = """
1522 [hooks]
1522 [hooks]
1523 pretxnchangegroup.acl = python:hgext.acl.hook
1523 pretxnchangegroup.acl = python:hgext.acl.hook
1524 [acl]
1524 [acl]
1525 sources = push
1525 sources = push
1526 [extensions]
1526 [extensions]
1527 [acl.deny.branches]
1527 [acl.deny.branches]
1528 foobar = *
1528 foobar = *
1529 """
1529 """
1530 pushing to ../b
1530 pushing to ../b
1531 query 1; heads
1531 query 1; heads
1532 searching for changes
1532 searching for changes
1533 all remote heads known locally
1533 all remote heads known locally
1534 listing keys for "phases"
1534 listing keys for "phases"
1535 checking for updated bookmarks
1535 checking for updated bookmarks
1536 listing keys for "bookmarks"
1536 listing keys for "bookmarks"
1537 listing keys for "bookmarks"
1537 listing keys for "bookmarks"
1538 4 changesets found
1538 4 changesets found
1539 list of changesets:
1539 list of changesets:
1540 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1540 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1541 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1541 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1542 911600dab2ae7a9baff75958b84fe606851ce955
1542 911600dab2ae7a9baff75958b84fe606851ce955
1543 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1543 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1544 bundle2-output-bundle: "HG20", 5 parts total
1544 bundle2-output-bundle: "HG20", 5 parts total
1545 bundle2-output-part: "replycaps" 155 bytes payload
1545 bundle2-output-part: "replycaps" 155 bytes payload
1546 bundle2-output-part: "check:heads" streamed payload
1546 bundle2-output-part: "check:heads" streamed payload
1547 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1547 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1548 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1548 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1549 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1549 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1550 bundle2-input-bundle: with-transaction
1550 bundle2-input-bundle: with-transaction
1551 bundle2-input-part: "replycaps" supported
1551 bundle2-input-part: "replycaps" supported
1552 bundle2-input-part: total payload size 155
1552 bundle2-input-part: total payload size 155
1553 bundle2-input-part: "check:heads" supported
1553 bundle2-input-part: "check:heads" supported
1554 bundle2-input-part: total payload size 20
1554 bundle2-input-part: total payload size 20
1555 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1555 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1556 adding changesets
1556 adding changesets
1557 add changeset ef1ea85a6374
1557 add changeset ef1ea85a6374
1558 add changeset f9cafe1212c8
1558 add changeset f9cafe1212c8
1559 add changeset 911600dab2ae
1559 add changeset 911600dab2ae
1560 add changeset e8fc755d4d82
1560 add changeset e8fc755d4d82
1561 adding manifests
1561 adding manifests
1562 adding file changes
1562 adding file changes
1563 adding abc.txt revisions
1563 adding abc.txt revisions
1564 adding foo/Bar/file.txt revisions
1564 adding foo/Bar/file.txt revisions
1565 adding foo/file.txt revisions
1565 adding foo/file.txt revisions
1566 adding quux/file.py revisions
1566 adding quux/file.py revisions
1567 added 4 changesets with 4 changes to 4 files (+1 heads)
1567 added 4 changesets with 4 changes to 4 files (+1 heads)
1568 calling hook pretxnchangegroup.acl: hgext.acl.hook
1568 calling hook pretxnchangegroup.acl: hgext.acl.hook
1569 acl: checking access for user "astro"
1569 acl: checking access for user "astro"
1570 acl: acl.allow.branches not enabled
1570 acl: acl.allow.branches not enabled
1571 acl: acl.deny.branches enabled, 1 entries for user astro
1571 acl: acl.deny.branches enabled, 1 entries for user astro
1572 acl: acl.allow not enabled
1572 acl: acl.allow not enabled
1573 acl: acl.deny not enabled
1573 acl: acl.deny not enabled
1574 acl: branch access granted: "ef1ea85a6374" on branch "default"
1574 acl: branch access granted: "ef1ea85a6374" on branch "default"
1575 acl: path access granted: "ef1ea85a6374"
1575 acl: path access granted: "ef1ea85a6374"
1576 acl: branch access granted: "f9cafe1212c8" on branch "default"
1576 acl: branch access granted: "f9cafe1212c8" on branch "default"
1577 acl: path access granted: "f9cafe1212c8"
1577 acl: path access granted: "f9cafe1212c8"
1578 acl: branch access granted: "911600dab2ae" on branch "default"
1578 acl: branch access granted: "911600dab2ae" on branch "default"
1579 acl: path access granted: "911600dab2ae"
1579 acl: path access granted: "911600dab2ae"
1580 error: pretxnchangegroup.acl hook failed: acl: user "astro" denied on branch "foobar" (changeset "e8fc755d4d82")
1580 error: pretxnchangegroup.acl hook failed: acl: user "astro" denied on branch "foobar" (changeset "e8fc755d4d82")
1581 bundle2-input-part: total payload size 2068
1581 bundle2-input-part: total payload size 2068
1582 bundle2-input-bundle: 4 parts total
1582 bundle2-input-bundle: 4 parts total
1583 transaction abort!
1583 transaction abort!
1584 rollback completed
1584 rollback completed
1585 abort: acl: user "astro" denied on branch "foobar" (changeset "e8fc755d4d82")
1585 abort: acl: user "astro" denied on branch "foobar" (changeset "e8fc755d4d82")
1586 no rollback information available
1586 no rollback information available
1587 2:fb35475503ef
1587 2:fb35475503ef
1588
1588
1589
1589
1590 Branch acl empty allow test
1590 Branch acl empty allow test
1591
1591
1592 $ init_config
1592 $ init_config
1593 $ echo "[acl.allow.branches]" >> $config
1593 $ echo "[acl.allow.branches]" >> $config
1594 $ do_push astro
1594 $ do_push astro
1595 Pushing as user astro
1595 Pushing as user astro
1596 hgrc = """
1596 hgrc = """
1597 [hooks]
1597 [hooks]
1598 pretxnchangegroup.acl = python:hgext.acl.hook
1598 pretxnchangegroup.acl = python:hgext.acl.hook
1599 [acl]
1599 [acl]
1600 sources = push
1600 sources = push
1601 [extensions]
1601 [extensions]
1602 [acl.allow.branches]
1602 [acl.allow.branches]
1603 """
1603 """
1604 pushing to ../b
1604 pushing to ../b
1605 query 1; heads
1605 query 1; heads
1606 searching for changes
1606 searching for changes
1607 all remote heads known locally
1607 all remote heads known locally
1608 listing keys for "phases"
1608 listing keys for "phases"
1609 checking for updated bookmarks
1609 checking for updated bookmarks
1610 listing keys for "bookmarks"
1610 listing keys for "bookmarks"
1611 listing keys for "bookmarks"
1611 listing keys for "bookmarks"
1612 4 changesets found
1612 4 changesets found
1613 list of changesets:
1613 list of changesets:
1614 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1614 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1615 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1615 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1616 911600dab2ae7a9baff75958b84fe606851ce955
1616 911600dab2ae7a9baff75958b84fe606851ce955
1617 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1617 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1618 bundle2-output-bundle: "HG20", 5 parts total
1618 bundle2-output-bundle: "HG20", 5 parts total
1619 bundle2-output-part: "replycaps" 155 bytes payload
1619 bundle2-output-part: "replycaps" 155 bytes payload
1620 bundle2-output-part: "check:heads" streamed payload
1620 bundle2-output-part: "check:heads" streamed payload
1621 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1621 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1622 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1622 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1623 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1623 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1624 bundle2-input-bundle: with-transaction
1624 bundle2-input-bundle: with-transaction
1625 bundle2-input-part: "replycaps" supported
1625 bundle2-input-part: "replycaps" supported
1626 bundle2-input-part: total payload size 155
1626 bundle2-input-part: total payload size 155
1627 bundle2-input-part: "check:heads" supported
1627 bundle2-input-part: "check:heads" supported
1628 bundle2-input-part: total payload size 20
1628 bundle2-input-part: total payload size 20
1629 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1629 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1630 adding changesets
1630 adding changesets
1631 add changeset ef1ea85a6374
1631 add changeset ef1ea85a6374
1632 add changeset f9cafe1212c8
1632 add changeset f9cafe1212c8
1633 add changeset 911600dab2ae
1633 add changeset 911600dab2ae
1634 add changeset e8fc755d4d82
1634 add changeset e8fc755d4d82
1635 adding manifests
1635 adding manifests
1636 adding file changes
1636 adding file changes
1637 adding abc.txt revisions
1637 adding abc.txt revisions
1638 adding foo/Bar/file.txt revisions
1638 adding foo/Bar/file.txt revisions
1639 adding foo/file.txt revisions
1639 adding foo/file.txt revisions
1640 adding quux/file.py revisions
1640 adding quux/file.py revisions
1641 added 4 changesets with 4 changes to 4 files (+1 heads)
1641 added 4 changesets with 4 changes to 4 files (+1 heads)
1642 calling hook pretxnchangegroup.acl: hgext.acl.hook
1642 calling hook pretxnchangegroup.acl: hgext.acl.hook
1643 acl: checking access for user "astro"
1643 acl: checking access for user "astro"
1644 acl: acl.allow.branches enabled, 0 entries for user astro
1644 acl: acl.allow.branches enabled, 0 entries for user astro
1645 acl: acl.deny.branches not enabled
1645 acl: acl.deny.branches not enabled
1646 acl: acl.allow not enabled
1646 acl: acl.allow not enabled
1647 acl: acl.deny not enabled
1647 acl: acl.deny not enabled
1648 error: pretxnchangegroup.acl hook failed: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
1648 error: pretxnchangegroup.acl hook failed: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
1649 bundle2-input-part: total payload size 2068
1649 bundle2-input-part: total payload size 2068
1650 bundle2-input-bundle: 4 parts total
1650 bundle2-input-bundle: 4 parts total
1651 transaction abort!
1651 transaction abort!
1652 rollback completed
1652 rollback completed
1653 abort: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
1653 abort: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
1654 no rollback information available
1654 no rollback information available
1655 2:fb35475503ef
1655 2:fb35475503ef
1656
1656
1657
1657
1658 Branch acl allow other
1658 Branch acl allow other
1659
1659
1660 $ init_config
1660 $ init_config
1661 $ echo "[acl.allow.branches]" >> $config
1661 $ echo "[acl.allow.branches]" >> $config
1662 $ echo "* = george" >> $config
1662 $ echo "* = george" >> $config
1663 $ do_push astro
1663 $ do_push astro
1664 Pushing as user astro
1664 Pushing as user astro
1665 hgrc = """
1665 hgrc = """
1666 [hooks]
1666 [hooks]
1667 pretxnchangegroup.acl = python:hgext.acl.hook
1667 pretxnchangegroup.acl = python:hgext.acl.hook
1668 [acl]
1668 [acl]
1669 sources = push
1669 sources = push
1670 [extensions]
1670 [extensions]
1671 [acl.allow.branches]
1671 [acl.allow.branches]
1672 * = george
1672 * = george
1673 """
1673 """
1674 pushing to ../b
1674 pushing to ../b
1675 query 1; heads
1675 query 1; heads
1676 searching for changes
1676 searching for changes
1677 all remote heads known locally
1677 all remote heads known locally
1678 listing keys for "phases"
1678 listing keys for "phases"
1679 checking for updated bookmarks
1679 checking for updated bookmarks
1680 listing keys for "bookmarks"
1680 listing keys for "bookmarks"
1681 listing keys for "bookmarks"
1681 listing keys for "bookmarks"
1682 4 changesets found
1682 4 changesets found
1683 list of changesets:
1683 list of changesets:
1684 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1684 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1685 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1685 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1686 911600dab2ae7a9baff75958b84fe606851ce955
1686 911600dab2ae7a9baff75958b84fe606851ce955
1687 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1687 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1688 bundle2-output-bundle: "HG20", 5 parts total
1688 bundle2-output-bundle: "HG20", 5 parts total
1689 bundle2-output-part: "replycaps" 155 bytes payload
1689 bundle2-output-part: "replycaps" 155 bytes payload
1690 bundle2-output-part: "check:heads" streamed payload
1690 bundle2-output-part: "check:heads" streamed payload
1691 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1691 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1692 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1692 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1693 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1693 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1694 bundle2-input-bundle: with-transaction
1694 bundle2-input-bundle: with-transaction
1695 bundle2-input-part: "replycaps" supported
1695 bundle2-input-part: "replycaps" supported
1696 bundle2-input-part: total payload size 155
1696 bundle2-input-part: total payload size 155
1697 bundle2-input-part: "check:heads" supported
1697 bundle2-input-part: "check:heads" supported
1698 bundle2-input-part: total payload size 20
1698 bundle2-input-part: total payload size 20
1699 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1699 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1700 adding changesets
1700 adding changesets
1701 add changeset ef1ea85a6374
1701 add changeset ef1ea85a6374
1702 add changeset f9cafe1212c8
1702 add changeset f9cafe1212c8
1703 add changeset 911600dab2ae
1703 add changeset 911600dab2ae
1704 add changeset e8fc755d4d82
1704 add changeset e8fc755d4d82
1705 adding manifests
1705 adding manifests
1706 adding file changes
1706 adding file changes
1707 adding abc.txt revisions
1707 adding abc.txt revisions
1708 adding foo/Bar/file.txt revisions
1708 adding foo/Bar/file.txt revisions
1709 adding foo/file.txt revisions
1709 adding foo/file.txt revisions
1710 adding quux/file.py revisions
1710 adding quux/file.py revisions
1711 added 4 changesets with 4 changes to 4 files (+1 heads)
1711 added 4 changesets with 4 changes to 4 files (+1 heads)
1712 calling hook pretxnchangegroup.acl: hgext.acl.hook
1712 calling hook pretxnchangegroup.acl: hgext.acl.hook
1713 acl: checking access for user "astro"
1713 acl: checking access for user "astro"
1714 acl: acl.allow.branches enabled, 0 entries for user astro
1714 acl: acl.allow.branches enabled, 0 entries for user astro
1715 acl: acl.deny.branches not enabled
1715 acl: acl.deny.branches not enabled
1716 acl: acl.allow not enabled
1716 acl: acl.allow not enabled
1717 acl: acl.deny not enabled
1717 acl: acl.deny not enabled
1718 error: pretxnchangegroup.acl hook failed: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
1718 error: pretxnchangegroup.acl hook failed: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
1719 bundle2-input-part: total payload size 2068
1719 bundle2-input-part: total payload size 2068
1720 bundle2-input-bundle: 4 parts total
1720 bundle2-input-bundle: 4 parts total
1721 transaction abort!
1721 transaction abort!
1722 rollback completed
1722 rollback completed
1723 abort: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
1723 abort: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
1724 no rollback information available
1724 no rollback information available
1725 2:fb35475503ef
1725 2:fb35475503ef
1726
1726
1727 $ do_push george
1727 $ do_push george
1728 Pushing as user george
1728 Pushing as user george
1729 hgrc = """
1729 hgrc = """
1730 [hooks]
1730 [hooks]
1731 pretxnchangegroup.acl = python:hgext.acl.hook
1731 pretxnchangegroup.acl = python:hgext.acl.hook
1732 [acl]
1732 [acl]
1733 sources = push
1733 sources = push
1734 [extensions]
1734 [extensions]
1735 [acl.allow.branches]
1735 [acl.allow.branches]
1736 * = george
1736 * = george
1737 """
1737 """
1738 pushing to ../b
1738 pushing to ../b
1739 query 1; heads
1739 query 1; heads
1740 searching for changes
1740 searching for changes
1741 all remote heads known locally
1741 all remote heads known locally
1742 listing keys for "phases"
1742 listing keys for "phases"
1743 checking for updated bookmarks
1743 checking for updated bookmarks
1744 listing keys for "bookmarks"
1744 listing keys for "bookmarks"
1745 listing keys for "bookmarks"
1745 listing keys for "bookmarks"
1746 4 changesets found
1746 4 changesets found
1747 list of changesets:
1747 list of changesets:
1748 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1748 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1749 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1749 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1750 911600dab2ae7a9baff75958b84fe606851ce955
1750 911600dab2ae7a9baff75958b84fe606851ce955
1751 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1751 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1752 bundle2-output-bundle: "HG20", 5 parts total
1752 bundle2-output-bundle: "HG20", 5 parts total
1753 bundle2-output-part: "replycaps" 155 bytes payload
1753 bundle2-output-part: "replycaps" 155 bytes payload
1754 bundle2-output-part: "check:heads" streamed payload
1754 bundle2-output-part: "check:heads" streamed payload
1755 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1755 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1756 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1756 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1757 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1757 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1758 bundle2-input-bundle: with-transaction
1758 bundle2-input-bundle: with-transaction
1759 bundle2-input-part: "replycaps" supported
1759 bundle2-input-part: "replycaps" supported
1760 bundle2-input-part: total payload size 155
1760 bundle2-input-part: total payload size 155
1761 bundle2-input-part: "check:heads" supported
1761 bundle2-input-part: "check:heads" supported
1762 bundle2-input-part: total payload size 20
1762 bundle2-input-part: total payload size 20
1763 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1763 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1764 adding changesets
1764 adding changesets
1765 add changeset ef1ea85a6374
1765 add changeset ef1ea85a6374
1766 add changeset f9cafe1212c8
1766 add changeset f9cafe1212c8
1767 add changeset 911600dab2ae
1767 add changeset 911600dab2ae
1768 add changeset e8fc755d4d82
1768 add changeset e8fc755d4d82
1769 adding manifests
1769 adding manifests
1770 adding file changes
1770 adding file changes
1771 adding abc.txt revisions
1771 adding abc.txt revisions
1772 adding foo/Bar/file.txt revisions
1772 adding foo/Bar/file.txt revisions
1773 adding foo/file.txt revisions
1773 adding foo/file.txt revisions
1774 adding quux/file.py revisions
1774 adding quux/file.py revisions
1775 added 4 changesets with 4 changes to 4 files (+1 heads)
1775 added 4 changesets with 4 changes to 4 files (+1 heads)
1776 calling hook pretxnchangegroup.acl: hgext.acl.hook
1776 calling hook pretxnchangegroup.acl: hgext.acl.hook
1777 acl: checking access for user "george"
1777 acl: checking access for user "george"
1778 acl: acl.allow.branches enabled, 1 entries for user george
1778 acl: acl.allow.branches enabled, 1 entries for user george
1779 acl: acl.deny.branches not enabled
1779 acl: acl.deny.branches not enabled
1780 acl: acl.allow not enabled
1780 acl: acl.allow not enabled
1781 acl: acl.deny not enabled
1781 acl: acl.deny not enabled
1782 acl: branch access granted: "ef1ea85a6374" on branch "default"
1782 acl: branch access granted: "ef1ea85a6374" on branch "default"
1783 acl: path access granted: "ef1ea85a6374"
1783 acl: path access granted: "ef1ea85a6374"
1784 acl: branch access granted: "f9cafe1212c8" on branch "default"
1784 acl: branch access granted: "f9cafe1212c8" on branch "default"
1785 acl: path access granted: "f9cafe1212c8"
1785 acl: path access granted: "f9cafe1212c8"
1786 acl: branch access granted: "911600dab2ae" on branch "default"
1786 acl: branch access granted: "911600dab2ae" on branch "default"
1787 acl: path access granted: "911600dab2ae"
1787 acl: path access granted: "911600dab2ae"
1788 acl: branch access granted: "e8fc755d4d82" on branch "foobar"
1788 acl: branch access granted: "e8fc755d4d82" on branch "foobar"
1789 acl: path access granted: "e8fc755d4d82"
1789 acl: path access granted: "e8fc755d4d82"
1790 bundle2-input-part: total payload size 2068
1790 bundle2-input-part: total payload size 2068
1791 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
1791 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
1792 pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
1792 pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
1793 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
1793 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
1794 pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01"
1794 pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01"
1795 bundle2-input-bundle: 4 parts total
1795 bundle2-input-bundle: 4 parts total
1796 updating the branch cache
1796 updating the branch cache
1797 bundle2-output-bundle: "HG20", 3 parts total
1797 bundle2-output-bundle: "HG20", 3 parts total
1798 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
1798 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
1799 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
1799 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
1800 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
1800 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
1801 bundle2-input-bundle: no-transaction
1801 bundle2-input-bundle: no-transaction
1802 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
1802 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
1803 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
1803 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
1804 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
1804 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
1805 bundle2-input-bundle: 2 parts total
1805 bundle2-input-bundle: 2 parts total
1806 listing keys for "phases"
1806 listing keys for "phases"
1807 repository tip rolled back to revision 2 (undo push)
1807 repository tip rolled back to revision 2 (undo push)
1808 2:fb35475503ef
1808 2:fb35475503ef
1809
1809
1810
1810
1811 Branch acl conflicting allow
1811 Branch acl conflicting allow
1812 asterisk ends up applying to all branches and allowing george to
1812 asterisk ends up applying to all branches and allowing george to
1813 push foobar into the remote
1813 push foobar into the remote
1814
1814
1815 $ init_config
1815 $ init_config
1816 $ echo "[acl.allow.branches]" >> $config
1816 $ echo "[acl.allow.branches]" >> $config
1817 $ echo "foobar = astro" >> $config
1817 $ echo "foobar = astro" >> $config
1818 $ echo "* = george" >> $config
1818 $ echo "* = george" >> $config
1819 $ do_push george
1819 $ do_push george
1820 Pushing as user george
1820 Pushing as user george
1821 hgrc = """
1821 hgrc = """
1822 [hooks]
1822 [hooks]
1823 pretxnchangegroup.acl = python:hgext.acl.hook
1823 pretxnchangegroup.acl = python:hgext.acl.hook
1824 [acl]
1824 [acl]
1825 sources = push
1825 sources = push
1826 [extensions]
1826 [extensions]
1827 [acl.allow.branches]
1827 [acl.allow.branches]
1828 foobar = astro
1828 foobar = astro
1829 * = george
1829 * = george
1830 """
1830 """
1831 pushing to ../b
1831 pushing to ../b
1832 query 1; heads
1832 query 1; heads
1833 searching for changes
1833 searching for changes
1834 all remote heads known locally
1834 all remote heads known locally
1835 listing keys for "phases"
1835 listing keys for "phases"
1836 checking for updated bookmarks
1836 checking for updated bookmarks
1837 listing keys for "bookmarks"
1837 listing keys for "bookmarks"
1838 listing keys for "bookmarks"
1838 listing keys for "bookmarks"
1839 4 changesets found
1839 4 changesets found
1840 list of changesets:
1840 list of changesets:
1841 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1841 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1842 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1842 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1843 911600dab2ae7a9baff75958b84fe606851ce955
1843 911600dab2ae7a9baff75958b84fe606851ce955
1844 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1844 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1845 bundle2-output-bundle: "HG20", 5 parts total
1845 bundle2-output-bundle: "HG20", 5 parts total
1846 bundle2-output-part: "replycaps" 155 bytes payload
1846 bundle2-output-part: "replycaps" 155 bytes payload
1847 bundle2-output-part: "check:heads" streamed payload
1847 bundle2-output-part: "check:heads" streamed payload
1848 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1848 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1849 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1849 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1850 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1850 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1851 bundle2-input-bundle: with-transaction
1851 bundle2-input-bundle: with-transaction
1852 bundle2-input-part: "replycaps" supported
1852 bundle2-input-part: "replycaps" supported
1853 bundle2-input-part: total payload size 155
1853 bundle2-input-part: total payload size 155
1854 bundle2-input-part: "check:heads" supported
1854 bundle2-input-part: "check:heads" supported
1855 bundle2-input-part: total payload size 20
1855 bundle2-input-part: total payload size 20
1856 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1856 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1857 adding changesets
1857 adding changesets
1858 add changeset ef1ea85a6374
1858 add changeset ef1ea85a6374
1859 add changeset f9cafe1212c8
1859 add changeset f9cafe1212c8
1860 add changeset 911600dab2ae
1860 add changeset 911600dab2ae
1861 add changeset e8fc755d4d82
1861 add changeset e8fc755d4d82
1862 adding manifests
1862 adding manifests
1863 adding file changes
1863 adding file changes
1864 adding abc.txt revisions
1864 adding abc.txt revisions
1865 adding foo/Bar/file.txt revisions
1865 adding foo/Bar/file.txt revisions
1866 adding foo/file.txt revisions
1866 adding foo/file.txt revisions
1867 adding quux/file.py revisions
1867 adding quux/file.py revisions
1868 added 4 changesets with 4 changes to 4 files (+1 heads)
1868 added 4 changesets with 4 changes to 4 files (+1 heads)
1869 calling hook pretxnchangegroup.acl: hgext.acl.hook
1869 calling hook pretxnchangegroup.acl: hgext.acl.hook
1870 acl: checking access for user "george"
1870 acl: checking access for user "george"
1871 acl: acl.allow.branches enabled, 1 entries for user george
1871 acl: acl.allow.branches enabled, 1 entries for user george
1872 acl: acl.deny.branches not enabled
1872 acl: acl.deny.branches not enabled
1873 acl: acl.allow not enabled
1873 acl: acl.allow not enabled
1874 acl: acl.deny not enabled
1874 acl: acl.deny not enabled
1875 acl: branch access granted: "ef1ea85a6374" on branch "default"
1875 acl: branch access granted: "ef1ea85a6374" on branch "default"
1876 acl: path access granted: "ef1ea85a6374"
1876 acl: path access granted: "ef1ea85a6374"
1877 acl: branch access granted: "f9cafe1212c8" on branch "default"
1877 acl: branch access granted: "f9cafe1212c8" on branch "default"
1878 acl: path access granted: "f9cafe1212c8"
1878 acl: path access granted: "f9cafe1212c8"
1879 acl: branch access granted: "911600dab2ae" on branch "default"
1879 acl: branch access granted: "911600dab2ae" on branch "default"
1880 acl: path access granted: "911600dab2ae"
1880 acl: path access granted: "911600dab2ae"
1881 acl: branch access granted: "e8fc755d4d82" on branch "foobar"
1881 acl: branch access granted: "e8fc755d4d82" on branch "foobar"
1882 acl: path access granted: "e8fc755d4d82"
1882 acl: path access granted: "e8fc755d4d82"
1883 bundle2-input-part: total payload size 2068
1883 bundle2-input-part: total payload size 2068
1884 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
1884 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
1885 pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
1885 pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
1886 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
1886 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
1887 pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01"
1887 pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01"
1888 bundle2-input-bundle: 4 parts total
1888 bundle2-input-bundle: 4 parts total
1889 updating the branch cache
1889 updating the branch cache
1890 bundle2-output-bundle: "HG20", 3 parts total
1890 bundle2-output-bundle: "HG20", 3 parts total
1891 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
1891 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
1892 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
1892 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
1893 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
1893 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
1894 bundle2-input-bundle: no-transaction
1894 bundle2-input-bundle: no-transaction
1895 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
1895 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
1896 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
1896 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
1897 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
1897 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
1898 bundle2-input-bundle: 2 parts total
1898 bundle2-input-bundle: 2 parts total
1899 listing keys for "phases"
1899 listing keys for "phases"
1900 repository tip rolled back to revision 2 (undo push)
1900 repository tip rolled back to revision 2 (undo push)
1901 2:fb35475503ef
1901 2:fb35475503ef
1902
1902
1903 Branch acl conflicting deny
1903 Branch acl conflicting deny
1904
1904
1905 $ init_config
1905 $ init_config
1906 $ echo "[acl.deny.branches]" >> $config
1906 $ echo "[acl.deny.branches]" >> $config
1907 $ echo "foobar = astro" >> $config
1907 $ echo "foobar = astro" >> $config
1908 $ echo "default = astro" >> $config
1908 $ echo "default = astro" >> $config
1909 $ echo "* = george" >> $config
1909 $ echo "* = george" >> $config
1910 $ do_push george
1910 $ do_push george
1911 Pushing as user george
1911 Pushing as user george
1912 hgrc = """
1912 hgrc = """
1913 [hooks]
1913 [hooks]
1914 pretxnchangegroup.acl = python:hgext.acl.hook
1914 pretxnchangegroup.acl = python:hgext.acl.hook
1915 [acl]
1915 [acl]
1916 sources = push
1916 sources = push
1917 [extensions]
1917 [extensions]
1918 [acl.deny.branches]
1918 [acl.deny.branches]
1919 foobar = astro
1919 foobar = astro
1920 default = astro
1920 default = astro
1921 * = george
1921 * = george
1922 """
1922 """
1923 pushing to ../b
1923 pushing to ../b
1924 query 1; heads
1924 query 1; heads
1925 searching for changes
1925 searching for changes
1926 all remote heads known locally
1926 all remote heads known locally
1927 listing keys for "phases"
1927 listing keys for "phases"
1928 checking for updated bookmarks
1928 checking for updated bookmarks
1929 listing keys for "bookmarks"
1929 listing keys for "bookmarks"
1930 listing keys for "bookmarks"
1930 listing keys for "bookmarks"
1931 4 changesets found
1931 4 changesets found
1932 list of changesets:
1932 list of changesets:
1933 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1933 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
1934 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1934 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
1935 911600dab2ae7a9baff75958b84fe606851ce955
1935 911600dab2ae7a9baff75958b84fe606851ce955
1936 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1936 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
1937 bundle2-output-bundle: "HG20", 5 parts total
1937 bundle2-output-bundle: "HG20", 5 parts total
1938 bundle2-output-part: "replycaps" 155 bytes payload
1938 bundle2-output-part: "replycaps" 155 bytes payload
1939 bundle2-output-part: "check:heads" streamed payload
1939 bundle2-output-part: "check:heads" streamed payload
1940 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1940 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
1941 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1941 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1942 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1942 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
1943 bundle2-input-bundle: with-transaction
1943 bundle2-input-bundle: with-transaction
1944 bundle2-input-part: "replycaps" supported
1944 bundle2-input-part: "replycaps" supported
1945 bundle2-input-part: total payload size 155
1945 bundle2-input-part: total payload size 155
1946 bundle2-input-part: "check:heads" supported
1946 bundle2-input-part: "check:heads" supported
1947 bundle2-input-part: total payload size 20
1947 bundle2-input-part: total payload size 20
1948 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1948 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
1949 adding changesets
1949 adding changesets
1950 add changeset ef1ea85a6374
1950 add changeset ef1ea85a6374
1951 add changeset f9cafe1212c8
1951 add changeset f9cafe1212c8
1952 add changeset 911600dab2ae
1952 add changeset 911600dab2ae
1953 add changeset e8fc755d4d82
1953 add changeset e8fc755d4d82
1954 adding manifests
1954 adding manifests
1955 adding file changes
1955 adding file changes
1956 adding abc.txt revisions
1956 adding abc.txt revisions
1957 adding foo/Bar/file.txt revisions
1957 adding foo/Bar/file.txt revisions
1958 adding foo/file.txt revisions
1958 adding foo/file.txt revisions
1959 adding quux/file.py revisions
1959 adding quux/file.py revisions
1960 added 4 changesets with 4 changes to 4 files (+1 heads)
1960 added 4 changesets with 4 changes to 4 files (+1 heads)
1961 calling hook pretxnchangegroup.acl: hgext.acl.hook
1961 calling hook pretxnchangegroup.acl: hgext.acl.hook
1962 acl: checking access for user "george"
1962 acl: checking access for user "george"
1963 acl: acl.allow.branches not enabled
1963 acl: acl.allow.branches not enabled
1964 acl: acl.deny.branches enabled, 1 entries for user george
1964 acl: acl.deny.branches enabled, 1 entries for user george
1965 acl: acl.allow not enabled
1965 acl: acl.allow not enabled
1966 acl: acl.deny not enabled
1966 acl: acl.deny not enabled
1967 error: pretxnchangegroup.acl hook failed: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
1967 error: pretxnchangegroup.acl hook failed: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
1968 bundle2-input-part: total payload size 2068
1968 bundle2-input-part: total payload size 2068
1969 bundle2-input-bundle: 4 parts total
1969 bundle2-input-bundle: 4 parts total
1970 transaction abort!
1970 transaction abort!
1971 rollback completed
1971 rollback completed
1972 abort: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
1972 abort: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
1973 no rollback information available
1973 no rollback information available
1974 2:fb35475503ef
1974 2:fb35475503ef
1975
1975
1976 User 'astro' must not be denied
1976 User 'astro' must not be denied
1977
1977
1978 $ init_config
1978 $ init_config
1979 $ echo "[acl.deny.branches]" >> $config
1979 $ echo "[acl.deny.branches]" >> $config
1980 $ echo "default = !astro" >> $config
1980 $ echo "default = !astro" >> $config
1981 $ do_push astro
1981 $ do_push astro
1982 Pushing as user astro
1982 Pushing as user astro
1983 hgrc = """
1983 hgrc = """
1984 [hooks]
1984 [hooks]
1985 pretxnchangegroup.acl = python:hgext.acl.hook
1985 pretxnchangegroup.acl = python:hgext.acl.hook
1986 [acl]
1986 [acl]
1987 sources = push
1987 sources = push
1988 [extensions]
1988 [extensions]
1989 [acl.deny.branches]
1989 [acl.deny.branches]
1990 default = !astro
1990 default = !astro
1991 """
1991 """
1992 pushing to ../b
1992 pushing to ../b
1993 query 1; heads
1993 query 1; heads
1994 searching for changes
1994 searching for changes
1995 all remote heads known locally
1995 all remote heads known locally
1996 listing keys for "phases"
1996 listing keys for "phases"
1997 checking for updated bookmarks
1997 checking for updated bookmarks
1998 listing keys for "bookmarks"
1998 listing keys for "bookmarks"
1999 listing keys for "bookmarks"
1999 listing keys for "bookmarks"
2000 4 changesets found
2000 4 changesets found
2001 list of changesets:
2001 list of changesets:
2002 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
2002 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
2003 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
2003 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
2004 911600dab2ae7a9baff75958b84fe606851ce955
2004 911600dab2ae7a9baff75958b84fe606851ce955
2005 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
2005 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
2006 bundle2-output-bundle: "HG20", 5 parts total
2006 bundle2-output-bundle: "HG20", 5 parts total
2007 bundle2-output-part: "replycaps" 155 bytes payload
2007 bundle2-output-part: "replycaps" 155 bytes payload
2008 bundle2-output-part: "check:heads" streamed payload
2008 bundle2-output-part: "check:heads" streamed payload
2009 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
2009 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
2010 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
2010 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
2011 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
2011 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
2012 bundle2-input-bundle: with-transaction
2012 bundle2-input-bundle: with-transaction
2013 bundle2-input-part: "replycaps" supported
2013 bundle2-input-part: "replycaps" supported
2014 bundle2-input-part: total payload size 155
2014 bundle2-input-part: total payload size 155
2015 bundle2-input-part: "check:heads" supported
2015 bundle2-input-part: "check:heads" supported
2016 bundle2-input-part: total payload size 20
2016 bundle2-input-part: total payload size 20
2017 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
2017 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
2018 adding changesets
2018 adding changesets
2019 add changeset ef1ea85a6374
2019 add changeset ef1ea85a6374
2020 add changeset f9cafe1212c8
2020 add changeset f9cafe1212c8
2021 add changeset 911600dab2ae
2021 add changeset 911600dab2ae
2022 add changeset e8fc755d4d82
2022 add changeset e8fc755d4d82
2023 adding manifests
2023 adding manifests
2024 adding file changes
2024 adding file changes
2025 adding abc.txt revisions
2025 adding abc.txt revisions
2026 adding foo/Bar/file.txt revisions
2026 adding foo/Bar/file.txt revisions
2027 adding foo/file.txt revisions
2027 adding foo/file.txt revisions
2028 adding quux/file.py revisions
2028 adding quux/file.py revisions
2029 added 4 changesets with 4 changes to 4 files (+1 heads)
2029 added 4 changesets with 4 changes to 4 files (+1 heads)
2030 calling hook pretxnchangegroup.acl: hgext.acl.hook
2030 calling hook pretxnchangegroup.acl: hgext.acl.hook
2031 acl: checking access for user "astro"
2031 acl: checking access for user "astro"
2032 acl: acl.allow.branches not enabled
2032 acl: acl.allow.branches not enabled
2033 acl: acl.deny.branches enabled, 0 entries for user astro
2033 acl: acl.deny.branches enabled, 0 entries for user astro
2034 acl: acl.allow not enabled
2034 acl: acl.allow not enabled
2035 acl: acl.deny not enabled
2035 acl: acl.deny not enabled
2036 acl: branch access granted: "ef1ea85a6374" on branch "default"
2036 acl: branch access granted: "ef1ea85a6374" on branch "default"
2037 acl: path access granted: "ef1ea85a6374"
2037 acl: path access granted: "ef1ea85a6374"
2038 acl: branch access granted: "f9cafe1212c8" on branch "default"
2038 acl: branch access granted: "f9cafe1212c8" on branch "default"
2039 acl: path access granted: "f9cafe1212c8"
2039 acl: path access granted: "f9cafe1212c8"
2040 acl: branch access granted: "911600dab2ae" on branch "default"
2040 acl: branch access granted: "911600dab2ae" on branch "default"
2041 acl: path access granted: "911600dab2ae"
2041 acl: path access granted: "911600dab2ae"
2042 acl: branch access granted: "e8fc755d4d82" on branch "foobar"
2042 acl: branch access granted: "e8fc755d4d82" on branch "foobar"
2043 acl: path access granted: "e8fc755d4d82"
2043 acl: path access granted: "e8fc755d4d82"
2044 bundle2-input-part: total payload size 2068
2044 bundle2-input-part: total payload size 2068
2045 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
2045 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
2046 pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
2046 pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
2047 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
2047 bundle2-input-part: "pushkey" (params: 4 mandatory) supported
2048 pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01"
2048 pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01"
2049 bundle2-input-bundle: 4 parts total
2049 bundle2-input-bundle: 4 parts total
2050 updating the branch cache
2050 updating the branch cache
2051 bundle2-output-bundle: "HG20", 3 parts total
2051 bundle2-output-bundle: "HG20", 3 parts total
2052 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
2052 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
2053 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
2053 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
2054 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
2054 bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
2055 bundle2-input-bundle: no-transaction
2055 bundle2-input-bundle: no-transaction
2056 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
2056 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
2057 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
2057 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
2058 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
2058 bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
2059 bundle2-input-bundle: 2 parts total
2059 bundle2-input-bundle: 2 parts total
2060 listing keys for "phases"
2060 listing keys for "phases"
2061 repository tip rolled back to revision 2 (undo push)
2061 repository tip rolled back to revision 2 (undo push)
2062 2:fb35475503ef
2062 2:fb35475503ef
2063
2063
2064
2064
2065 Non-astro users must be denied
2065 Non-astro users must be denied
2066
2066
2067 $ do_push george
2067 $ do_push george
2068 Pushing as user george
2068 Pushing as user george
2069 hgrc = """
2069 hgrc = """
2070 [hooks]
2070 [hooks]
2071 pretxnchangegroup.acl = python:hgext.acl.hook
2071 pretxnchangegroup.acl = python:hgext.acl.hook
2072 [acl]
2072 [acl]
2073 sources = push
2073 sources = push
2074 [extensions]
2074 [extensions]
2075 [acl.deny.branches]
2075 [acl.deny.branches]
2076 default = !astro
2076 default = !astro
2077 """
2077 """
2078 pushing to ../b
2078 pushing to ../b
2079 query 1; heads
2079 query 1; heads
2080 searching for changes
2080 searching for changes
2081 all remote heads known locally
2081 all remote heads known locally
2082 listing keys for "phases"
2082 listing keys for "phases"
2083 checking for updated bookmarks
2083 checking for updated bookmarks
2084 listing keys for "bookmarks"
2084 listing keys for "bookmarks"
2085 listing keys for "bookmarks"
2085 listing keys for "bookmarks"
2086 4 changesets found
2086 4 changesets found
2087 list of changesets:
2087 list of changesets:
2088 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
2088 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
2089 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
2089 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
2090 911600dab2ae7a9baff75958b84fe606851ce955
2090 911600dab2ae7a9baff75958b84fe606851ce955
2091 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
2091 e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
2092 bundle2-output-bundle: "HG20", 5 parts total
2092 bundle2-output-bundle: "HG20", 5 parts total
2093 bundle2-output-part: "replycaps" 155 bytes payload
2093 bundle2-output-part: "replycaps" 155 bytes payload
2094 bundle2-output-part: "check:heads" streamed payload
2094 bundle2-output-part: "check:heads" streamed payload
2095 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
2095 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
2096 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
2096 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
2097 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
2097 bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
2098 bundle2-input-bundle: with-transaction
2098 bundle2-input-bundle: with-transaction
2099 bundle2-input-part: "replycaps" supported
2099 bundle2-input-part: "replycaps" supported
2100 bundle2-input-part: total payload size 155
2100 bundle2-input-part: total payload size 155
2101 bundle2-input-part: "check:heads" supported
2101 bundle2-input-part: "check:heads" supported
2102 bundle2-input-part: total payload size 20
2102 bundle2-input-part: total payload size 20
2103 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
2103 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
2104 adding changesets
2104 adding changesets
2105 add changeset ef1ea85a6374
2105 add changeset ef1ea85a6374
2106 add changeset f9cafe1212c8
2106 add changeset f9cafe1212c8
2107 add changeset 911600dab2ae
2107 add changeset 911600dab2ae
2108 add changeset e8fc755d4d82
2108 add changeset e8fc755d4d82
2109 adding manifests
2109 adding manifests
2110 adding file changes
2110 adding file changes
2111 adding abc.txt revisions
2111 adding abc.txt revisions
2112 adding foo/Bar/file.txt revisions
2112 adding foo/Bar/file.txt revisions
2113 adding foo/file.txt revisions
2113 adding foo/file.txt revisions
2114 adding quux/file.py revisions
2114 adding quux/file.py revisions
2115 added 4 changesets with 4 changes to 4 files (+1 heads)
2115 added 4 changesets with 4 changes to 4 files (+1 heads)
2116 calling hook pretxnchangegroup.acl: hgext.acl.hook
2116 calling hook pretxnchangegroup.acl: hgext.acl.hook
2117 acl: checking access for user "george"
2117 acl: checking access for user "george"
2118 acl: acl.allow.branches not enabled
2118 acl: acl.allow.branches not enabled
2119 acl: acl.deny.branches enabled, 1 entries for user george
2119 acl: acl.deny.branches enabled, 1 entries for user george
2120 acl: acl.allow not enabled
2120 acl: acl.allow not enabled
2121 acl: acl.deny not enabled
2121 acl: acl.deny not enabled
2122 error: pretxnchangegroup.acl hook failed: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
2122 error: pretxnchangegroup.acl hook failed: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
2123 bundle2-input-part: total payload size 2068
2123 bundle2-input-part: total payload size 2068
2124 bundle2-input-bundle: 4 parts total
2124 bundle2-input-bundle: 4 parts total
2125 transaction abort!
2125 transaction abort!
2126 rollback completed
2126 rollback completed
2127 abort: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
2127 abort: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
2128 no rollback information available
2128 no rollback information available
2129 2:fb35475503ef
2129 2:fb35475503ef
2130
2130
2131
2131
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now